WWDC Quick Look 💓 By SwiftGGTeam
Accelerate networking with HTTP/3 and QUIC

Accelerate networking with HTTP/3 and QUIC

观看原视频

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 流 ID
  • applicationError 用于在关闭流前向对端报告应用层错误
  • 元数据在连接取消前修改才有效

核心启发

  1. 为服务端开启 HTTP/3

    • 做什么:在 CDN 或自建服务器上启用 HTTP/3,让 iOS 15+ 用户自动获得更快的网络体验
    • 为什么值得做:URLSession 已默认启用,用户无需更新应用即可享受 0-RTT 握手、无队头阻塞、连接迁移等优势
    • 怎么开始:CDN 用户检查服务商的 HTTP/3 开关;自建服务器用 nginx 1.25+ 或 Cloudflare quiche。配置 DNS HTTPS 记录和 Alt-Svc 响应头
  2. 用 Instruments 验证 HTTP/3 生效

    • 做什么:用 Xcode 13 的 HTTP Traffic Instrument 确认应用实际在使用 HTTP/3
    • 为什么值得做:HTTP/3 依赖服务发现,首次连接可能仍走 HTTP/2。需要验证配置是否正确
    • 怎么开始:选择 Network profiling template,记录应用运行,检查 HTTP Version 列和 Alt-Svc 响应头
  3. 为实时通信应用接入 QUIC

    • 做什么:用 Network.framework 的 QUIC 支持构建自定义协议,替代基于 TCP 的私有协议
    • 为什么值得做:QUIC 提供内置 TLS 1.3、多路复用、连接迁移,比自研基于 TCP 的协议更可靠
    • 怎么开始:用 NWConnectionGroup + NWMultiplexGroup 管理 QUIC 隧道和流。设置 QUIC.Options 配置传输参数
  4. 实现连接迁移友好的长连接

    • 做什么:利用 QUIC 的连接迁移能力,让应用在 Wi-Fi 和蜂窝网络切换时保持连接
    • 为什么值得做:传统 TCP 连接在网络切换时会断开,需要重新握手。QUIC 的连接 ID 机制允许无缝迁移
    • 怎么开始:用 NWConnection + .quic() 替代现有的 TCP 连接。无需额外代码,Network.framework 自动处理迁移

关联 Session

评论

GitHub Issues · utterances