iOS 网络优化: 使你的 App 网络交互更流畅

原创
10/28 08:30
阅读数 1.2W

作者:Tom, QQMailApp 创始团队成员之一,经历了 QQMailApp 从0到亿的过程。2017年加入字节跳动,现在负责字节跳动广州研发中心的技术管理工作。正在搭建团队中,大量招聘 iOS /Android/Windows/前端/后端,也可以加微信号 tomtan 交流一下喔


Sessions: https://developer.apple.com/videos/play/wwdc2020/10111/


本文发表于 2018/09/13 《WWDC18 内参》

弱网优化

所谓的弱网络,也就是指在网络不好的条件下进行使用 APP,如 2G、3G 网络,这类网络条件下,用户的网络速度基本维持在10K/S~60K/S。如此差的网络环境, 如果还希望给用户提供良好的用户体验,那么我们的APP就该想想如何优化了。根据腾讯分析的报告,目前国内 App 通过 2G 和 3G 方式接入的比例只有 6% 左右,在 2018 年前后,国内的三大移动运营商将会停止提供 2G 网络服务,因此大部分的国内用户还是在处于网络环境好的情况下使用 App 的。

然而,如果考虑一下需要做海外应用的话,那就必须考虑弱网优化的方案了。根据掌众集团在GMTC2018中分享的经验,东南亚国家慢速网络用户的比例并不小

而爱立信的分析报告,在世界上还有很多国家依然是慢网络占主流。

苹果建议,我们应该从工程的最开始,就是用苹果内置的Network Link Conditioner 来模拟各种的网络环境来处理 App 的体验问题。Network Link Conditioner在 Xcode 和手机中都可以开启,具体可以参考这篇文章 Network Link Conditioner[1]

<<< 左右滑动见更多 >>>

在实际开发的时候,当遇到了网络层的性能问题时,可以使用Wiresharktcptrace 等工具来追踪网络请求的具体细节来定位问题,特别是tcptrace,提供了很多很有价值的特性来帮助开发者提高程序调试的效率

关于Wireshark的使用,推荐两本书《Wireshark网络分析就这么简单》[2]《wireshark网络分析的艺术》[3],几乎是手把手教会使用Wireshark

tcptrace的使用教程可以参考使用tcptrace输出网络性能[4],相关资源可以参考这个网站[5],其中有不少工具可以输出可视化的图表数据

关于更多网络调试工具的介绍,可以参考WWDC2015:Your App and Next Generation Networks[6]中的介绍

尽量使用IPv6

IPv6拥有更好的 IP 拓展性,更高的安全保障以及更快的传输速度,互联网协会将2012年6月6日定为了世界IPv6启动日,距此 5 年后,国内外 Cloudflare、又拍云等 CDN 服务已支持IPv6,国内商业网站腾讯、阿里等已开始对旗下部分网站服务进行了IPv6改造升级。

IPv6更快有两个原因。第一点,像 iOS、MacOS、Chrome 和 Firefox 这样的主流的操作系统或者浏览器,在它们使用IPv4连接的时候,会强加一个 25ms~300ms 的人工延迟。第二点,某些仅支持IPv6的移动终端网络就无需进行 v4→v6 或 v6→v4 这样的翻译以使用户成功访问仅支持IPv6网络的站点。

在美国,已经有 87% 的移动运营商在提供IPv6,而根据 Google 的统计,在中国只有 2.69% 左右的IPv6网络请求

想知道你当前的网络线路是否支持IPv6,可以使用这个网站[7]进行免费的检测

以印度的网络环境为例,以下是IPv6IPv4的网络质量对比,在IPv6下,75% 的网络连接在不到 150毫秒 内建立起来,而IPv4却需要超过 350毫秒 的时间。个人猜想造成如此巨大的性能差异应该跟印度的网络建设方案有关,可能对于IPv6网络提供了更大的带宽,更好的流量控制、更少的 NAT 从而实现更高效的网络拓扑结构(IP 地址资源多从而不需要对数据包进行多次地址翻译和转发)。

显式拥塞通知(Explicit Congestion Notification)

在网络中传输数据,如果发生了数据包丢失而导致重传的话,性能将会显著下降,而如果使用了显式拥塞通知技术,就可以有效地改善此现象。显式拥塞通知(英语:Explicit Congestion Notification,简称ECN)是一个对网际协议和传输控制协议( TCP )的扩展,定义于 RFC 3168(2001)。ECN允许拥塞控制的端对端通知而避免丢包。由于ECN仅在配合活动队列管理( AQM )策略时有效,因此ECN的益处依赖于所用的 AQM 的精确度。如预期那样,ECN减少 TCP 连接中被丢弃的数据包数量,以避免重传、减少等待时间,尤其是网络抖动。当 TCP 连接有单个未完成段时,这种效果最为明显,它可以避免传输控制协议( RTO )超时;这通常发生在交互式连接时,例如远程登录,以及事务协议,例如 HTTP 请求、SMTP 的会话阶段、SQL 请求。关于ECN的工作原理,可以参考拥塞控制机制(ECN, QC-QCN)[8]

