Network

一个好的网络框架的设计是很复杂的,抛开设计本身,一个好的网络框架的设计应该包含如下几种特性:

尽可能多的传输请求的数据。
避免失效
大流量传输的需要配备UE
适当的错误处理
适当的处理网络状况受限
选择该任务的API
小心谨慎的设计软件,避免风险

考虑网络使用的真实环境

在理想环境下,网络应该是稳定的,高速传输的,低延迟的。但是在现实使用的环境中,网络经常在一些不可思议的环境下出错:

  • 中断或是重新加载的网络经常丢失包信息
  • 当网络线路趋于饱和的时候,路由就会在传输数据的同时缓存数据以免数据丢失,导致了网络延迟
  • 局域网络经常拦截HTTP请求,弹出一个登录页面。
  • 防火墙的存在组织了客户端向服务端的请求,或阻止了部分请求
  • 防火墙的存在使得服务器阻值了部分端口,或是部分设备
  • 第三方防火墙的存在,使得当前网络需要认证

所以在设计网络框架的时候,需要从多方面考虑网络的性能。核心的目的就是减少用户的损失,这些损失可能是:

  • 金钱
  • 设备的使用寿命

电池和带宽高效利用

最需要在写网络框架时考虑的事情是:更新或是下载数据,都在消耗用户的时间和金钱。

网络操作浪费用户的时间:

  • 用户需要等待网络任务完成才能继续后续的操作
  • 传输数据消耗了大量的电力,减少了用户使用手机的时间

消耗了用户的金钱

  • 电力。无线设备,网络耗时越长,所消耗的电力也是越多的
  • 虚拟数据收费
  • 带宽。数据传输越快,用户所付出的费用越高,无论是月租还是包流量

所以,作为一个网络框架的开发者,减少电力的消耗,以及带宽的使用都是义不容辞的责任

批量传输数据,利用程序的空闲时间

在做网络数据传输的时候,尽量一次传输或接收多的数据。例如:

  • 如果应用做了一个数据流的服务,尽可能多的下载这个视屏的资源。取代每次只下载很小的资源
  • 如果应用中包含广告资源,尽量一次性下载完广告资源,取代使用到的时候才去下载的方案
  • 如果应用中使用到了邮件的功能,尽量一次下载多个邮件信息,以便用户回去查看这些信息,取代每次只下载一条信息

WHY,因为当用户每次都下载一个很小的资源的,这样会产生两个问题:

  • 造成当前应用延迟网络敏感,表现是网络减速,视频卡顿
  • 浪费电力,尤其是在蜂窝网络状态下的通讯的时候(碎片网络数量的增加,导致了网络没有空闲的状态)

尽量下载最小的资源文件,并缓存他们

下载数据伴随着很多消耗,电池、性能、虚拟数据的传输…所以,应该在满足也无需求的情况下,尽量的下载最小的资源文件。例如,有一个图片管理的项目,需要下载一系列很大的图片,并且用一个缩略图的形式展示。真正点击这个缩略图的时候才会展示这个图片的大图信息。那么为了更好的网络设计,应该在后台渲染出这个缩略图,在应用显示缩略图的界面,只是请求这个缩略图就好了。当用户点击查看图片详情的时候,再去请求这个图片的完整版。而不是在app中做这些工作。这样做带来的优点:

  • 减少电力的消耗
  • 减少用户金钱的消耗,该用户可能正在使用蜂窝数据请求数据

在应用中,会请求大量的图片,这是不可避免的。针对这类重复使用的资源。一个好的缓存方案是必不可少的。缓存方案的利用可以有效的减少资源的消耗。这个目标似乎和之前的目标是冲突的,但是,处于不同维度的考虑。他们其实是不冲突的,最终目的都是为减少设备的消耗,减少用户金钱的消耗

处理网络错误

在高速发展的今天,一切都在变化中。作为一个开发者,需要制定好方案来应对这些问题。

可变网络接口的设计,在ios中,可变网络接口的变化是有规律可循的:

  • 在地铁中,到站信号就会重新建立,离站信号就会失去。
  • 远离WIFI
  • 关闭WIFI或是打开飞行模式
  • 拔出网线

