Highlight
iOS 15 和 macOS Monterey 默认启用 HTTP/3,URLSession 无需修改代码即可自动升级;同时 Network.framework 新增 QUIC 传输层支持,允许开发者用 NWConnectionGroup 构建自定义多路复用协议。
核心内容
HTTP 协议的演进:从 HTTP/1 到 HTTP/3
每次请求资源都要经历建立连接、发送请求、等待处理、接收响应四个步骤。如果同时请求多个资源,HTTP/1 需要为每个资源单独建立连接,大量时间浪费在连接建立上。
HTTP/1 的改进方案是复用单个连接,但请求必须串行发送,前一个响应结束后才能发下一个,这就是队头阻塞(head-of-line blocking)。浏览器通过开多个并行连接来缓解,但这给客户端和服务器都带来了低效的网络行为。
HTTP/2 在单个 TCP 连接上多路复用多个流,请求可以更早发出,不同流的数据可以交错传输。空闲等待时间大幅减少。
HTTP/3 在此基础上进一步改进:连接建立更快,请求能更早发出。更重要的是,HTTP/3 的流相互独立——HTTP/2 所有流共享一个 TCP 连接,网络丢包可能影响所有流;HTTP/3 中丢包只影响对应的流,其他流的数据可以正常送达。
QUIC:HTTP/3 的底层引擎
QUIC 是 IETF 标准化的可靠传输协议,基于 TCP 的相同概念,但提供端到端加密、多路复用流和身份验证。安全性建立在 TLS 1.3 之上。
QUIC 的核心优势:
- 更快的握手:QUIC 依赖 TLS 1.3 完成安全握手,不需要 TCP 的三次握手,将握手时间减少到单次往返
- 无队头阻塞:多路复用流是 QUIC 的核心概念,不会像 TCP 那样被阻塞
- 更好的丢包恢复:QUIC 端点能向对端传递更复杂的收包信息,不受 TCP 限制,连接丢包恢复能力更强
- 连接迁移:允许连接在不同网络接口间无缝切换(如蜂窝网络到 Wi-Fi),无需重新建立会话
URLSession 中的 HTTP/3
iOS 15 和 macOS Monterey 默认启用 HTTP/3。使用 URLSession 的应用无需修改代码,服务器开启 HTTP/3 后即可自动使用。
支持的版本包括即将发布的 HTTP/3 RFC 和早期的 Draft 29。
如何确认应用在使用 HTTP/3?
Xcode 13 的 Instruments 新增了 HTTP Traffic 检查工具,直接接入 URLSession,无需额外配置。可以查看每个请求的 HTTP 版本和响应头。
服务器通过 Alt-Svc 响应头宣告 HTTP/3 支持:
Alt-Svc: h3=":443"; ma=2592000
URLSession 会记住这个信息用于后续连接,称为”服务发现”。更推荐的方式是在 DNS 中配置 HTTPS 资源记录,用 h3 字符串宣告 HTTP/3,这样首次连接就能建立 HTTP/3。
如果确定服务器支持 HTTP/3 且想加速发现过程,可以设置 assumesHTTP3Capable 属性。这允许 HTTP 栈假设服务器支持 HTTP/3,但不保证一定使用——网络可能屏蔽 HTTP/3,或服务器实际不支持,此时会自动回退到 HTTP/2。
HTTP/3 的优先级模型
HTTP/2 的优先级方案因复杂而很少被服务器尊重,HTTP/3 移除了旧模型,改用基于 HTTP 头的更简单方案。优先级用 urgency 参数(0-7)和可选的 incremental 参数指定。
URLSession 的 API 保持不变,仍用 priority 属性设置优先级,用 prefersIncrementalDelivery 启用增量投递。默认优先级是 3。URLSession 会根据是否使用便捷 API(如 async data 方法)推断增量投递。
动态调整资源优先级也受支持。例如可以先用低优先级预加载照片,用户滑动到对应区域时再提升优先级。
Network.framework 中的 QUIC 支持
HTTP/3 基于 QUIC,但 QUIC 的能力不只限于 HTTP。如果应用交换非请求/响应型数据、需要多路复用流共享底层传输上下文、或实现自定义协议(如 P2P 通信、RPC 调用),可以直接使用 QUIC 传输。
iOS 15 和 macOS Monterey 中,NWProtocolQUIC 加入 Network.framework。创建 QUIC 连接的方式与其他协议类似:
let connection = NWConnection(
host: "example.com",
port: 443,
using: .quic(alpn: ["myproto"])
)
connection.stateUpdateHandler = { newState in
switch newState {
case .ready:
print("Connected using QUIC!")
default:
break
}
}
connection.start(queue: queue)
建立连接后,发送和接收数据的方式与其他 NWConnection 相同。
NWConnectionGroup 管理多路复用流
QUIC 隧道(tunnel)在底层传输上下文上多路复用多个单向或双向 QUIC 流。流可以由任一端点创建,能并发发送数据并与其他流交错,状态与传统 TCP 流类似。
NWMultiplexGroup 是管理这类连接的新类型:
let descriptor = NWMultiplexGroup(to: .hostPort(host: "example.com", port: 443))
let group = NWConnectionGroup(with: descriptor, using: .quic(alpn: ["myproto"]))
group.stateUpdateHandler = { newState in
switch newState {
case .ready:
print("Group ready!")
default:
break
}
}
group.start(queue: queue)
创建新的出站流:
let connection = NWConnection(from: group)
接收远程端点发起的入站流:
group.newConnectionHandler = { newConnection in
newConnection.stateUpdateHandler = { newState in
// Handle stream states
}
newConnection.start(queue: queue)
}
服务端接收 QUIC 隧道
使用 NWListener 运行服务端时,可以通过 newConnectionGroupHandler 接收新的 QUIC 隧道:
listener.newConnectionGroupHandler = { group in
group.stateUpdateHandler = { newState in
// Handle tunnel states
}
group.newConnectionHandler = { stream in
// Set up and start new incoming streams
}
group.start(queue: queue)
}
访问 QUIC 元数据
通过 NWProtocolQUIC.Metadata 可以获取和修改流信息:
if let metadata = connection.metadata(definition: NWProtocolQUIC.definition)
as? NWProtocolQUIC.Metadata {
print("QUIC Stream ID is \(metadata.streamIdentifier)")
// Set application error before cancelling
metadata.applicationError = 0x100
connection.cancel()
}
调试 QUIC 连接
调试时设置环境变量输出 qlog 文件。qlog 是 IETF 提出的标准化日志格式,比传统抓包包含更丰富的 QUIC 连接信息。测试完成后,在 Xcode 的 Devices 窗口下载应用容器获取 qlog 文件,再用开源可视化工具分析。
详细内容
创建 QUIC 连接
(13:20)
let connection = NWConnection(
host: "example.com",
port: 443,
using: .quic(alpn: ["myproto"])
)
connection.stateUpdateHandler = { newState in
switch newState {
case .ready:
print("Connected using QUIC!")
default:
break
}
}
connection.start(queue: queue)
关键点:
.quic(alpn:)参数指定 ALPN(应用层协议协商)字符串stateUpdateHandler监听连接状态变化,.ready表示连接已建立start(queue:)在指定队列上启动连接
建立 QUIC 隧道
(15:08)
let descriptor = NWMultiplexGroup(to: .hostPort(host: "example.com", port: 443))
let group = NWConnectionGroup(with: descriptor, using: .quic(alpn: ["myproto"]))
group.stateUpdateHandler = { newState in
switch newState {
case .ready:
print("Connected using QUIC!")
default:
break
}
}
group.start(queue: queue)
关键点:
NWMultiplexGroup是 QUIC 隧道的描述符NWConnectionGroup管理共享底层 QUIC 隧道的多个流- 状态处理器跟踪的是隧道状态,而非单个流
管理流
(15:45)
// Create new outgoing stream
let connection = NWConnection(from: group)
// Receive incoming streams
group.newConnectionHandler = { newConnection in
newConnection.stateUpdateHandler = { newState in
// Handle stream states
}
newConnection.start(queue: queue)
}
关键点:
NWConnection(from: group)从组中创建新的出站流newConnectionHandler处理远程端点发起的入站流- 每个流独立管理自己的状态
访问 QUIC 元数据
(17:22)
if let metadata = connection.metadata(definition: NWProtocolQUIC.definition)
as? NWProtocolQUIC.Metadata {
print("QUIC Stream ID is \(metadata.streamIdentifier)")
// Set application error before cancelling
metadata.applicationError = 0x100
connection.cancel()
}
关键点:
streamIdentifier获取 QUIC 流 IDapplicationError用于在关闭流前向对端报告应用层错误- 元数据在连接取消前修改才有效
核心启发
-
为服务端开启 HTTP/3
- 做什么:在 CDN 或自建服务器上启用 HTTP/3,让 iOS 15+ 用户自动获得更快的网络体验
- 为什么值得做:URLSession 已默认启用,用户无需更新应用即可享受 0-RTT 握手、无队头阻塞、连接迁移等优势
- 怎么开始:CDN 用户检查服务商的 HTTP/3 开关;自建服务器用 nginx 1.25+ 或 Cloudflare quiche。配置 DNS HTTPS 记录和
Alt-Svc响应头
-
用 Instruments 验证 HTTP/3 生效
- 做什么:用 Xcode 13 的 HTTP Traffic Instrument 确认应用实际在使用 HTTP/3
- 为什么值得做:HTTP/3 依赖服务发现,首次连接可能仍走 HTTP/2。需要验证配置是否正确
- 怎么开始:选择 Network profiling template,记录应用运行,检查 HTTP Version 列和
Alt-Svc响应头
-
为实时通信应用接入 QUIC
- 做什么:用 Network.framework 的 QUIC 支持构建自定义协议,替代基于 TCP 的私有协议
- 为什么值得做:QUIC 提供内置 TLS 1.3、多路复用、连接迁移,比自研基于 TCP 的协议更可靠
- 怎么开始:用
NWConnectionGroup+NWMultiplexGroup管理 QUIC 隧道和流。设置QUIC.Options配置传输参数
-
实现连接迁移友好的长连接
- 做什么:利用 QUIC 的连接迁移能力,让应用在 Wi-Fi 和蜂窝网络切换时保持连接
- 为什么值得做:传统 TCP 连接在网络切换时会断开,需要重新握手。QUIC 的连接 ID 机制允许无缝迁移
- 怎么开始:用
NWConnection+.quic()替代现有的 TCP 连接。无需额外代码,Network.framework 自动处理迁移
关联 Session
- Use async/await with URLSession — 在 URLSession 中使用 async/await 简化网络代码
- Reduce networking delays for your app — 减少应用网络延迟的策略
- Meet async/await in Swift — Swift async/await 入门
评论
GitHub Issues · utterances