ECN技术已经在 macOS 和 iOS 中默认启用了好些年(从 2015年5月 开始打开默认开关,对应 OS X 10.11 和 iOS9),因此开发者可以无需在 app 中添加任何代码即可享受此特性,以下截图来自WWDC2015:Your App and Next Generation Networks[9]

苹果公司从 Alexa 中排名靠前的域名抽查,近几年支持ECN技术的域名越来越多了。目前开启ECN特性支持还需要硬件厂商的配合,因此开发者可以咨询自家的云服务厂商来查看是否已经可以使用上这个技术

Multipath TCP (MPTCP)

MPTCP允许在一条TCP链路中建立多个子通道。当一条通道按照三次握手的方式建立起来后,可以按照三次握手的方式建立其他的子通道,这些通道以三次握手建立连接和四次握手解除连接。这些通道都会绑定于MPTCP session,发送端的数据可以选择其中一条通道进行传输,因此MPTCP可以让用户在切换网络环境的时候,不需要重新建立 TCP 连接。以下图文来自WWDC2017 Advances in Networking, Part 1[10]

从全球移动运营商中做的调查来看,78% 的网络可以支持MPTCP

如果需要开启MPTCP的话,代码实现大致如下

// Load MPTCP

func loadWithMultipathServiceType _ type : URLSessionConfiguration.MultipathServiceType, handler:@escaping (MPTCPStats?, Error?) -> Void) {
    let urlString = "http://amiusingmptcp.de/v1/check_connection"
    
    let sessionConfiguration = URLSessionConfiguration.ephemeral
    sessionConfiguration.multipathServiceType = type
    let session = URLSession(configuration: sessionConfiguration)
    let url = URL(string: urlString)!
    session.dataTask(with: url) { (data, response, error) in
        
        // Error 1: Check error variable
        
        if let data = data {
            
            let decoder = JSONDecoder()
            do {
                let stats = try decoder.decode(MPTCPStats.self, from: data)
                handler (stats, nil)
            }
            catch {
                handler (nil, error)
            }
            if let string = String(data: data, encoding: .utf8) {
                print (string)
            }
        }
        else {
            handler(nilRequestError.noData)
        }
        
        }.resume()
}

其中URLSessionConfiguration.MultipathServiceType有4种类型

  • none :不启用,默认选项

  • handover :优先考虑的是链接的可靠性。只有在 Wi-Fi 信号不好的时候,流量才会走 Cellular

  • interactive :优先考虑的是链接的低延时。系统会看 Wi-Fi 快还是 Cellular 快。如果 Cellular 比 Wi-Fi 快,哪怕此时 Wi-Fi 信号很好,系统也会把流量切到 Cellular 链路

  • aggregate :在这种模式下,Wi-Fi 和 Cellular 会同时起作用。如果 Wi-Fi 是 1G 带宽,Cellular 也是 1G 带宽,那么你的设备就能享受 2G 带宽(只能在开发者模式下使用)

TCP Fast Open (TFO)

TCP 快速打开(英语:TCP Fast Open,简称TFO)是对计算机网络中传输控制协议( TCP )连接的一种简化握手手续的拓展,用于提高两端点间连接的打开速度。它通过握手开始时的 SYN 包中的TFO cookie(一个 TCP 选项)来验证一个之前连接过的客户端。如果验证成功,它可以在三次握手最终的 ACK 包收到之前就开始发送数据,这样便跳过了一个绕路的行为,更在传输开始时就降低了延迟。通过TFO就可以缩短建立 TCP 连接的握手时间(因为少了一次握手)

目前 TFO 特性需要服务器端的支持

Quick UDP Internet Connection(QUIC)

Quic全称 quick udp internet connection ,“快速 UDP 互联网连接”,(和英文 quick 谐音,简称“快”)是由 google 提出的使用 udp 进行多路并发传输的协议。

Quic相比现在广泛应用的 http2+tcp+tls 协议有如下优势 :

  1. 减少了 TCP 三次握手及 TLS 握手时间。

  2. 改进的拥塞控制。

  3. 避免队头阻塞的多路复用。

  4. 连接迁移。

  5. 前向冗余纠错。

更多的QUIC标准的介绍,可以参考这篇文章[11],一种通俗的理解就是,目前 HTTP2.0 是基于 TCP 实现的,QUIC协议用 UDP 将 HTTP2.0 再实现了一次。目前苹果对于该标准保持了长期关注的态度,一旦该标准正式发布以后有望在 iOS 中启用。由于目前这个协议主要是 Google 在推进,所以开源库中也只有 Chromium 中的Cronet网络库[12]可以使用(这个库看起来Android端使用会比较成熟)。