所以针对这些网络问题,做好防范措施,显得尤为重要。这些防范措施可以是预先规划好的,也可以是根据网络状态做出的一个决定。

如下是在用户的操作下做出的请求的出现问题的处理

  • 总是尝试建立连接。不要去猜测网络是否可达,不要缓存当前的请求
  • 如果网络错误,用SCNetworkReachabilityAPI去检测当前网络的状态,分析出错的原因,之后
    • 如果只是一个小小的网络卡顿,try again
    • 如果是主机无法到达,可以SCNetworkReachability利用这个API做轮询,当主机可达的时候,再做请求。(用户主动取消当前请求除外)
  • 尝试去展示网络出错的信息(非模态的方式)。但是这个对话界面做好不要影响到网络重新链接,当主机可达的时候。也可以设计成当主机可达的时候,就dismiss这个对话。

在background状态下做出的请求

  • 总是尝试建立连接。如果有需要,可以检测当前的网络状态,避免在不必要的请求,例如检测当前环境是否是蜂窝传输,来规避大量数据。(但是利用reachability检测的状态未必就能保证app不再蜂窝状态下不传输数据)
  • 网络出现错误了,等待SCNetworkReachability变得可达了,再重新建立连接。(这个过程可能需要增加一个轮询)
  • 不要显示对话。用户在后台模式是不关心网络是否正常的
  • 避免轮询过快的问题。(这样比较浪费资源,apple给出的参考时间是15min)

针对网络变化的问题,可以在UE设计的时候,增加一个网络的交互。及时的告知用户。(这个也是用SCNetworkReachability来做)

多变的网络速度

无论是WIFI还是蜂窝网络,网速都是变化的。针对网速变化,需要结合应用的特性,来做出相应的调整,例如,一个视屏缓存的业务,那么不仅要为用户考虑当前的网络是否为WIFI来为用户节省开销,还要考虑当前网络的速度,即便在wifi下,也有被限速的时候,所以当网络一直处于一个速度很慢的下载时,可以暂停这个网络。等到一个好的网络时静默缓存。

网络高延迟

网络延迟也是网络请求中不可避免的问题,主要造成的原因有

  • 路由过多。即便光和点在物质中传输速度极快,但是路由器转发包的时间确实不可忽略的,本地到服务器路由过多的话,网络延迟就会越明显
  • 带宽不够
  • 处理带宽不够。服务器高并发处理能力弱

同时网络延时也是可以叠加的,例如,有多个请求,每个请求的参数来源,都来自于上一个请求的响应数据。那么,最后一个数据的网络延时就是所有网络请求网络延时的总和。需要从网络设计上就规避这中增加网络延时的设计。

多个条件下验收

Xcode提供这样一个工具(Network Link Conditioner)可以在simulater下设置网络的各种条件,其中包括:

  • 减少带宽
  • 增加网络延迟
  • DNS延迟
  • 包丢失 …

如果在应用中使用了NSURLConnectionAPI做网络请求,可以开启setHTTPShouldUsePipelining:设置,可以提升这种顺序的网络请求的速度。但是如果服务器不支持处理管道网络请求的话,表现上还是没有变化。

衡量网络需求

在选择网络API之前,先看一下网络家族的API

OSX和ios提供了三个主要的用户端的API layer,前两个是Foundation和CFNetwork(依附于Core Foundation),是特殊的框架对于OSX和ios.底层的API设计,POSIX,与UNIX和LINUX操作系统是一样的。可以使用Foundation完成客户端的网络设计,如果有特别的需求,可以使用底层的API.

常见的网络任务

