关于网络
网络世界是复杂的。用户可以使用很多技术接入到互联网-电缆调制解调器,数字用户线,蜂窝连接,卫星,以太网和甚至传统的声音调制解调器。这些连接每一个都有不同的个性,包括带宽,延迟,丢包率和可靠性。
用户和互联网的连接的故事还没有讲完,还有别的复杂性存在。从用户到互联网的服务器,用户的数据经过了一个至一打数量的物理连接,这其中可能是高速的数据传输媒介,也可能是低速的传输媒介。更糟糕的是,任何时刻,用户和服务器之间的连接速度会变化剧烈-有人可能打开了一个微波炉,干扰了用户的WiFi通信,用户由于移动可能走出了蜂窝覆盖范围,世界的另一头有人可能开始从服务器下载一部大电影,这个服务正是这个用户在访问的服务器,还有很多别的情况会增加网络的复杂性。
作为基于网络软件的开发者,你的代码必须能够适应网络条件的变化,包括性能,可接入性和可靠性。这个文档告诉你如何做到这些。
惊鸿一瞥
网络的固有本质就是不可靠的-蜂窝网络更是如此。因此,优良的网络代码一般都很复杂。你的软件应该:
- 传输完成任务所需要的代码,仅此而已。最小化需要传输和接收的代码能延长电池寿命,也能降低用户的费用。
- 尽可能避免超时。你应该不希望一个网页停止加载,原因是它持续时间太久了。取而代之的是,为用户提供一个取消操作的方式。
在某些场合,如果延迟太明显,数据会变得不相关。在这些场景中,使用不重传包的协议更说得通。例如,如果你的应用是一个实时的多用户游戏,通过局域网或者蓝牙,向另一个设备发送小的状态信息,丢几个包也是可以的,如果延迟太久又收到之前的包,逻辑反而不对。然而,对于大多数情况来说,除非你要维持和现有协议的兼容性,你都应该使用TCP。 - 设计的用户界面应该能够允许用户轻松地取消费时太久地交易。如果你的应用执行大文件的操作,你应该提供一个暂停下载和继续下载的操作方式。
- 优雅地处理错误。引发连接失败有很多原因-网络不可靠,主机名解析不正确等等。当错误发生地时候,你的程序应该在没有网络的情况下还能以最好的状态继续工作。
有时候,用户只有在某些网络中才能访问资源,这增加了复杂性。例如,AirPlay在相同的网络中才能接入到一个Apple TV。企业网络资源只有在工作网络或者通过VPN才能访问。视觉语音信箱只有通过蜂窝载波的网络才能访问,等等。
特别是,你应该避免设计这样的用户界面,当网络出故障的时候,需要用户来照看你的程序。不要显示modal对话框,告诉用户网络down了。而是当网络再一次工作的时候自动的重新尝试。当不是用户发起时,不要警告用户连接失败。 - 当网络性能差的时候,优雅地降低程序性能。因为用户设备和其ISP之间的带宽是受限的,你的应用通过用户家庭网络接触到其他设备的速度要快于连接世界另一端的服务器。当局域网的某个人开始使用这个受限带宽时,差距更加明显。
- 选择适合任务的APIs。如果有高层的API满足你的需要,就是用高层的API,不要使用低层的API重新实现。如果有一个专用API对应你要做的事,赶紧使用它。
使用高层的API,你提供给了操作系统更多有关你要完成的任务相关信息,这样它能更好的处理你的请求。这些高层的API也为你解决最复杂和困难的网络问题-缓存,代理,为主机选择IP地址等等,如果你用低层的代码来完成同样的任务,你需要自己处理这些复杂性。 设计你的软件,最小化安全风险。利用安全技术,例如SSL和TLS来阻止哄骗,隐藏敏感数据,详细检查不被信任的内容来避免缓冲期和整数溢出。
这篇文档会帮助你学习这些概念。
学习网络困难的原因
虽然为大多数平凡的网络需要编写网络代码可以很简单,但是编写优良的网络代码却不简单。取决于你的软件需要,它可能需要适应网络性能的变化,断开的网络连接,连接失败和其他由于因特网不可靠的固有本质引起的问题。
OS X和iOS提供了不同层次的APIs
你可以在OS X和iOS中使用相同或者近乎相同的代码完成下面的网络任务:
- 执行HTTP/HTTPs请求,例如GET和POST请求
- 建立到远程主机的连接,使用或者不使用加密和认证
- 侦听接入连接
- 使用无连接协议发送和接收数据
- 使用Bonjour发布,浏览和解决网络服务
保密通信是你的责任
适当的网络安全是需要的。你应该将你的用户发送的数据视为机密,因而保护这些数据。特别的是,你应该在传输的过程中对数据加密,并且保护它不发送到错误的用户或者服务端。
大多数OSX和iOS网络APIs提供了TLS的轻松集成。TLS是SSL协议的继承者。除了对数据加密,TLS使用证书对服务端进行认证,以防欺骗。
你的服务端也应该采取相应的步骤对客户进行认证。认证可以简单到一个密码,也可以复杂到一个硬件认证token,取决于你的需要。
对所有输入数据都要隄防。所有从不信任的源接受的数据都可能是恶意的攻击。你的应用应该仔细检查引入数据,将看上去有嫌疑的数据都抛弃掉。
iOS和OS X提供了特殊的平台特色
在OS X上的网络环境是高度可配置和可扩展的。系统配置框架提供了决定和设置当前网络配置的API。另外,网络kernel扩展使你通过增加特色例如防火墙或者VPN来扩展OSX的核心网络设施。
在iOS上,你可以使用网络APIs来处理认知网络的认证,指派VoIP网络流。
网络必须是动态且异步的
一个设备的网络环境在任何时刻都是可变的。有许多简单但是具有破坏性的网络错误能够对你应用的性能和使用性带来负作用,例如在你的程序主线程执行同步网络代码,不能优雅地处理网络变化等等。从一开始设计你的程序来避免这些错误而不是后期调试能够节省你很多时间和努力。
怎么样使用这篇文档
这篇文档适合就顺序阅读
第一章,Design for Real-World Networks,揭示了当软件使用网络时你要面对的挑战,为什么延迟那么重要,和其他一些在开始编写网络代码之前你需要知道的概念。
接下去的一章 Accessing Your Networking Needs,提供了选择API家族的细节并且决定你的程序要执行的网络任务类型。
最后,Using Networking Securely和Avoiding Common Networking Mistakes提 gone 了帮助你避免常见网络错误的指导。
其它
这篇文档为OS X和iOS提供了网络的综述,下面的文档提供了更多的深度和广度。
学习底下发生了什么
了解网络工作的机理帮助你理解他们的行为,这样,你应该学习基本的概念,再开始编写你的网络代码。至少,你应该对包和封装,基于连接和无连接协议,子网和路由,域名查询,带宽和延迟等概念熟悉。为了学习这个主题,可以阅读下面的文档。
Networking Concepts
学习具体的技术
对于更深的信息,咨询下面的文档
URL Loading System Programming Guide
Stream Programming Guide
CFNetwork Programming Guide
NSNetServices and CFNetServices Programming Guide
学习如何在OS X和iOS中分享文档
下面的文档描述了你在OS X和iOS之间分享文档的技术
iCloud Design Guide
Document Transfer Strategies
设计-现实世界网络
在一个理想的世界,网络一直处于工作状态。你的网络连接时可靠的,快速的,低延迟的。在实际网络,网络大多数时间工作,但是当它崩溃的时候,它会变得奇怪有时候还让人着迷。例如:
- 一个过载或者损坏的网络链路会呈现丢包。如果一个链路丢失足够的包,这条链路上建立连接就会变得困难,性能会掉到你原本期望的很小数值,
- 当一条网络链路变得饱和,链路任何一边的路由会缓冲数据避免丢失数据。这增加了另外的延迟。在高度负荷的DSL连接中看到以秒为单位的延迟并不罕见。
- 被俘虏的网络(经常使用在酒店,咖啡馆或者其它公共地方)会拦截你软件的HTTP请求,提供一个注册网页而不是实际的数据。
- 用户和目的地之间的防火墙会阻止除了某些端口的所有连接
- 防火墙会执行NAT,可能不允许远程服务器连接到用户的电脑或者其它设备。
第三方防火墙软件可能会阻挡你的软件向外的连接请求,这中情形通常发生在等待用户授予权利打开连接。
虽然你的软件不能修复实际损坏的网络,但是糟糕的网络代码会很容易地把事情变得更加糟糕。例如,假设一个服务器已经高度负荷并且需要45s来相应每一个请求。如果你的软件连接到这个服务器,并且设定了一个30s的超时,它增加了服务器的工作量,但是永远也不会成功地接收任何数据。
有效地使用功率和带宽
编写网络代码的时候最重要的考虑事项是每一次你的软件上传或者下载数据,它消耗了用户的时间和金钱。
- 用户必须等待操作完成才能执行一些任务
数据传输通常需要无线保持激活状态。对于电池供电的设备,这介绍了用户使用设备的时间。
一个网络操作也会消耗用户的金钱,因为带宽不是免费的。一些消耗包括:
- 电力功率。无线硬件(Wi-Fi,蜂窝等等)消耗功率。无线硬件激活的时间越久,消耗的功率越多。
- 数据传输。许多用户为发送的数据付费。你的软件传输的字节越多,用户付的越多。即使用户有flat-rate 服务,ISP也将消费设置为基于用户消耗的平均带宽。
- 带宽。用户为更快的连接速度付更多的钱。
作为网络软件的开发者,最小化你的软件消耗的功率和带宽是你的责任。
批传输,尽可能的保持空闲状态
当编写代码时,你应该执行尽可能多的工作然后返回到空闲状态。这更适用于网络活动。例如:
- 如果你的应用从一个HTTP服务端下载视频剪辑流,一次下载整个文件(至少时文件的一大部分)而不是每次请求一小块。
- 如果你的应用有广告,一次下载好几个广告,然后显示它们一段时间,而不是需要的时候下载它们。
如果你的应用从服务端下载邮件消息,一次下载一些邮件,这个含有用户可能阅读其中的大部分的假设,而不是用户选择了哪封就下载哪封。
一次下载一点呢日哦那个会造成两个问题。第一,它使得应用对小的网络延迟更敏感,造成视频卡顿。第二,它使得蜂窝或者Wifi radio几乎一直激活。这浪费功率,特别是当你的应用通过蜂窝连接通信。如果你的应用下载大量的数据然后允许无线连接进入空闲状态,你可以极大地提高用户的电池寿命。
这特别适用于socket通信。除了极少的例外(例如远程终端程序),你应该永远避免一次只发送一些字节。考虑到CPU负载,这样做极度的无效率,也会引起操作系统发送更多不需要的包。
下载最小的资源,在本地缓存资源
下载数据有相关的代价-电池,性能,在很多情况下还有数据的传输价格。为此,你应该永远下载满足你需要的asset最小版本。
例如,如果你有一个照片目录应用,下载一系列大图像然后将它们渲染为缩略图,你应该在服务器上渲染那些缩略图。你的应用应该只下载缩略图,当用户选择这个缩略图的时候才下载这个图像的原大小版本。这么做有两个原因:
- 传输数据会让网络硬件和CPU在一段长时间里保持激活状态,这样消耗了功率。通过减少你的程序传输的assets的大小,你可以提高你的用户的电池寿命。
如果你用户在一个测量的因特网连接(例如蜂窝电话),传输更小的assets能减少你的用户数据账单。
由于相同的原因,对下载的资源保持一分当地的缓存可以节省时间,带宽和电池寿命。这样,不需要向server发送资源请求,只要询问这个资源自从你上次下载后是否有变化,如果没有,使用缓存的版本就行。
在OS X和iOS上的很多高层API(NSURL)提供缓存(NSURLCache)支持。然而,你必须选择caches的合适大小。无论你使用内置的缓存API还是生成你自己的,你应该实验cache大小并且替换政策来决定最适用你的应用的设置。
注意 在这里和之前的目标之间常常有一个冲突-一次下载大量的数据这样网络硬件可以处于空闲。例如,考虑一个应用,加载图像缩略图。如果用户一般会浏览好几屏的缩略图,这个应用应该一次下载足够的图像填满几屏幕,这样网络硬件可以在下载之间保持空闲。另一方面,如果用户从不滑到第二个屏幕,所有的额外图像都浪费了带宽。
每一个网络应用都会面临这样一个平衡,所以这取决于你,开发者,选择最好的方式。
优雅地处理网络问题
在今天高度移动的世界,你再也不能假设,一旦因特网连接建立,它一直会保持连接状态,也能假设带宽永远不增加或者减少-正如所提出的,变化是永远不变的。作为开发者,你应该为这些常见的失败做准备,设计你的代码来恰当地应对。
设计-变化的可用网络接口
可用的网络接口会变化,特别在iOS上。例如,用户:
- 在地铁上,在每一个stop上获取一个无线信号,然后离开时又丢失了信号。
- 离开了当前Wife网络的覆盖范围
- 激活了飞行模式或者关闭了Wifi
- 拔出了网络电缆
正因为如此,当编写使用网络的软件时,你需要为网络失败做准备。当一个网络错误发生时,你的程序应该基于许多考虑决定怎么做-其中最重要的是,这个请求是不是用户提出的。
对于用户提出的请求
- 总是尝试生成一个连接。不要尝试网络服务是否可用,也不要缓存这个决定。
- 如果连接失败,使用SCNetworkReachability API帮助诊断失败的原因,然后:
- 如果连接失败由于瞬态错误,尝试再一次连接。
- 如果连接失败由于主机不可reach,等待SCNetworkReachability API呼叫你注册的回调函数,当host又一次可以reach时,你的应用应该重新尝试这个连接而不需要用户干预(除非这个用户采取了某些行动取消这个请求,例如关闭了浏览器或者点击了取消按钮)
- 用非modal的方式显示连接状态信息。然而,如果你必须显示一个错误对话框,确保它不会跟你的应用产生冲突,你的应用当远程host再一次变成reachable的时候会自动尝试连接。当host变成reachable的时候自动解散这个对话框。
对于后台发生的请求
- 尝试连接,使用SCNetworkReachability避免在不方便的时候进行连接-例如,通过检查SCNetworkReachabilityFlagsIsWWAN标记避免蜂窝连接上的不必要traffic。
重要 校验这个reachability标识不保证你的traffic永远不会通过蜂窝连接发送。
- 如果连接失败,使用SCNetworkReachability API等待host再次变成reachable,然后重新发起你的请求。
- 不要显示任何对话框;用户通常不关心不是他们发起的,在后台下载引起的失败。
- 即使网络reachability API告诉你的应用网络发生了变化,也不要重新尝试的太快。当失败重复发生时,你应该慢慢的增加请求间的间隔,知道你到达一个合理的不要再增加的间隔(15分钟,譬如)
你的程序应该能够对当前网络接口的变化作出合理的响应。为了支持这个,使用SCNetworkReachability API。通过注册网络变化通知,当网络接口变化时,你的程序会得到警告。
Reachability样本代码演示了注册网络接口变化的通知。
重要 SCNetworkReachability API不是用来决定网络连接的。你通过试图连接确定网络连接。如果连接失败,咨询SCNetworkReachability API来诊断失败引起的原因。
无论请求来自用户还是后台,SCNetworkReachability API提供了一个很好的方法来观察接口可用变化。当你使用的网络接口消失的时候,你应该马上重新连接来避免不必要的延迟。
在iOS,如果你通过蜂窝网络连接,无论何时WiFi服务重新可用时,你应该马上在后台重连。WiFi连接使用更少的电量,通常也更快,相比蜂窝网络消耗用户更少的钱。
设计-变化的网络速度
你的程序应该对网络速度的变化有准备,即使当前的网络接口保持不变。例如,当要给移动设备用户改变位置时,WiFi或者蜂窝网络的性能能够变化巨大,要么是因为增加的干扰,要么是因为设备切换到另一个小区。这也不需要位置的变动非常大,即使从一个房间走到另一个房间都能造成WiFi和蜂窝服务速度很大的变化。
更进一步的是,即使忽略干扰和竞争,接口本身也不能告诉你几个hop后的实际带宽。当用户试图接入Google时,WiFi网络可能很快,但是用户和你的服务器之间的路由可能通过一个蜂窝调制器或者一个卫星链路。类似的,一个用户所在的局域网可能有1G以太网连接速度,但是只有128kb上行连接速度到外面的世界。因此,你不能根据当前网络接口的速度作出任何总体网络速度的假设。
只有一个方法来决定网络的速度:使用这个网络。当你下载了一小段数据,你可以建立一个初始的网络速度评估。你应该继续检测你的下载速率来维持一个精确的估计,然后调整你的期望。例如,如果你在流化你的视频,你发现你的流速度不再变化,你可能转换到一个更低的带宽流,继续播放。如果你后来发现下载速度提高了,你应该再次默默的切换到高速率的流。
设计-高度延迟
作为一个开发者,假设你的用户可能适用一个高延迟的连接。高延迟再蜂窝网络接口很常见,因为只有有限的时隙能够被一个设备使用。例如,在一个EDGE连接的延迟通常以秒计算。然而,如果你没有在你的软件设计中为延迟做过准备,即使半秒的延迟也能造成严重的问题。
当一个用户向一个或者多个远程主机发送多个请求时,如果它等第一个返回结果后在发送第二个请求,这个连接的延迟成为了加性。第二个请求的延迟加上了第一个请求的延迟,第三个请求也受到了惩罚,惩罚的代价是前两个请求的延迟。
为了避免这个问题,无论什么时候你的程序需要发送多个消息,而且这些消息彼此没有依赖(资源请求,ack等等)。同时发送它们而不是等第一个消息响应返回再发送第二个。下图阐明了同时发送好几个消息而带来的加速。
如果你的iOS应用使用NSURLConnection,你可以启用HTTP pipelining轻松地得到加速。当pipelining启用后,你的连接自动的同时发送多个HTTP请求。呼叫NSMutableURLRequest上的setHTTPShouldUsePipelining:方法。
注意,一些服务器不支持pipelining。如果你连接到一个不支持pipelining的服务器,连接工作但是不会提高性能。
测试不同的条件
Xcode提供了一个工具,叫做Network Link Conditioner,可以模拟不同的网络条件,包括减少的带宽,高延迟,DNS延迟,丢包等等。你应该安装这个工具,启用这个工具,然后运行你的程序看看它在实际条件下执行的怎么样。
这里有一些需要测试的点
- 确保你的软件在带宽很少的情况下仍然能用。
- 将延迟增加到3s或者4s。确保任何用户发起的操作只延迟几秒,而不是几分钟。
- 当网络连接掉包时,你的软件应该继续工作,不过是更慢而已。
你会发现使用第三方工具例如tcptrace来可视化你的软件网络接入样式会比较有用。
评估你的网络需要
在你选择一个网络API之前,你需要对OS X和iOS提供的网络API家族有一些了解。
OS X和iOS提供了3个主要的用户空间网络APIs。前两个-Foundation和CFNetwork(基于Core Foundation),是OSX和iOS专用的框架。最低层的,POSIX和任何Unix或者linux操作系统上提供的一样。
在每一层中,有函数和类支持常见的网络任务,例如连接到远程主机(协议流),下载URLs的内容,在你的局域网发现服务。
使用Foundation类,你可以轻松地完成大多数客户端的网络任务。如果你在编写服务端代码或者你有专门的需要,你可能想要使用低层的框架。然而,作为通用的规律,你应该总是使用贴近你需要的最上层的API。
常见的网络任务
在你决定使用哪个具体的API之前,你必须首先评估你的程序需要执行的网络任务。
为游戏提供peer-to-peer网络支持。在iOS中,Game Kit框架提供了peer-to-peer通讯,全球的(使用因特网)或者当地的(使用蓝牙或者Wi-Fi)。
你可以使用Game Kit来简化下面的peer-to-peer网络任务
- 为多人游戏提供网络通讯
提供语音通讯
任何上面提到的peer-to-peer通讯不包含的网络任务应该由较低层的网络APIs来完成。
为其它应用提供peer-to-peer网络支持。在iOS,Multipeer Conectivity框架提供了peer-to-peer通讯。
连接到web server发送和接收少量信息的最好方法是通过一个标准的协议例如HTTP或者HTTPs。使用这些现存的协议,你可以最小化客户端和服务端支持这个连接所需要完成的工作。HTTP也使得进化到HTTPs连接变得容易-你只是在你的服务端添加一个证书然后在你的URL上添加一个字母。
连接到FTP server除非你必须要和现存的服务器保持兼容,一般使用FTP是不鼓励的。FTP是一个古老的协议,有严重的限制,也没有安全(数据和密码都是明文发送的)。
记住,如果你只需要透过FTP下载文件,你应该使用NSURLConnection API然后传递合适的URL。
对于更复杂的请求,CFNetwork框架(基于Core Foundation)提供了CFFTPStreamAPI来和FTP服务器通讯。CFNetwokr也提供了CFURLAccessAPI,用来删除FTP服务器上的文件。
__发现和广告网络服务__OS X和iOS提供了DNS服务发现的支持,这允许你描述你的程序提供的服务和发现用户机器上,附近机器上的服务。例如,OS X使用DNS Service Discovery让用户发现附近的打印机,从附近的电脑发现stream music,Finder中分享屏幕等等。
__解决DNS 主机名字__OS X和iOS提供了Core Foundation层和POSIX层名字解析API来获取主机名的IP地址。然而,如果你解析主机只是因为想要和它们连接,你一般就使用名字连接就是。
使用socket或者socket streams如果你需要生成的网络请求不是高层的APIs支持的,你可以使用socket(POSIX层和Core Foundation层)或者socket stream(Core Foundation层)。
__安全通信__OS X和iOS支持TLS协议和它的祖先,SSL,用来加密通讯和服务端信任。
下一步
现在你已经决定了你想要做什么,你在OS X和iOS中可以轻松地完成很多网络任务,基本不用配置。许多常见的网络任务和建议的方法都包含在下面的章节。记住,这些不是全面的API讨论:每一章会提供文档连接,提供更具深度的信息。
发现和广告网络服务
OS X和iOS提供了4个APIs用来发现和广告网络服务:
- NSNetService-一个高级的OC API,适合大多数应用开发者
- CFNetService-一个高级的C API,适合使用在Core Foundation代码中
- DNS Service Discovery-一个低级的C API 适合跨平台代码。这个API也提供了更多的灵活性。
- Game Kit framework-一个高级的OC API提供了peer-to-peer游戏的通讯支持,当地的也可以是透过因特网全局的。
除了这些APIs,iOS提供了Multipeer Connectivity Framework,这提供了发现和你的app和先相关的app通讯。
作为一个规律,对于peer-to-peer网络游戏,你应该使用Game Kit。对于其它iOS7.0以上设备间的peer-to-peer网络,你应该考虑使用Multiplayer Connectivity框架。
为了和更老的iOS版本兼容,你也能编写你自己的网络代码,使用CFNetService或者NSNetService。
注意,对于支持蓝牙的设备。Game Kit自动使用蓝牙通讯
Bonjour Service综述
Bonjour service广告包括三个部分
- Service name-这个名字应该对于一台计算机上的一个程序实例是唯一的
- Servcie type-这对于你的程序的所有实例都是一样的,而且应该使用IANA注册。
- Domain-如果这个domain value是空的,主机挑选合适的domains
当一个app浏览Bonjour服务时,它询问某个特定域里某个特定类型的service,然后它获得匹配的服务名字列表。应该想用户呈现合适的UI。当用户告诉应用连接到特定的service,应用应该使用connect-to-service API来连接这个服务。(如果因为某种原因不能连接,应用可以传递service的主机名和端口号到connect-by-name API,如connect-by-name API不能用,应用可以请求Bonjour解析这个主机名,应用可以connect-by-IP和端口号)
公布一个网络服务
Bonjour的零配置网络允许你广告网络服务,例如打印机或者闻到那个同步服务。有三个方法公布一个网络服务。
- 对于OC和Core Foundation代码,建议使用CFNetService API。
- 对于需要移植的C代码,建议使用DNS Service Discovery C API。
你可以通过下列步骤公布一个网络服务
- 生成一个socket侦听连接到该服务的连接。
- 生成一个service对象,提供你的socket的端口号,domain(通常是空字符串),service类型字符串
- 对于Foundation 初始化NSNetService对象,使用initWithDomain:type:name:port:方法。
- 对于Core Foundation,生成一个CFNetServiceRef对象,使用CFNetServiceCreate函数
- 对于DNS Service Discovery API,呼叫DNSServiceRegister返回一个DNSServiceRef对象。
- 分配一个代理或者回调:
- 如果是Foundation,分配一个代理给NSNetService对象。
- 如果是Core Foundation 使用CFNetServiceSetClient函数为CFNetServiceRef对象分配一个客户端回调。
- 如果是DNS Service Discovery API,你应该传递一个客户端回调到DNSServiceRegister。
- 调度或者重新调度这个服务
- 对于Foundation,服务自动的调度在当前的run loop。如果你需要再另一个run loop中调度这个对象或者以其它模式。你应该解调度然后重新调度。
- 对于Core Foundation,你必须使用CFNetServiceScheduleWithRunLoop调度这个CFNetServiceRef对象。
- 对于DNS Service Discovery API,使用DNSServiceSetDispatchQueue在dispatch queue中调度这个service。
- 发布这个service
- 对于Foundation,呼叫publish方法来发布这个service。
- 对于Core Foundation 呼叫CFNetServiceRegisterWithOptions发布这个service。
- 对于DNS Service Discovery API,不需要别的动作了。当你呼叫DNSServiceRegister时,服务已经发布了。
- 分配一个代理或者回调:
当你的service发布后,你可以在你的socket上侦听连接,然后当连接建立后,生成输入输出流。
浏览和连接到网络service
发现和解析网络服务的过程和发布网络服务的过程一样简单。在OC中浏览网络服务,生成一个NSNetServiceBrowser类的实例然后分配一个代理。然后,呼叫serchForServicesOfType:inDomain:方法。netServiceBrowser:didFindService:moreComing代理方法对每一发现的服务都会调用一次。
建立到服务的连接,首先调用stop停止浏览(除非你有具体的理由继续浏览),然后调用getInputStream:outputStream:方法到代表这个服务的NSNetService对象上。服务的地址自动解析。
你也可以使用CFStremCreatePairWithSocketToNSNetService函数连接到Bonjour服务。
解析网络服务
你可能需要手工解析网络服务,向不接受网络服务名字的API提供服务的地址。在OC中解析网络服务,首先调用stop函数停止浏览(除非你有具体的理由继续浏览),然后调用resolveWithTimeOut:方法到这个代表服务的NSNetService对象。
当服务的地址解析后,服务的代理会收到netServiceDidResolveAddress:方法。你可以使用hostName方法或者addresses方法来访问服务的hostname或者它的地址信息。为了不必要的网络流量,当NSNetService对象返回地址后就应该调用stop方法。
重要:这个解析过程返回数值IP地址和一个hostname。IP地址可能是IPv4和IPv6地址的混合。除非你在做什么非比寻常的事,你应该将hostname传递给支持hostnames的API而不是直接使用IP地址,因为否在你就需要编写代码,尝试连接到多个IP地址中的一个。解析器缓存了hostname到IP地址的映射。
Multipeer Connectivity Overview
Multipeer Connectivity框架在Bonjour上又构建了一层,使你能够和附近设备上的应用通讯。而不需要编写很多别的网络代码。
使用Multipeer Connectivity,你的应用为它的可连接性做广告。它接着能够发现运行在附近设备上其它同一应用的实例(或者其它使用相同服务类型的应用),可以邀请那些附近的peers来加入session中。如果它们接受邀请,你的应用可以发送消息和文件到相连的peers,而这仅仅需要一个方法。
重要,和Bonjour一样,你的应用必须提供一个服务类型。
如果你需要基于流的通讯,你的应用可以打开单向的流到连接的peer(它也可以打开单向的流到你的应用作为响应)
最后,Multipeer Connectivity提供了分享少量数据的能力,如果想要,允许你提供信息,让用户在邀请peers加入session的时候可以使用。
显示网页和多媒体内容
OS X和iOS提供了APIs,允许你显示网页内容和流媒体内容。通常,如果这些高层的多媒体和网页API满足你的需要,你应该使用它们而不是直接使用网络APIs。这一部分简要的概括这些APIs。
在默认应用打开网页内容或者流媒体
在用户的默认浏览器或者媒体播放器中打开一个网页或者流URL:
- 在iOS中,使用UIApplication类的openURL:方法
- 在OS X中,使用LSOpenCFURLRef或者LSOpenFromURLSpec函数。
在你的应用中显示网页内容
OS X和iOS提供了简单的方法使用WebKit引擎来装载和显示网页,Safari也使用这个渲染引擎。
- 在OS X中,你使用WebView类来装载网页内容。你可以在nib文件中添加一个web view或者编写代码构造一个WebView对象,调用initWithFrame:frameName:groupName:方法。调用loadRequest:方法装载内容。
- 在iOS中,你使用UIWebView类的loadRequest:方法装载网页内容。
注意 iOS中,Web views在装载数据的时候不停供访问下面的连接,意味着不能自动解析的连接(例如需要认证的连接)会失败。
在你的应用中显示流媒体内容
在iOS和OS X中有几个可用的框架来显示流媒体内容。
- 在OS X中,对于基本播放使用QTKit框架,对于更复杂的功能使用AV Foundation框架。
在iOS中,对于基本播放使用Media Player框架,对于更复杂的功能使用AV Foundation框架。
更多的信息,阅读Multimedia Programming Guide(iOS),和AV Foundation Programming Guide
制作HTTP和HTTPS请求
OS X和iOS提供了许多通用的APIs来制作HTTP和HTTPs请求。使用这些APIs,你可以下载文件到磁盘,制作简单的HTTP和HTTPs请求,或者精确地调整你的请求符合你的服务端设置的具体要求。
当选择API时,你应该首先考虑为什么你要制作一个HTTP请求:
- 如果你在编写一个Newsstand应用,你应该使用NKAssetDownloadAPI在幕后下载内容。
- 如果你需要在OS X中下载一个文件到disk,最方便的方法是使用NSURLDownload类。
- 如果是下面所列的情况,你应该使用CFHTTPStream:
- 你被严格要求不能使用OC
- 你不需要重载代理设置
- 你需要和一个特别的服务端兼容
- 否则,你通常就应该使用NSURLSession或者NSURLConnection APIs。
使用Foundation制作请求
不使用代理获取URL内容
如果你只需要获取URL内容,最后对结果做些处理,在OS X10.9以后或者iOS7以后,你应该使用NSURLSession类。你也可以使用NSURLConnection类,为的是和之前的版本兼容。
为了完成这个任务,调用下面的方法之一:dataTaskWithRequest:completionHandler:(NSURLSession),dataTaskWithURL:completionHandler:(NSURLSession),或者sendAsynchronousRequest:queue:completionHandler(NSURLConnction)。你的应用必须提供下面的信息:
- 要么一个NSURL对象,要么一个NSURLRequest对象,提供URL,body数据,和其它需要的信息。
- 一个完成的handler block,当传输失败或者结束时执行。
- 对于NSURLConnection,提供你的block运行的NSOperation queue。
如果传输成功,请求的内容传递到回调的handler block,以NSData对象和一个NSResponse对象的形式。如果URL loading System不能够获取URL内容,NSError对象作为传递的第三个参数。
使用代理获取URL内容
如果你的应用对请求需要有更多的控制,例如控制是否跟随重定向,执行定制的认定,或者变接收变获取数据。你可以使用NSURLSession类和定制代理。为了兼容较早的版本,你也可以使用NSURLConnection类和定制代理。
NSURLSession和NSURLConnection类工作相似,但是有一些显著不同:
- NSURLSession API提供了类似于NSURLDownload类的下载任务支持。
- 当你生成一个NSURLSession对象时,你提供可重用的配置对象,将许多常见的配置选项封装。使用NSURLConnection,你必须为每次的连接设置那些选项。
- 一个NSURLConnection对象处理单个请求和任意接下去的请求。一个NSURLSession对象管理多个任务,每一个任务表示一个URL请求和任意接下去的请求。当你的应用启动时,你通常生成一个NSURLSession。
- 使用NSURLConnection,每一个connection对象有分开的代理对象。使用NSURLSession,代理在session中的所有任务中共享。如果你需要使用一个不同的代理,你必须生成一个新的session。
当你初始化一个NSURLSession或者NSURLConnection对象时,这个连接或者session自动的调度在默认模式下的当前run loop。
你提供的代理接收通知,包括间歇性的呼叫URLSession:dataTask:didReceiveData:或者connection:didReceiveData:方法。记录已经收到的数据是代理的责任。一般来说:
- 如果数据一次能够被处理一块,就这样做。例如,你可以使用一个streaming XML parser。
- 如果数据很小,你可以将其附加在NSMutableData对象上。
- 如果数据很大,你应该将其写在文件里,当传输完成后再处理。
- 当URLSession:task:didCompleteWithError:或者connectionDidFinishLoading:方法调用时,代理已经收到了URL内容的全部。
下载URL内容到disk
在OS X10.9和ios7.0以后,如果你需要下载URL,将结果存储为一个文件,但是不需要中途处理数据,NSURLSession类提供了直接下载URL到disk(相比装载内容到内存然后你自己编写代码到文件)。NSURLSession类也允许你暂停和重新开始下载,重启失败的下载,当应用悬挂,崩溃或未运行的情况下继续下载。
在iOS中,当下载完成后,NSURLSession类会在幕后运行你的应用,你可以对该文件执行任何应用有关的处理。
注意,在较早版本的OS X,你可以使用NSDownload类下载文件到disk。这个NSDownload类不提供应用不运行的时候下载文件的能力。
在较早版本的iOS,你必须使用NSURLConnection对象下载数据到内存,然后将数据写到文件中。
为了使用NSURLSession下载,你的代码必须这么做:
- 生成一个session,分配代理,选择配置对象:
- 如果你想要在应用未运行的时候继续下载,你需要提供一个幕后session配置对象(一个唯一标示符)。
- 如果你不关心幕后下载,你可以使用任意提供的session配置对象类型生成session。
- 在session中生成或者重新开始一个或者多个下载任务。
- 等待代理收到消息。特别的,你必须实现URLSession:downloadTask:didFinishDownloadingToURL:方法,当下载完成时对文件做处理,URLSession:task:didCompleteWithError:处理任何错误。
注意 上面的步骤是简化的视角;取决于你的需要,你可能想要你的session代理处理许多另外的代理消息,比如定制认证,重定向处理等等。
制作POST请求
你可以制作HTTP或者HTTPs POST请求,制作方法和其它URL请求一样。主要的区别是你必须首先配置一个NSMutableURLRequest对象。
你也需要构造body data。你可以有三种方法:
- 上传少量,内存中的数据,你应该URL-encoding数据。这个过程描述在Encoding URL Data。
- 上传disk中的文件数据,呼叫setHTTPBodyStream:方法告诉NSMutableURLRequest从NSInputStream中读取数据,并且作为body内容。
- 上传大量的数据,呼叫CFStreamCreateBoundPair生成流对,然后呼叫setHTTPBodyStream:方法告诉NSMutableRequest使用那些流中的一个作为body内容的源。
为了对请求指定不同的内容类型,使用setValue:forHTTPHeaderField:方法。如果你这么做,确保你的body数据格式化成那种内容类型。
为了获取POST请求的进度估计,实现connection:didSendBodyData:totalBytesWritten:totalBytesExpectedToWrite:方法。
配置认证
使用NSURLSession和NSURLConnection执行认证相对很直接。
对于NSURLSession类,你的代理应该实现URLSession:task:didReceiveChallenge:completionHandler:方法。在这个方法中,你执行一系列操作决定如何响应这个challenge,然后调用提供的completion Handler。
对于NSURLConnection类:
- 在OS X10.7或者iOS5以后,你的代理应该实现connection:willSendRequestForAuthenticationChallenge:方法。这个方法必须呼叫NSURLConnection一个方法来告诉它如何处理。
- 在较早的版本,你的代理应该实现connection:canAuthenticatieAgainstProtectionSpace:和connection:didReceiveAuthenticationChallenge:方法。
对认证Challenge可能的响应
不管你使用那个类,你的认证处理方法必须检查这个认证challenge然后告诉URL Loading System如何继续:
- 为了给认证提供凭据,传递NSURLSessionAuthChallengeUseCredential(NSURLSession)或者呼叫useCredential:forAuthenticationChallenge:(NSURLConnection)。关于更多生成credential对象的信息,阅读Creating a Credential Object。
- 为了继续请求而不提供认证,传递NSURLSessionAuthChallengeUseCredential,但是nil,或者呼叫continueWithoutCredentialForAuthenticationChallenge:(NSURLConnection)。
- 告诉操作系统处理这个challenge,传递NSURLSessionAuthChallengePerformDefaultHandling或者呼叫performDefaultHandlingForAuthChallenge:(NSURLConnection)。如果你请求默认处理,操作系统发送凭据缓存中合适的凭据。
- 在协商过程中拒绝特定类型的认证,意图是接收另外的方法,传递NSURLSessionAuthChallengeRejectProtectionSpace或者呼叫rejectProtectionSpaceAndContinueWithChallenge:(NSURLConnection)。
生成凭据对象
在代理的connection:willSendRequestForAuthenticationChallenge:或者connection:didReceiveAuthenticationChallenge:方法中,你需要提供一个NSURLCredential对象,这个对象提供实际的认证信息。
- 对于简单的登录密码认证,呼叫credentialWithUser:password:persistence:
- 对于基于证书的认证,呼叫credentialWithIdentity:certificate:persistence:使用一个SecIdentityRef对象(通常从用户的keychain中获取)。
使用Core Foundation制作请求
除了语法细节,Core Foundation中的请求功能和Foundation层密切相关。
Core Foundation URL接入是C语言API,是Core Foundation框架的一部分。
CFHTTPStream API是C语言API,是Core Foundation框架的一部分。
这些APIs是和HTTP服务端通讯的最灵活的方法,提供了对发送到远程服务端消息的body的完全控制,也控制了大多数消息头的。这些APIs也更加的复杂,只有当上层的APIs不能支持你的需要的时候再使用-例如,你需要重载默认的系统代理。
和Web Services打交道
如果你在OS X程序中合并了客户端web service,你可以利用许多技术的优点:
- NSJSONSerialization类提供Cocoa对象和JavaScript Object Noation(JSON)的转换。
- NSXMLParser类提供了Cocoa API,用来解析XML内容。
- libxml2库提供了跨平台的C API介些XML内容。
使用Sockets和Socket Streams
这一章描制作socket连接的方法,让你的程序完全掌控这个连接。大多数程序还是由高层的APIs服务比较好,例如NSURLConnection,这在前面的章节已经讨论过。如果你需要Cocoa内置不支持的协议,或者Core Foundation没有的功能,再使用这些APIs。
选择Socket API
几乎网络的每一个层次,软件都可以分为两类:客户端(连接到其它应用的程序)和服务(其它应用可以连接的程序)。在高层次,分界线很明显。大多数使用高层APIs的程序是纯客户端。在较低的层次,然而,分界线就不明显了。
Socket和Stream编程通常分为下面两大块:
- 基于包的通讯-程序一次作用于一个包,侦听来的包,发送包作为回应。使用基于包的通讯,客户和服务之间的差别在于每个程序发送和接收包的内容,每一个程序处理数据的不同。网络代码本身相同。
- 基于流的客户端-程序使用TCP发送和接收数据,以两个连续的字节流形式。使用基于流的通讯,客户端和服务端区分更加明显。客户端和服务端实际处理数据的部分相似,但是程序一开始构造通讯信道的方式很不一样。
你对基于socket连接的API选择取决于你是要生成一个对另外主机的连接还是接受从别的主机来的连接。也取决于你是使用TCP还是别的协议。下面是一些考量的 因素:
- 在OS X上,如果你已经有和非苹果平台共享的网络代码,你可以使用POSIX C网络APIs,接着使用你的网络代码。如果你的程序基于Core Foundation或者Foundation run loop,你也可以使用Core Foundation CFStream API集成POSIX网络代码到你的整个结构。或者,如果你在使用GCD,你可以在dispatch source中添加一个socket。
在iOS上,不鼓励使用POSIX网络代码因为它不会激活蜂窝或者on-demand VPN。因此,通常来说,你应该将网络代码和任何数据处理功能分开,使用高层的APIs重写网络代码。 - 对于daemons和services,或者非TCP连接,使用POSIX或者Core Foundation(CFSocket)网络APIs
- 对于OC中的客户代码,使用Foundation OC网络APIs。Foundation定义了高层的类管理URL连接,socket streams,网络services和其它网络任务。它也定义了主要的非UI OC框架。
- 对于C中的客户代码,使用Core Foundation C网络APIs-CFNetwork框架的一部分。Core Foundation框架和CFNetwork框架是iOS和OS X平台上主要的两个C语言框架。它们一起定义了Foundation网络类基于的函数和结构。
使用网络安全
无论你在编写银行应用还是游戏,如果你的程序使用网络,它就应该是安全的。对于数据来说,软件不可能决定用户的数据是否保密,尴尬甚至危险,大量看起来没有意义的信息集合起来,可能会成为隐患。
基于这些原因,总是假设你的程序碰到的数据可能包含了银行账号或者密码,因而需要对其保护。
一些你的程序可能会遇到的攻击:
- 窥探-第三方嗅你发送的数据
- 中间人发起的攻击-第三方在你的程序和服务端之间通过计算机进行干预。Man-in-the-middle攻击包括:
- 哄骗和网络欺诈-生成假的服务端,乔装为合法服务端。
- 干涉-修改服务端和你的程序间的数据。
- 会议劫持-捕获认证信息,使用它来冒充你的用户。
- 注射攻击-精心制作数据,让客户或者服务软件执行命令。这通常发生在程序和一个脚本翻译器之间的对话,例如一个shell或者SQL数据服务端。
- 缓冲器溢出和数值溢出-精心制作的数据引起程序读写不应该的地址空间。
这一章解释如何防守窥探和中间人攻击。要学习更多注射攻击,缓冲器溢出和其它软件安全问题,阅读Secure Coding Guide
启用SSL或者TLS
Transport Layer Security协议为基于socket的通信提供了数据加密,服务器和客户认证,来阻止电子哄骗。
OS X和iOS也提供了Secure Sockets Layer协议的支持。因为TLS是SSL的继承者,OS X和iOS在两种协议都支持的条件下默认使用TLS。
注意 设计TLS和SSL主要用来使用在CS模型中,在peer-to-peer环境中使用这些协议确保安全通信更困难。
安全的连接到URL
通过TLS连接到URL很微不足道。当你生成NSURLRequest对象,提供到initWithRequest:delegate:方法时,指定https作为URL的方案,而不是http。连接自动使用TLS而不需要另外的配置。
使用Streams安全连接
你可以在NSStream对象中使用TLS,呼叫setProperty:forKey:。指定NSStreamSocketSecurityLevelNegotiatedSSL作为property参数,NSStreamSocketSecurityLevelKey作为key参数。如果你需要绕过兼容的bugs,你也可以指定一个更加具体的协议,例如NSStream Socket Security LevelTLSv1。
使用BSD Sockets安全连接
当制作安全连接时,如果可能,你应该使用NSStream而不是使用socket。然而,如果你必须直接和BSD sockets打交道,你必须执行SSL或者TLS加密和解密。取决于你的平台,有两种方法可以搞定:
- 在OS X或者iOS5以后,你可以使用Security框架中的Secure Transport API处理你的SSL和TLS握手,加密和解密。
- 在iOS和OS X中,你可以下载开源的SSL或者TLS实现,例如OpenSSL,将编译过的库放进你的应用bundle。
常见的错误
当编写安全网络代码时,开发者会有一些常见的错误。这一部分提供避免错误的建议。
小心你所信任的人
如果应用发送或者接收潜在的保密数据,确保对服务端认证。确保你的服务端认证了用户避免将数据提供给错误的用户。也要确保连接建立使用了合适的加密。
类似的,确保只有需要的时候才存储数据,完成某项任务提供最小的数据。例如,为了保护用户给人信息的隐私,你可能会将你的数据库存储在不同的服务器上,将其配置为只接受从你的网页服务器来的SQL查询,而且跟互联网的连接有限。
小心你所信任的数据
每一个程序都在受到恶意内容攻击的风险。如果你的程序从不信任的服务端接收数据,这更为真实,或者你的程序从信任的服务端获取不信任的数据。(论坛投稿)
为了保护,你的程序应该仔细检查所有从网络或者disk来的数据。如果数据看上去畸形,不要以常规方式处理这些数据。
要知道许多小的泄漏会爆发洪水
总是采取步骤确保你的应用互联网流量保持私有。尽管某些信息看上去无害,一个有技术的攻击者可以结合信息来发现趋势。
因此,你的应用使用加密通信是重要的。
正确安装证书
当使用SSL或者TLS连接到服务端时,如果你的应用得到一个错误,说证书中心未知,假设你从一个有名望的证书中心得到的证书,这意味着你的证书链丢失或者不完整。
当你的客户端接受使用TLS或者SSL加密的连接,它提供两样东西:你的服务端的SSL证书和SSL证书的完整chain,以你的服务端证书为开始,以操作系统认识的信任的证书截止。如果你的chain里有丢失的证书,你会得到这个错误。
为了观察你的服务端到底发送了什么,在终端打下面的命令
openssl s_client -showcerts -connect www.example.com:443
当你打完这个命令,你应该看到你的服务端证书,紧接着是一系列中间的证书。如果你没有看到,检查你的服务器配置。为了获取正确的证书放进你的服务端证书链文件,联系提供你服务端SSL的证书中心。
永远不要禁止证书链确认
禁止链确认将你能够从安全连接得到的好处全都抹杀了。这个连接比使用未加密的HTTP安全不到哪里去,因为它不能保护伪冒服务端。
如果你使用来自信任的证书中的服务端证书,确保你的证书安装正确。
如果你在和self-signed证书打交道,你应该将它们添加到你的测试机器的信任⚓️列表。
平台有关的网络技术
iOS和OS X上的网络很相似,你需要意识到几个小的不同。
在iOS中,你可以使用具体平台的网络APIs未捕获网络处理认证,指定VoIP网络流。iOS网络应用更可能运行在多重地址的设备上,当应用转到幕后时,必须合适的清理网络连接。
在OS X上的网络环境是高度可配置和扩展的。系统配置框架提供了决定和设置当前网络配置的APIs。另外,网络kernel扩展允许你通过添加防火墙或者VPN扩展OS X的网络设施。
这一章描述这些具体平台的不同。
iOS要求你处理后台和指定蜂窝使用政策
这个部分描述iOS上的网络技术,包括捕获网络支持,后台,和只生成WiFi连接。
正确地限制蜂窝网络
有两种方法阻止连接从蜂窝网络发送。使用哪种方法取决于你应用地要求和目标。SCNetworkReachability API中的kSCNetworkReachabilityFlagsISWWAN标记告诉你当你的应用连接到具体host时会使用哪个接口。然而,这个标识会误导人:
- WiFi信号可能消失在你检查reachability之后,连接之前。
- 不同的hosts可能由不同的接口reach。你不能信任一个对A reachability检验对B也是有效的。
- 同一个host的不同IP地址可能由不同的接口reach。如果一个远程host具有IPv4和IPv6地址,iOS一般尝试同时连接两个地址,然后使用首先建立的连接,取消另一个连接。如果用户的蜂窝网络提供IPv6,用户的WiFi网络不支持,连接可能使用蜂窝或者WiFi,取决于哪个网络连接更快速。
注意 在iOS6中,如果WiFi作为主要的接口就不会使用蜂窝网络。
如果你的应用必须严格避免透过蜂窝网络发送数据,你的应用必须声明政策限制。如果你以咨询的方式使用reachabiliy(例如,通过蜂窝网络上传大电影时警告用户),你应该考虑禁用蜂窝连接来制作连接。这样,如果连接失败,向用户请求透过蜂窝网络发送数据然后以没有那些标识的条件下再次尝试。
在Foundation层,你可以使用NSMutableURLRequest上的setAllowsCellularAccess:方法指定请求能不能透过蜂窝连接。你也可以使用allowsCellularAccess来检查当前的值。
在Core Foundation层,你可以在open stream之前设定kCFStreamPropertyNoCellular特征来达到相同的效果。
在iOS的早前版本中,你可以继续使用kSCNetworkReachabilityFlagsIsWWAN作为竭尽所能的方式决定流量是否透过蜂窝连接,但是你要意识它的局限性。
正确的处理后台
当你的应用走向后台时,应用会被悬挂起来,这意味着这个应用不再处理网络流量。在某些情况下,当你的应用悬挂的时候,某些存在的连接甚至会关闭。学习处理后台,阅读 Networking and Multitasking
正确的注册VoIP Sockets
NSInputStream,NSOutpuStream,CFStream和NSURLConnection APIs具有对VoIP通讯的支持。这个支持允许将一个TCP连接注册未VoIP,这样,当你的应用悬挂的时候,当有数据到达socket时引起你的应用重新开始。
更多的信息在 Implementing a VoIP Application
支持捕获网络
一个捕获网络是一个WiFi网络,这个WiFi网络不提供因特网连接,直到用户执行某些动作,例如登录,指定付款,或者同意某些操作。在公共场合,捕获网络很常见,例如飞机场和酒店。
当用户加入捕获网络时,捕获网络通常提供一个web sheet,用户可以和这个网络认证。如果你的应用注册了这个捕获网络的SSID,然而,不会弹出web sheet,用户可以在你的应用中完成认证。
避免常见的网络错误
当编写基于网络的软件时,开发者经常犯一些常见的设计和使用错误,这些错误能导致严重的性能问题,崩溃和其他不正当行为。
清理你的连接
TCP连接保持open直到连接刻意关闭或者发生超时。除非连接启用了TCP keepalive,超时只会发生在有数据传送但是不能传递的情形中。这意味如果你不关闭你的TCP连接,它们会一直open直到你的程序关闭。
建议使用setsockopt设置SO_KEEPALIVE标识来启用TCP keepalive。
尽可能避免在iOS 中使用POSIX Sockets和CFSocket
直接使用POSIX sockets有优点也有缺点。主要的优点是:
- Sockets大大地简化了非苹果平台的网络代码移植。
- 你可以支持其他协议。
主要的缺点是: - sockets有许多复杂的东西,而这些复杂的东西高层的APIs已经为你处理了。这样,你需要编写更多的代码,这意味着更多的错误。
- 在iOS中,直接使用POSIX函数或者CFSocket不会自动的激活设备的蜂窝调制器或者VPN。
避免在主线程call同步网络
如果你在主线程执行网络操作,你必须使用异步呼叫
网络通信易于收到延迟。例如,DNS请求可能需要半分钟,连接会需要更久。如果你执行同步的网络呼叫-等待响应然后返回数据-执行呼叫的线程变得阻塞直到操作完成或者失败。如果这个线程是你的程序的主线程,你的程序变得没有响应。
在OS X GUI应用中,这个引起旋转的光标出现。菜单不响应,点击和键盘流延迟或者丢失,你的用户会变得沮丧。
在iOS中,你的应用如果在规定的时间内不响应用户事件,你的应用会被watchdog定时器杀灭。大多数网络操作的超时都要比iOS 看门狗定时器设定得更久。因此,在一个同步网络呼叫中,如果你的网络连接失败,你的应用绝对会被杀灭。
如果你的iOS应用生成了一个崩溃报告,异常代码是0x8badfood,这意味着看门狗定时器杀灭了你的应用,因为它不响应。这样的崩溃可能是由同步网络呼叫导致的。
Cocoa(Foundation)和CFNetwork(Core Foundation)代码
异步执行网络代码最简单和最常见的方法是将你的网络对象调度在当前线程的run loop中。
所有的Foundation或者CFNetwork网络对象-包括NSURLConnection,NSStream/CFStream,NSNetService/CFNetService还有CFSocket都内置集成了run loop。每个对象具有代理方法集合或者回调函数。
如果你想要执行密集计算的任务(例如处理一个大的下载文件)在你的网络通讯中,你应该在另一个线程中调度。NSOperation类允许你封装这样一个任务在operation对象中,你可以将它添加到NSOperationQueue对象中来轻松地运行在另外一个线程中。
对于更多的信息,阅读Run Loops
在连接到主机前避免解析DNS Names
连接到host的最受偏爱的方式是使用接受DNS名字的API,例如CFHost或者CFNetService。
虽然你的程序可以解析一个DNS名字,然后连接到其IP地址,你通常应该避免这样做。DNS查询通常返回多个IP地址,连接到哪个IP地址通常不明显。例如
- 大多数的现代电脑和一些移动设备都是多重地址。这意味着它们同时存在于多个物理网络。你的计算机可能处于WiFi和以太网络;你的蜂窝电话可能处于WiFi和3G网络等等。然而,不是所有的主机在每个连接中都能连接到。
举个例子,你iPhone上的远程app允许你控制你的apple TV,但是只能通过Wifi连接。这两个设备不能透过你电话的蜂窝连接因为你的apple tv没有公开的IP地址。 - 如果你的设备和你的服务端有多个不同网络的不同IP地址,最好的IP地址取决于网络连接。
举个例子,如果你的家庭媒体客户端有一个局域网IP地址和一个WiFi网络IP地址,操作系统常常可以探测到性能差别,支持更快的,也就是LAN IP地址。相比,如果你的程序查询IP地址,连接到哪个IP地址几乎就是等概率事件了。 - 如果服务端由IPv4和IPv6协议,他可能不能都被连接。使用基于hostname的API,操作系统可以同时尝试,使用首先连接到的地址。如果你的程序查询IP地址,它可能无法成功连接。
- 如果DNS服务端是较老的服务端,不处理IPv6,你呢请求一个AAAA记录,同步的DNS查询阻塞直到请求超时(默认的是30s)。如果你使用接受hostname的API,操作系统会隐藏这个问题,通过并行的发送IPv4和IPv6请求,当IPv4连接成功建立后取消IPv6查询。
- 如果服务端在一个link-local IP地址(因为某种原因 DHCP没有工作)使用Bonjour广告service,如果你的应用查询这个service,将其解析为一个IP地址,这个地址只有当设备持有这个IP地址时才会工作。如果DHCP服务端来自线上,IP地址会变化,当变化的时候,你的程序就不会再连接到旧的地址上了。
如果你使用广告的name,当服务端取得IP地址后,你的程序能够继续连接到这个service。 - 如果你的服务端处于VPN的另一边,通过IP连接不会激活VPN,这意味着主机永远不会连接。
如果你不能避免解析DNS name,首先检查CFHost API满足你的要求;它提供了地址清单而不是单个地址,如果CFHost API不满足你的需要,使用DNS Service Discovery API。
理解IPv6转换
当前的IP协议即IPv4用尽了单个地址。为了解决这个问题,IPv6正在稳定地代替IPv4.取决于你程序的nature,这个转换有一些不同的暗示:
- 如果你使用高层的网络APIs编写客户端程序,而且你通过使用name连接,你应该不需要改变任何事。
- 如果你在编写服务端或者其他低层网络程序,你需要确保你的socket代码在IPv4和IPv6地址下都正确工作。