优化dns查询时间

有很多案例显示,为了避免 DNS 中心出现错误的缓存,所以有些网站设置了过短的 DNS 缓存时间,如60秒或更短,而如果可以对 DNS 查询结果做一次标记,这样就可以更好地利用缓存。这个被称为乐观 DNS(Optimistic DNS) 的优化。DNS 是有时效限制的,当查询的结果过期后,我们可以乐观地认为这个结果依然是有效的并使用这个 IP 地址建立连接,同时,系统会并行的发起一个新的 DNS 请求。目前这个策略在 cloudkit 里面已经使用。如果想要手动应用上该特性,可以按照如下示例来做

parameters.expiredDNSBehavior = .allow
let connection = NWConnection(to: endpoint, using: parameters)
connection.start(queue: myQueue)

如果之前查询得到的 IP 地址确实是有效的,并且你设置了.expiredDNSBehavior = .allow,那么这就省去了等待 DNS 查询结果的时间。如果服务器的地址确实改变了,那么系统将等待查询结果并用新得到的地址建立连接。这就是被称为 Happy Eyeballs(RFC 6555) 算法。想了解更多,可以参考WWDC2018 Introducing Network.framework: A modern alternative to Sockets[13]

不要用系统的网络接口判断网络可用性,而应该让系统的属性去自然处理网络请求

如果使用SCNetworkReachability作为网络可用性检查,会因为用户网络环境的瞬变而导致不可预知的结果,建议还是使用waitsForConnectivity属性来更好地处理网络流程,后面会说到具体的策略。关于这部分的介绍,可以回顾WWDC2017 Advances in Networking, Part 1[14]

iOS12将会支持TLS1.3

TLS1.3版本是一个飞跃性发展的版本,主要的特性包含

  1. 废除不支持前向安全性的 RSA 和 DH 密钥交换算法;

  2. MAC 只使用 AEAD 算法;

  3. 禁用 RC4 / SHA1 等不安全的算法;

  4. 加密握手消息;

  5. 减少往返时延 RTT,支持 0-RTT;

  6. 兼容中间设备TLS 1.2

直观的流程对比如下所示

总体而言,TLS1.3将会比TLS1.2更安全,速度更快

证书透明度日志(certificate transparency logs)

当前的数字证书管理系统中的缺陷正使欺诈证书导致的安全问题与隐私泄露风险变得日益明显。2011年,荷兰的数字证书机构 DigiNotar 在入侵者利用其基础设施成功创建了超过 500 个欺诈性数字证书后申请破产。证书透明度(英语:Certificate Transparency,简称 CT)也称证书透明、证书透明化,它是一个实验性的 IETF 开源标准和开源框架,目的是监测和审计数字证书。通过证书日志、监控和审计系统,证书透明度使网站用户和域名持有者可以识别不当或恶意签发的证书,以及识别数字证书认证机构( CA )的作为,避免证书颁发机构发布了流氓证书。从客户端是可以验证证书是否带有公开日志信息。

从 iOS12 以后,底层的网络库将会默认检验 https 证书是否带有日志信息,如果没有的话,将会拒绝连接。该策略从 2018年末开始。

  1. 所有公开的受信任的 CA 机构颁发的新的 TLS 证书必须能够被 Certificate Transparency 验证

  2. 现有的已经颁发的证书不受影响

  3. 客户端不受影响

Bonjour

Bonjour,原名 Rendezvous,是苹果电脑公司在其开发的操作系统 Mac OS X10.2版本之后引入的服务器搜索协议所使用的一个商标名。适用于 LAN,Bonjour使用多点传送域名系统服务记录来定位各种设备,比如打印机或者其他电脑,以及另外设备上的服务。Bonjour一致性测试是一种工具,可以帮助验证硬件设备是否正确实施了Bonjour,从而提高产品质量,使其更可靠,让客户满意并不会将产品退回商店

  • 如果要在包装上使用Bonjour商标名称和徽标,则需要运行此测试

  • 如果要将Bonjour for Windows安装程序与 Windows 应用程序捆绑在一起,则需要运行此测试

  • 如果想在包装上使用 AirPrint,AirPlay,CarPlay, HomeKit 徽标,通过Bonjour一致性测试是徽标许可流程的一部分,因为可靠的Bonjour是这些产品的重要组成部分

不要直接用BSD Socket,而用network.framework

URLSession是苹果官方维护的网络库,比起直接用 BSD Socket 的 api 会更可靠

network.framework将会暴露URLSession中使用的底层api,这样你可以构建自己的URLSession

尽量从基于 BSD Socket 或 CFSocketStream 或 SecureTransport 的第三方库移植到 network.framework