在选择网络API之前,需要清楚app所需的网络任务

  • 支持点到点的游戏网络请求。在ios中,GameKit支持这种点到点的网络请求,包括全网和本地。可以用GameKit去完成一些网络任务
    • 提供多玩家的网络信息传输
    • 提供语音传输
  • 支持点到点的网络信息传输给其他应用。在ios中,Multipeer Connectivity framework支持这种点到点信息传输。
  • 连接到web服务器。为发送和接受标准的HTTP/HTTPS做准备,使用标准的HTTP协议,可以减少大量的服务端和客户端的工作量,HTTP协议也更好的支持了网络框架向HTTPS框架的迁移,只需要在服务端增加一个证书,客户端增加几行代码就可以了
  • 支持文件传输。除非必须这么做,FTP协议是一个很古老的协议,这个协议存在严重的安全问题(数据和密码都是通过明文的形式传输)。更复杂的FTP可以去CFNetwork Programming Guide.学习更多
  • 发现和推广网络服务。OSX和ios提供DNS Service Discovery,通过这个服务可以为自己的服务定义一个名称,也可以发现周边的服务。例如,可以使用这个技术发现周边的打印机…
  • 解决DNS主机名问题。OSX和ios提供Core-Foundation-layer and POSIX-layer来解析ip地址
  • 使用sockets。如果不想使用顶层的API,可以使用Scokets做网络请求。
  • 连接安全。OSX和ios支持TSL协议,同时支持SSL协议

发现和推广网络服务

OS X和ios提供四个API来发现和推广网络服务

  • NSNetService。顶层OC API,大部分开发使用该API
  • CFNetService。顶层C API,使用Core Foundation时使用
  • DNS Service Discovery。底层C API,提供了更好的柔韧性
  • Game Kit framework。顶层OC API。

为了增加这个API,ios增加了Multipeer Connectivity Framework,这个framework增加了发现和推广的API,利用点到点的WIFI,或是蓝牙设备。但是,在游戏应用中,还是要是用GameKit去做点到点的工作。其他的应用,ios7之后可以使用Multipeer Connectivity Framework。在ios7之前可以使用前两个API处理

优秀的服务概述

一个好的服务宣传应该包含下面三个部分:

  • 服务名称。此名称必须是唯一的一个特定的计算机上运行程序的特定实例。
  • 服务类型。这必须和程序的所有实例相同,并应向互联网编号分配机构(IANA )注册。
  • 服务域名。如果域值为空,主机选择适当的域名,在其中发布或浏览。

当一个app浏览一个网络服务的时候,会向服务器匹配特殊域名下的特殊类型。之后,返回一个服务列表,选择相应的服务,进行连接。

发布一个网络服务

Bonjour是一种0配置的网络推广服务,例如打印机以及文件同步系统。有三种方式来推广一个网络服务:

  • 针对OC/CoreFoundation编程,推荐的方式是CFNetServicesAPI
  • 针对移植性更好的C设计的代码,目的运行在不同的操作系统中,推荐使用DNS Service Discovery C的API

可以根据下面的步骤推广网络服务

  1. 创建一个Socket来监听网络连接状态。
  2. 创建一个服务对象,提供一个端口给socket,domain,type
    • 如果使用的是Foundation,使用NSNetService对象initWithDomain:type:name:port:创建
    • 如果使用的是Core Foundation,使用CFNetServiceRef对象CFNetServiceCreate创建
    • 如果使用的是DNS Service Discovery API,使用DNSServiceRegister来获取对象DNSServiceRef
  3. 指派一个代理/callBack
    • 如果使用的是Foundation,指派一个代理给NSNetService对象
    • 如果使用的是Core Foundation,指派一个客户端的callBack给CFNetServiceRef对象用CFNetServiceSetClient
    • 如果使用的是DNS Service Discovery API,应该传递一个callBack给DNSServiceRegister当处理完成的时候handle这个CallBack
  4. 计划或在必要时重新安排服务
  5. 发布服务

当服务发布之后,可以通过socket检测网络状态,传输数据。

如果使用的代理是自定义类型的,那么配套的网络服务类型也应该是自定义类型的,但是这个自定义类型的网络服务必须符合IANA协议

浏览/连接网络服务

这个过程和发布网络的工程一样简单,用OC的编辑代码来浏览一个服务,首先用NSNetServiceBrowser创建一个对象,并分配一个代理给这个对象,在NSNetServiceBrowser中调用searchForServicesOfType:inDomain:这个函数,netServiceBrowser:didFindService:moreComing:将会被响应,当发现服务的时候。更多的函数调用,可自行尝试。

解决网络服务

在网络服务遇到问题的时时候,可以先叫停这个网络服务,之后利用NSNetService提供的resolveWithTimeout:来检测网络出错的原因。在这个delegate中有一系列的方法来解决网络问题。

多点链接

