Highlight
Apple 在 iOS 15 和 macOS Monterey 提供网络响应度测试、HTTP/3、TCP Fast Open、Multipath TCP 和后台服务类型,让开发者减少网络往返次数并降低用户等待时间。
核心内容
一个天气应用打开后显示加载指示器。代码很快,接口也不大。用户仍然要等一两秒。
开发者常先看带宽。家庭网络已经从几兆升级到千兆,视频会议和网页加载仍会卡。Stuart Cheshire 在开场给出原因:空闲 ping 时间不代表真实使用体验。网络被使用时,缓冲区会排队,20ms 的空闲往返延迟可能变成 600ms。
这类问题叫缓冲膨胀(bufferbloat)。包没有更快到达,只是在网络设备的大缓冲区里排队。带宽提升不能消除排队时间。
Apple 的做法分两层。
第一层是测量。iOS 15 的开发者设置加入 Responsiveness 测试,macOS 提供 networkQuality 命令。它们测的是工作状态下的响应度,用 RPM(Round-trips Per Minute,每分钟往返次数)表示。数字越高,网络越能快速完成往返。
第二层是减少往返。应用不能改变光速,也不能控制用户家里的路由器。但应用可以减少建立连接和拿到首字节所需的网络往返次数。Session 重点讲了 HTTP/3 与 QUIC、TCP Fast Open、TLS 1.3、Multipath TCP,以及如何把不紧急的流量标记为后台。
从等待首字节开始优化
如果应用当前使用 TCP + TLS 1.2,用户拿到首字节可能需要 4 次网络往返。往返延迟升到 600ms 时,首字节等待接近 2.4 秒。
HTTP/3、QUIC、TLS 1.3 和 TCP Fast Open 的价值在这里:它们减少握手阶段的往返。对于已经使用 URLSession 的应用,iOS 15 和 macOS Monterey 默认启用 HTTP/3。服务器支持后,客户端可以自动受益。
网络切换也会产生等待
用户从 Wi-Fi 走到户外,设备切到蜂窝网络。普通 TCP 连接需要重新建立。Multipath TCP(多路径 TCP)允许一个 TCP 连接在网络切换时继续存在,并让系统自动选择更快路径。
这对聊天、视频会议、远程控制这类交互应用很直接:用户移动时,连接不需要从头握手。
后台下载不该堵住前台请求
很多应用会预取图片、音频或文件。预取本身合理,但如果它把网络队列塞满,用户点击个人主页时,真正需要立即显示的数据会排在队尾。
iOS 15 和 macOS Monterey 改进了后台服务类型的拥塞控制。开发者把预取、批量同步和非紧急任务标记为后台后,系统可以让前台流量先通过,同时让后台传输接近原来的完成时间。
详细内容
测量工作状态下的网络响应度
(00:36)iOS 15 的开发者设置中,Networking 区域新增 Responsiveness 测试。它会生成网络流量,测量工作状态下的响应度。macOS 上对应的命令行工具叫 networkQuality。
networkQuality
关键点:
networkQuality在 macOS 上运行网络响应度测试。- 测试关注工作状态下的往返能力,区别于空闲 ping。
- 结果用 RPM 表示,RPM 越高,网络响应越快。
- 这个指标适合用来复现用户抱怨“网速很快但应用很慢”的场景。
(02:13)Session 给出一个具体数字:空闲往返时间可能是 20ms,工作状态下可能升到 600ms 或更高。应用等待时间由两项决定:一次往返要多久,以及应用需要多少次往返。
wait time = round-trip time × number of round trips
关键点:
round-trip time是单次网络往返时间。number of round trips是应用协议完成一次操作需要的往返次数。- 开发者很难控制用户网络的基础 RTT。
- 开发者可以通过协议和 API 选择减少往返次数。
HTTP/3 和 QUIC:URLSession 默认受益
(08:15)iOS 15 和 macOS Monterey 默认启用 HTTP/3 和 QUIC。QUIC 是传输协议,建立连接比 TCP + TLS 更快,还能减少队头阻塞(head-of-line blocking,同一连接内丢包导致其他数据等待)。
let session = URLSession.shared
let (data, response) = try await session.data(from: url)
关键点:
URLSession.shared使用系统提供的 URLSession 配置。session.data(from:)发起 HTTP 请求。- Session 明确说明:如果应用已经使用 URLSession,就已经准备好使用 HTTP/3。
- 是否走 HTTP/3 还需要服务器端支持。
(08:47)如果应用自己实现应用层协议,可以使用 Network framework(网络框架)的 QUIC 支持。演讲说明做法是创建带 QUIC 参数的 NWConnection,并设置 TLS Application-Layer Protocol Negotiation(ALPN,应用层协议协商)。
let connection = NWConnection(
host: "example.com",
port: 443,
using: .quic(alpn: ["myproto"])
)
connection.start(queue: myQueue)
关键点:
NWConnection表示一条 Network framework 连接。host和port指向服务器。.quic(alpn: ["myproto"])使用 QUIC 参数,并通过 ALPN 声明应用协议。start(queue:)启动连接。- 服务器也必须支持对应的 QUIC 协议和 ALPN。
TCP Fast Open:把首次数据放进握手
(09:28)TCP 仍适合一部分应用。使用 TCP 时,TCP Fast Open 可以把应用的首次数据和 TCP 握手一起发送,省掉一个完整往返。
/* Allow fast open on the connection parameters */
parameters.allowFastOpen = true
let connection = NWConnection(to: endpoint, using: parameters)
/* Call send with idempotent initial data before starting the connection */
connection.send(content: initialData, completion: .idempotent)
connection.start(queue: myQueue)
关键点:
parameters.allowFastOpen = true允许这条连接使用 TCP Fast Open。NWConnection(to:using:)用这些参数创建连接。connection.send(content:completion:)在start之前提交首次数据。.idempotent声明这段首次数据是幂等的,可以安全重放。connection.start(queue:)启动连接,系统可以把首次数据随握手发出。
(10:14)幂等(idempotent)是关键限制。HTTP GET 请求通常可以重放;购买手机这类会产生交易的请求不能随握手发送。网络可能重传首次数据,非幂等操作会导致重复执行。
GET / HTTP/1.1
Host: developer.apple.com
关键点:
GET请求获取资源。Host指定目标主机。- 演讲用访问
developer.apple.com的 GET 说明幂等请求。 - 如果确认请求重放不会产生额外效果,才适合放进 TCP Fast Open 的首次数据。
(11:01)如果应用基于 Sockets,可以调用 connectx 并使用对应标志。
connectx(fd, ..., CONNECT_DATA_IDEMPOTENT | CONNECT_RESUME_ON_READ_WRITE, ...); // delay SYN
write(fd, ...); // SYN goes out with first data segment
关键点:
connectx发起 socket 连接。CONNECT_DATA_IDEMPOTENT声明首次数据可以安全重放。CONNECT_RESUME_ON_READ_WRITE让连接在读写时继续。write写入首次数据。- 注释说明 SYN 会和第一个数据段一起发出。
TLS 1.3:默认减少一次握手往返
(12:46)TLS 1.3 相比 TLS 1.2 移除一个完整握手往返,并提供更强的安全性。Session 说明它从 iOS 13.4 起已经默认用于 URLSession 和 NWConnection。
let session = URLSession.shared
let connection = NWConnection(to: endpoint, using: .tls)
关键点:
URLSession.shared使用系统 TLS 栈。NWConnection(to:using: .tls)创建 TLS 连接。- TLS 1.3 默认启用,无需给 URLSession 或 NWConnection 额外开关。
- TLS 1.3 定义 early data,可以把幂等请求随 TLS 握手发送,再省一次往返。
Multipath TCP:网络切换时保留连接
(13:35)Multipath TCP 允许一个 TCP 连接在设备从一个网络切到另一个网络时继续存在。低延迟场景使用 interactive 模式,系统会自动为数据包选择更快路径。
// Multipath TCP
// Save multiple round-trips when switching networks
// On URLSessionConfiguration
let configuration = URLSessionConfiguration.default
configuration.multipathServiceType = .interactive
// On NWParameters
let parameters = NWParameters.tcp
parameters.multipathServiceType = .interactive
关键点:
URLSessionConfiguration.default创建默认 URLSession 配置。configuration.multipathServiceType = .interactive为 URLSession 开启交互型 Multipath TCP。NWParameters.tcp创建 TCP 参数。parameters.multipathServiceType = .interactive为 NWConnection 开启交互型 Multipath TCP。.interactive适合低延迟流量,系统会处理路径选择。
把非紧急流量标记为后台
(17:43)应用经常同时有两类流量:用户正在等待的前台请求,以及预取、批量同步、上传日志这类非紧急请求。如果非紧急请求填满瓶颈队列,用户点击后发出的请求会排在后面。
(20:09)前台应用执行非用户发起的传输时,可以继续使用默认 URLSession,但在 URLRequest 上设置后台服务类型。Network framework 对应设置是 NWParameters.serviceClass。
//Use default URLSession, set background on URLRequest
var request = URLRequest(url: myurl)
request.networkServiceType = .background
//Set service class on parameters to apply to the NWConnection
let parameters = NWParameters.tls
parameters.serviceClass = .background
关键点:
URLRequest(url:)创建一次请求。request.networkServiceType = .background声明这次传输是后台性质。NWParameters.tls创建 TLS 连接参数。parameters.serviceClass = .background把后台服务类别应用到 NWConnection。- 系统可以据此保持网络队列较短,让前台数据更快送达。
(20:37)如果应用启动长时间传输,并且希望应用挂起后继续执行,应使用后台 URLSession。对时间不敏感的任务,可以把 isDiscretionary 设为 true,让系统等待更合适的条件。
//Configure background URL Session
lazy var urlSession: URLSession = {
let configuration = URLSessionConfiguration.background(withIdentifier: "MySession")
configuration.isDiscretionary = true
return URLSession(configuration: configuration, delegate: self, delegateQueue: nil)
}()
关键点:
URLSessionConfiguration.background(withIdentifier:)创建后台会话配置。"MySession"是后台会话标识符。configuration.isDiscretionary = true允许系统选择合适时机执行传输。URLSession(configuration:delegate:delegateQueue:)用该配置创建会话。- 这个模式适合时间不敏感的下载、上传和同步任务。
服务器端也要减少发送端排队
(21:17)延迟还可能出现在发送设备本身。历史上的网络栈会使用很大的发送缓冲区,包在进入网络前就可能排队几秒。Apple 已经在 2015 年为 URLSession 和 NWConnection 修复了这个问题。
setsockopt(fd, IPPROTO_TCP, TCP_NOTSENT_LOWAT, &value, sizeof(value));
关键点:
setsockopt设置 socket 选项。IPPROTO_TCP表示设置 TCP 级别选项。TCP_NOTSENT_LOWAT是演讲提到的 TCP not sent low watermark socket option。- 该选项用于减少发送端尚未进入网络的数据排队。
- Session 建议联系服务器运维人员确认服务器使用该选项。
核心启发
-
做什么:给 App 加一个“网络体验诊断”页面,提示用户在卡顿时运行系统网络响应度测试。 为什么值得做:Session 明确区分空闲 ping 和工作状态下响应度,RPM 更接近真实体验。 怎么开始:在帮助页说明 iOS Developer Settings 的 Responsiveness 测试和 macOS
networkQuality命令,把用户反馈从“网速慢”变成可记录的 RPM 数据。 -
做什么:为首屏接口启用 TCP Fast Open。 为什么值得做:首屏接口通常决定用户看到加载指示器的时间,Fast Open 能把幂等首次请求放进握手。 怎么开始:在 Network framework 连接参数上设置
parameters.allowFastOpen = true,并在connection.start(queue:)前用.idempotent发送只读请求数据。 -
做什么:让聊天、通话或协作连接支持网络切换。 为什么值得做:用户在 Wi-Fi 和蜂窝之间移动时,重新建连会引入多次往返。Multipath TCP 可以保留连接。 怎么开始:对 URLSession 设置
configuration.multipathServiceType = .interactive,或对 Network framework 设置parameters.multipathServiceType = .interactive。 -
做什么:把图片预取、离线包下载、日志上传标记为后台流量。 为什么值得做:非紧急流量填满队列后,会拖慢用户刚刚触发的前台请求。后台服务类型让系统控制队列长度。 怎么开始:对单次
URLRequest设置request.networkServiceType = .background;对时间不敏感的长任务使用URLSessionConfiguration.background并设置isDiscretionary = true。 -
做什么:建立一组网络延迟回归测试。 为什么值得做:开发机上的 5G、LTE 或快速 Wi-Fi 可能掩盖问题,Session 建议用 Network Link Conditioner 复现真实网络条件。 怎么开始:在 iOS Developer Settings 打开 Network Link Conditioner,在 macOS 从 Apple Developer 网站下载工具,用高 RTT、丢包、拥塞配置测试首屏、登录、搜索和上传路径。
关联 Session
- Accelerate networking with HTTP/3 and QUIC — 深入讲 HTTP/3 默认启用、QUIC 传输和 Network.framework 的 QUIC API。
- Use async/await with URLSession — 展示如何用 async/await 编写 URLSession 请求、上传、下载和取消逻辑。
- Get ready for iCloud Private Relay — 解释网络应用、服务器和网络如何适配更私密的互联网连接。
- Ultimate application performance survival guide — 从指标、工具和流程角度系统排查应用性能问题。
评论
GitHub Issues · utterances