基于URLSession的优化方案

如果 App 中不使用 HTTP 而是自定义网络协议,可以使用URLSessionStreamTask,基于 TCP 来构建任意协议

减少延迟

  1. http2.0 的连接合并和多路复用,解决了 http 头阻塞的问题(HTTP head-of-line blocking)

  2. http2.0 可以针对不同的子域名共用同一份 https 证书

  3. 尽量复用URLSession对象

最大化吞吐量

  1. 减少 cookie 的体积,并且不要传输已经不再使用的 cookie

  2. URLSession支持 Gzip 压缩算法和 Brotli 压缩算法,在 iOS11 以上可以使用 Brotli 获得更好的压缩比,目前在AFNetworking完全由外部自定义 HttpHeader,因此可以自行开启 Brotli 的支持,而Alamofire会在 iOS11 以上版本默认开启 Brotli 的支持。

提高响应处理能力

  1. 注意URLSession也是 QoS 调度的,responsiveData的优先级略高于 Default ,参考WWDC2016 Networking for the Modern Internet[15]

  2. 建议使用waitsForConnectivity并实现以下回调方法

    @available(iOS 11.0, *)
    public func urlSession(_ session: URLSession, taskIsWaitingForConnectivity task: URLSessionTask){
        // Present fallback UI or error message
        
        // task.cancel() // 如果有需要的话就cancel掉
    }
    ```
    在这个回调方法可以随时调用```task.cancel()```来取消网络请求
* 更好地利用系统资源
    1. 如果涉及大文件的上传和下载,最好使用后台任务,这些任务使用系统 智能来决定何时 开始以及何时停止 基于 电池,CPU, Wi-Fi 等各种因素的下载,这样即使 app 处于后台暂停状态,任务也会继续。参考[WWDC2014 What's New in Foundation Networking](https://developer.apple.com/videos/wwdc/2014/?id=707 "WWDC2014 What's New in Foundation Networking")
    2. 合理利用缓存,因为缓存的存取动作也会引起 I/O 消耗,所以尽量不要缓存一次性的资源。可以利用以下回调来决定要不要进行本地缓存
    ```swift
    // Implement delegate method to decide when to cache content
    public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, willCacheResponse proposedResponse: CachedURLResponse, completionHandler: @escaping (CachedURLResponse?) -> Void) { 
        // If you don't want to cache
        completionHandler(nil
    }

同时服务端也可以通过Cache-Control: no-store来控制客户端缓存的行为

推荐阅读

✨ 探索现代的移动网络

✨ 阿里XQUIC:标准QUIC实现自研之路

关注我们

我们是「老司机技术周报」,每周会发布一份关于 iOS 的周报,也会定期分享一些和 iOS 相关的技术。欢迎关注。

这篇文章的内容来自于《WWDC18 内参》。关注【老司机技术周报】,回复「2020」, 即可领取。

参考资料

[1]

Network Link Conditioner: https://nshipster.com/network-link-conditioner/

[2]

《Wireshark网络分析就这么简单》: https://book.douban.com/subject/26268767/

[3]

《wireshark网络分析的艺术》: https://book.douban.com/subject/26710788/

[4]

使用tcptrace输出网络性能: http://www.voidcn.com/article/p-oftbkdlu-wq.html

[5]

这个网站: http://www.tcptrace.org/useful.shtml

[6]

WWDC2015:Your App and Next Generation Networks: https://developer.apple.com/videos/play/wwdc2015/719/

[7]

这个网站: https://test-ipv6.com/index.html.zh_CN

[8]

拥塞控制机制(ECN, QC-QCN): https://cloud.tencent.com/developer/article/1078916

[9]

WWDC2015:Your App and Next Generation Networks: https://developer.apple.com/videos/play/wwdc2015/719/

[10]

WWDC2017 Advances in Networking, Part 1: https://developer.apple.com/videos/wwdc/2017/?id=707

[11]

这篇文章: https://zhuanlan.zhihu.com/p/32553477

[12]

Cronet网络库: https://cs.chromium.org/chromium/src/components/cronet/ios/docs/BUILD.md

[13]

WWDC2018 Introducing Network.framework: A modern alternative to Sockets: https://developer.apple.com/videos/wwdc/2018/?id=715

[14]

WWDC2017 Advances in Networking, Part 1: https://developer.apple.com/videos/wwdc/2017/?id=707

[15]

WWDC2016 Networking for the Modern Internet: https://developer.apple.com/videos/wwdc/2016/?id=714


本文分享自微信公众号 - 老司机技术周报(LSJCoding)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

展开阅读全文
打赏
3
12 收藏
分享
加载中
更多评论
打赏
0 评论
12 收藏
3
分享
OSCHINA
登录后可查看更多优质内容
返回顶部
顶部