Multipeer Connectivity Framework提供一个顶层的服务传输信息的服务,可以通过wifi,蓝牙等。通过这个服务,可以发现附近的设备,可以和附近的设备建立连接。

展示网络和多媒体内容

对于展示这些内容,ios和OSX推出了顶层的API,不用考虑之前提到的网络API.

默认APP展示网页或多媒体信息

  • ios中可以使用openURL
  • OS X中使用LSOpenCFURLRef or LSOpenFromURLSpec f

在应用内部展示网页

  • ios中使用webView展示

应用中展示媒体流信息

  • ios中使用Media Player Framework来展示简单的媒体流信息,使用AVFoundation展示复杂的数据流信息。
  • OSX中使用QTKit Framework 来展示简单的媒体流信息,使用AVFoundation展示复杂的数据流信息。

建立HTTP/HTTPS请求

ios和OSX提供了一系列的网络请求给开发人员使用,利用已有的API,可以下载网络数据,发出简单的网络请求等

在选择网络传输的API之前,需要了解网络应用需求

  • 如果是一个报刊应用,需要结合NKAssetDownloadAPI在background模式下,下载数据

  • 如果需要下载文件,可以结合NSURLDownload来使用

  • 使用CFHTTPStreamAPI,如果满足了如下需求

    • 禁止使用OC
    • 重写协议
    • 兼容非标准的服务器
  • 除此之外,还需要了解NSURLSessionNSURLConnection相关的API

    如果有特殊的需求,可以使用更底层的Socket API来设计客户端的网络

用Foundation框架搭建网络

用非代理的方式获取数据

如果只是简单的通过一个URL来获取数据,并当得到数据的时候,处理接下来的事项。可以考虑使用这种方式,现在支持的API有NSURLSessionNSURLConnection

可以使用NSURLSession的两个方法dataTaskWithRequest:completionHandler/dataTaskWithURL:completionHandler:或是NSURLConnection的方法sendAsynchronousRequest:queue:completionHandler:。在使用这两个方法之前,需要如下准备

  • 一个适当的网络请求对象,可以是一个URL,可以是一个NSURLRequest,body…
  • 一个网络完成的处理操作,无论网络回调是成功还是失败
  • 对于NSURLConnection,一个操作队列

当网络请求成功,会返回一个NSData对象来承接网络请求的结果,一个response来描述网络状态。如果失败,返回Error对象,可以对error做解析找到正真的错误。

用代理的方式获取数据

如果想更多地参与到网络的请求中,例如请求调度变更,例如增加网络验证,例如分段接收数据等。可以自定义一个代理方法,实现更多的功能。

NSURLSessionNSURLConnection在顶层的API设计上大部分都是相似的,但是也有不同点

  • NSURLSession中的下载任务的网络请求和NSURLConnection的NSURLDownload很是相似
  • 在NSURLSession中可以定义一个公用的配置信息,而NSURLConnection则做不到这一点,需要每一个请求单独配置
  • NSURLConnection管理一个单独的请求和后续请求,NSURLSession管理多个任务,每个任务相当于一个单独的请求或后续请求
  • NSURLConnection每个请求对应一个代理,NSURLSession每个session对应一个代理,这个代理被多个任务公用着

当初始化了一个NSURLConnection或是一个NSURLSession,这个对象就会被安排到当前的runLoop的默认模式中。

通过代理URLSession:dataTask:didReceiveData:/connection:didReceiveData:来接受网络数据,接受规则中应该具有:

  • 如果数据可以分块处理,获得数据的时候就处理,例如,利用XMLParser
  • 接受的数据比较小,利用NSMutableData拼接
  • 接受的数据比较大,间数据写入文件

connectionDidFinishLoading或是URLSession:task:didCompleteWithError:被触发,代表这个请求已经完成。

从给定的URL中下载文件到磁盘

在ios7/OSX10.9之后,如果想通过一个URL下载数据并保存到磁盘,可以使用NSURLSession,它提供了直接将文件网络文件下载到本地磁盘的API,(代替之前将网络文件下载到当前缓存中,再从缓存中将文件写入磁盘的操作),它还可以做到:

  • 暂停下载

  • 恢复下载

  • 重新启动失败的下载

  • 继续下载当应用暂停使用,奔溃,或是其他方式导致的不能运行

  • 可以在background模式下启动网络下载任务,可以根据自己的需要,完成特殊的任务。

    之前的NSURLConnection不能在background模式下完成任何网络任务,也不能直接将网络文件直接下载到磁盘中,只能通过先将网络文件下载到缓存中,再将缓存中的文件移动到磁盘中去

NSURLSession做下载任务,代码必须遵守如下规则:

  • 创建一个Session和一个自定义的代理以及可选的配置方案:
    • 如果希望应用可以在background模式下进行网络请求,需要提供一个background Session Configuaration(独一无二的key)
    • 如果不关心background模式下的情况,可以指定任意类型的configuration type
  • 创建或是恢复一个下载任务用一个session
  • 必须实现网络处理完成的代理

创建POST请求

创建一个HTTP/HTTPS请求在和普通的URL请求也是很相似的,区别在于必须配置一个NSMutablRequest,并实现initWithRequest:delegate:方法。

除了上述的一些配置之外,还需要构建一个body对象,可以在下面的三个方案中,选择其中的一个构建。

  • 对于小的数据上传,像内存数据,可以将已有的数据片段URLCode。
  • 对于文件数据的传输,可以在NSMutable指定输入流的来源setHTTPBodyStream:
  • 对于大量数据的传输,可以先创建一个CFStreamCreateBoundPair对象,并用setHTTPBodyStream:指定上传的数据来自于这个对象。

setValue:forHTTPHeaderField:方法特殊定义了文件传输类型的话, 需要确认body内的数据是遵守了该编码协议的。对于大量数据的上传,需要管理文件的上传进度,可以实现这个connection:didSendBodyData:totalBytesWritten:totalBytesExpectedToWrite:代理方法,及时查看

管理权限

管理NSURLConnection或是NSURLSession的验证相对简单,主要依赖于使用的网络类和当前目标。

对于使用NSURLSession的网络验证,实现代理URLSession:task:didReceiveChallenge:completionHandler:,当任何需要验证的网络请求都会经过这个代理,这个时候需要加载一个验证对象给completionHandler继续后续的操作

对于使用NSURLConnection的网络验证:

  • ios5/OSX7之后实现connection:willSendRequestForAuthenticationChallenge:完成验证
  • 更早之前的系统需要实现 connection:canAuthenticateAgainstProtectionSpace:connection:didReceiveAuthenticationChallenge:两个代理方法
    • 其中connection:didReceiveAuthenticationChallenge:方法相当于ios5之后的代理方法
    • connection:canAuthenticateAgainstProtectionSpace:必须返回YES当[protectionSpace authenticationMethod]是这其中的任何一个指时:NSURLAuthenticationMethodDefault,NSURLAuthenticationMethodHTTPBasic,NSURLAuthenticationMethodHTTPDigest,NSURLAuthenticationMethodHTMLForm,NSURLAuthenticationMethodHTMLForm,NSURLAuthenticationMethodNTLM
可能通过验证回去到回调的方案

忽视选择网络实现类的方案,当网络需要验证的时候,必须提供一个网络凭证给URL加载系统

  • 为了提供一个网络凭证,通过NSURLSessionAuthChallengeUseCredential(for NSURLSession)或是useCredential:forAuthenticationChallenge:来创建一个网络凭证对象
  • 为了在不提供网络凭证还可以继续网络请求,可以传递一个nil值给NSURLSessionAuthChallengeUseCredential来创建一个凭证(for NSURLSession),或者在NSURLConnection中调用continueWithoutCredentialForAuthenticationChallenge:
  • 如果取消验证,在NSURLSession中调用NSURLSessionAuthChallengeCancelAuthenticationChallenge,或是在NSURLConnection中调用cancelAuthenticationChallenge,但是造成的问题是,这个网络代理会立即报错。
  • 告诉网络用系统通用的方式处理网路认证,在NSURLSession中使用NSURLSessionAuthChallengePerformDefaultHandling,在NSURLConnection中使用performDefaultHandlingForAuthenticationChallenge:。如果设置了这种处理方式,系统会从凭证缓存池中匹配一个合适的发送出去
  • 拒绝莫以特殊身份的验证,在NSURLSession中使用NSURLSessionAuthChallengeRejectProtectionSpace,在NSURLConnection中使用rejectProtectionSpaceAndContinueWithChallenge:
创建一个身份认证对象

在代理方法connection:willSendRequestForAuthenticationChallenge:或是connection:didReceiveAuthenticationChallenge:被调用的时候,应该提供一个NSURLCredential验证对象,在这个凭证对象中包含虚拟的验证信息

  • 例如简单的用户名/密码验证。可以使用credentialWithUser:password:persistence:
  • 例如证书认证。可以credentialWithIdentity:certificates:persistence:创建一个SecIdentityRef对象(通常这个对象的获取可以通过系统的KeyChain)

利用Core Foundation来创建请求

从实现来说,底层的实现和顶层实现的目标都是一样的,所以理解了如何在Foundation框架上实现构建网络对于底层网络的立即也是有帮助的。但是Apple给出的理念是在顶层API可以完成的网络构建时,尽量不要使用Core Foundation来构建网络,除非

  • 足够熟悉CoreFoundation中的C API
  • 不满足当前需求,例如,需要在请求中更改网络协议

否则,使用顶层的Foundation网络吧!!!!CoreFoundation太复杂了

网络服务处理

如果在OS X程序整合客户端Web服务通信,可以采取一些技术优势:

  • NSJSONSerialization可以帮助解析Json文件
  • NSXMLParser 可以帮助解析XML文件
  • 引用libxml2库可以帮助解析多类型的XML文件
  • NSXMLDocument支持解析DOM节点XML

除此之外,还有很多的第三方库可以使用。利用好第三方资源,可以大大的增加工作效率。

使用套接字

Socket的使用场景是用户希望可以自定义一些网络协议。如果没有这方面的需求可以使用Founfation提供的顶层的API来构建网络。

选择API

在网络的所有层面,大致可以分为两个部分:客户端和服务端。如果使用顶层的API设计,整个网络逻辑就会比较清晰,这样的设计一般都是纯粹的客户端采用的方案。如果使用底层的API,网络逻辑可能没有顶层API的逻辑清晰,这样的设计可以在服务端中出现。

套接字和数据流编程一般分为以下几大类:

  • 基于数据包的请求,程序在同一时间操作一个包数据,监测传输来的数据,发送包作为返回。这种方案的客户端与服务端的不同在于传递数据本身内容的不同,编码处理的方式都是相同的
  • 基于数据流的请求,利用TCP的传输协议发送和接受数据流信息在两个数据流上,其中一个是双工的。

API的选择的依据是:是想建立一个服务器连接或是获取一个服务器连接,同时也依附于:使用TCP数据流或是自定义网络协议。这里有一些因素可供参考:

  • 在OSX系统中,如果选择了非APP平台共享的代码,可以选择POSIX C网络协议继续网络编码。如果程序基于CoreFoundation或是Foundation,可以使用CoreFoundation的CFStream整合到POSIX网络中去,如果使用的GCD,可以将Socket作为一个网络资源发放出去。

    在ios系统中POSIX协议是不可行的,在使用蜂窝网络或是VPN的时候,POSIX就会变得不友好,基本不支持上述两种请求方式,所以还是使用顶层的API来代替
  • 对于一个端口上侦听守护程序和服务,或者是一个非TCP协议的网络请求,可以使用POSIX或是CoreFoundationAPI

  • 对于用OC语言设计的客户端代码,可以使用FoundationAPI,顶层的API设计提供了方便的网络管理,Socket管理,网络服务支持,以及一些网络任务的管理。同时他还是一个非UI的框架,提供运行循环,字符串处理,集合对象,文件访问服务。

  • 对于用C语言设计的客户端网络服务,可以使用CoreFoundation来设计API,

网络安全

如果在写一个银行的app或是一个游戏的app,一旦使用到了网络,必须保证的数据安全。总体来说,简单的,琐碎的数据是不可能通过软件监控来得知数据是安全还是存在风险的,但是,当获得了大量数据,这个时候数据的规律就会便可清晰,这个时候用户数据就会被泄露出去。

为了保证数据的安全性,希望用户获取的没一条信息都是经过网络验证了的。

这里有一些网络攻击的手段:

  • 网络窥视。来自于网络传输时,第三方的网络窥视
  • 中间人攻击。穿插在用户和服务器中间的攻击方式。这种方式包含:
    • 窥视网络内容/伪装服务器。创建一个错误的服务,或是伪装成一个合法的服务
    • 截取篡改数据。在用户和服务器交互的时候,截取或是篡改网络服务数据
    • 劫持session。伪造当前session,可以欺骗服务器
  • 植入攻击。这是一种特殊手段,可以使得用户或是服务器执行额外的任务。但是使用的场景往往也是比较特殊的,例如在服务器下发的更改客户端数据库的时候,植入SQL命令。
  • 地址攻击。一种特殊手段,可以使得程序执行非指定代码的地址。这个地址中可能存在可执行代码,来读写用户的私人信息。

这一节主要讲述的就是如何避免上述的攻击。

支持TSL/SSL协议

TLS(Transport Layer Security)协议是基于socket的网络传输协议,通过服务器验证或是客户端验证来防止网络攻击。ios/OSX系统支持SSL(Secure Socket Layer)协议.TLS协议本身就是SSL协议的前身,如果支持SSL协议的话,TSL协议默认也是支持的。

建立一个安全的URL连接

创建一个简单的安全的URL连接使用TLS协议,显得不必要,配置工作太繁杂了。直接使用HTTPS协议可以使用O配置的网络TLS协议,默认使用HTTPS协议就是指TLS协议的。

建立一个网络流连接

可以在建立网络连接流的使用为NSStream对象设置setProperty:forKey:,设置value为 NSStreamSocketSecurityLevelNegotiatedSSL,key为 NSStreamSocketSecurityLevelKey ,如果想更多的解析出现的bug,可以增加网络配置,NSStreamSocketSecurityLevelTLSv1

利用BSD Sockets建立一个安全的连接

当建立一个安全连接的时候,如果可以的话使用上面的NSStream来创建连接代替使用Socket。如果必须使用BSD Socket来创建连接,必须使用SSL/TLS来加密会解密。根据使用场景,这里有两种方式:

  • 在OSX或是ios5之后的设备,可以参考Secure Transport API来处理SSL/TLS的握手操作。
  • 在ios/OSX,可以下载SSL/TLS的第三方实现,例如OpenSSL,可以将第三方的可执行文件下载当前的bundle中。但是在使用的时候必须确认当前的可执行文件是有许可的。

在OSX中使用其他的协议

为了使用默认TLS网络加密的协议,在OSX中可以使用,如下的网络协议作参考。

  • The Kerberos protocol is available via the Kerberos framework.
  • The Secure Shell (SSH) protocol is available.
  • The OpenSSL implementation of TLS is available

简单错误

这里是一些开发者在开发程序的时候可以造成的错误。这里列举了一些建议的方法来规避这些错误。

认清你可以信任的对象

当你发送或是接受敏感信息的时候,必需验证服务确保信息没有被篡改,必须在服务端验证用户的身份,一面伪造的用户身份发送有害的信息。同事必须确认网络连接建立在一个适合的加密方案上。

同样,要确保你只在必要时存储数据,并仅提供执行任务所必需的最低限度。例如,要最大限度地提高用户隐私的个人信息,你可以存储你的web服务器到单独的服务器,接受SQL查询只从你的网络服务器,并与有限的连接到互联网作为一个整体的数据库。换句话说,正确的分离特权

认清可以信赖的数据

当程序接受到了伪造的数据,或是攻击数据的时候都将不安全。尤其是当程序获取到了不被信任的服务器数据,或是,获取到了不被信任的数据即便这个数据是来自可以信任的服务器(网络拦截)。

为了避免这种类型的错误,客户端需要检测获取到的网络数据的安全性,只要存在一点点的差别,就不在执行网络下载的数据。

懂得千里之堤毁于蚁穴的道理

谨记在使用网络数据的时候,确认数据的安全性。即便他看起来是无害的,但有可能在某次网络请求结束的时候,获取到了足够的数据,组装到了一起,后悔晚矣。这些数据甚至不需要是目的性很强的数据,但是从这些数据中可以分析出一个用户的习惯,根据这些习惯可以知道这个用户接下来可能做什么,谁又知道接下来的事件中不会被人做了手脚了。所以保证用户信息的安全是一个APP应该首要考虑的事情。

安装正确的证书

在基于TLS/SSL建立网络连接的时候,经常受到证书错误,或是未知的验证机构。这往往说明你的证书丢失或是不完整。

当你的服务器接受使用TLS或SSL加密的连接,它提供了两件事情:服务器的SSL证书和SSL证书的完整链条,从你的服务器的证书开始,由受信任证书之一签名的证书截止。如果在你的环节中缺少证书,就会出现证书丢失的错误,因为证书的验证需要通过证书连接来验证通过。

不要将证书验证的功能关闭(除非已经手动验证)

关闭证书验证的功能将使得之前使用证书验证的好处都被废弃。导致的问题有

  • 网络连接不安全
  • 网络传输不被保护,攻击者可以看到传递信息的明文
  • 可以任意的更改信息

如果已经使用了证书验证的功能,那就安装正确的证书即可。同时可以将证书存储在KeyChain中加以应用。

避免简单的网络错误

当开发者使用软件构建项目的时候,难免会在设计上或是使用上考虑不全而产生一些隐藏的bug,有可能会对性能造成影响。这一章节主要讲述的就是如何避免常见的bug。

清楚网络连接

TCP网络连接在当前任务没有被明确关闭或是没有过时操作的时候是一直处于打开的状态的。除非你是需要将这个连接一直处于打开状态,否则,还是关闭的好。但是这里需要介绍网络过时产生的条件:网络任务中等待将数据发出,却由于某种原因导致一直没有分发下去。这个状态是随机的,不可控的。当没有将网络明确关闭的时候,TCP连接还是一直建立的状态。

在ios避免使用POSIX Socket和CFSocket

使用POSIX Socket有其好处也有其坏处,相对于使用顶层API来说。主要的好处在于:

  • 在非Apple平台的使用时更简单的网络编码方式
  • 可以支持其他的网络协议,(可以避免使用默认的TCP协议)

主要的不可取之处:

  • Socket相对于顶层的API来说更加的复杂,这也就意味着:在完成一个任务的同时,编辑了更多的代码,带来的更多出错的风险。
  • 在ios系统中使用POSIX Socket或是使用CFSocket默认情况下不支持蜂窝数据和VPN传输

所以使用Socket的场景大多是在跨平台支持的时候。普通状况下,还是建议使用顶层的API

避免在主线程调用同步网络连接

如果你在主线程上调用网络请求,最好使用异步的方式调用。首先,网络请求本身就是一个延时的操作,一个网络请求的发出,DNS解析就有可能花费30秒以上的时间,加上网络本身可能会小号更多的时间。如果在主线程上调用这个网络请求,在网络回调没有完成之前,整个app将陷入不可操作的状态。

如果这是在OSX系统下的GUI应用中,应用将一直等待鼠标的出现。或者按钮选项点击不响应,延迟或是操作任务丢失。这将使你的用户相当的恼火。

在ios系统中,应用会被watchdog杀死,当这个应用不能响应用户的操作超出了规定的时间的时候。如果你在主线程上调用网络请求,网络延时往往大于watchdog默认的时长。所以,经常收到0x8badfood这样的crash报告,这种地址的报错,说明你的程序中存在响应超时的行为。

使用cocoa(Foundation)和CFNetwork(Core Foundation)编程

最早时期的网络请求的异步方式是将当前网络请求的线程加入到runloop中。所用的Foundation和CFNetwork子对象在编译的时候都是被整合到当前的runloop中去的。这些方法往往都有自己的callback对象。如果使用网络获取大量的数据,可以将这个网络分配到一个单独的线程中去。NSOperation可以很好的支持此类操作。

Note

Apple给出的Reachability的使用场景是,当网络链接出错的时候,利用SCNetworkReachability分析出错的原因。并不是要让SCNetworkReachabilityAPI承担网络检索状态的任务,这个任务还是在建立链接的时候去判断的。

TSL协议和SSL协议只是为客户端服务类型做的网络协议,如果想用它来做P2P的网络加密,这个就比较困难了。

在使用第三方OpenSSL库文件的时候,必须了解该库文件的功能,避免版本的不同导致的功能不适用。

参考

Networking Overview