2. QUIC核心特性:0-RTT握手、连接迁移、多路复用、前向纠错
QUIC 之所以能成为 HTTP/3 的基石,说白了就是它把 TCP 那些老毛病全给治了。我刚开始研究 QUIC 时,第一反应是:这玩意儿怎么敢不用 TCP?后来抓了几个包才明白——它不是在跟 TCP 硬刚,而是直接换了个赛道。
这一章咱们就拆开 QUIC 的四个核心特性,看看它们到底解决了什么问题。每个特性我都会结合抓包经验来讲,保证你听完就能上手分析。
2.1 0-RTT 握手:连接秒开的秘密
先问个问题:你打开一个网页,从点击到页面出现,中间到底经历了多少次握手?
TCP 三次握手 + TLS 1.3 一次往返,最少也要 2 个 RTT。如果网络延迟 100ms,光握手就 200ms 没了。用户等得黄花菜都凉了。
QUIC 的 0-RTT 是怎么做到的?我直接说结论:它把加密握手和传输握手合并了。
- 首次连接(1-RTT):客户端发一个 Initial 包,里面同时带了传输参数和加密密钥交换材料。服务端回复 Handshake 包,双方一次往返就建立好安全连接。
- 后续连接(0-RTT):客户端缓存了服务端的配置参数,下次直接发 0-RTT 包,里面已经带了加密的应用数据。服务端收到就能直接解密处理。
quic 协议,看到 Initial 包后面紧跟着 0-RTT 包,就说明客户端在尝试 0-RTT 握手。如果服务端拒绝,会回复 Retry 包或 Version Negotiation 包。
我记得有一次排查一个视频 App 的启动慢问题。抓包发现每次冷启动都是 1-RTT,但热启动时客户端明明有缓存,却还是走了 1-RTT。后来一查,是服务端返回的 max_idle_timeout 太短,客户端缓存过期了。调大这个值,热启动直接降到 0-RTT,首帧加载快了 150ms。
2.2 连接迁移:换个网络不断连
你想想看,用手机刷视频时从 WiFi 切到 4G,TCP 连接会怎样?
断了。因为 TCP 用「IP + 端口」标识连接,IP 一变,连接就没了。应用层得重新建连,视频得重新缓冲,体验极差。
QUIC 怎么解决的?它用 Connection ID 来标识连接,而不是 IP 和端口。
| 特性 | TCP | QUIC |
|---|---|---|
| 连接标识 | 源 IP + 源端口 + 目标 IP + 目标端口 | Connection ID(64 位随机数) |
| 网络切换 | 必须重建连接 | 发送新路径探测包,迁移连接 |
| 切换延迟 | 至少 1 RTT(TCP 握手) | 0 RTT(数据包直接发) |
| 安全性 | 无 | 路径验证 + 地址校验 |
具体流程是这样的:
- 客户端检测到网络变化(比如 WiFi 断开),立即用新 IP 发送一个
PATH_CHALLENGE帧。 - 服务端收到后,回复
PATH_RESPONSE帧,确认新路径可用。 - 双方开始用新 IP 通信,旧路径进入「等待关闭」状态。
我在做移动端直播推流优化时,就靠这个特性解决了「主播从室内走到室外,WiFi 切 4G 导致断流」的问题。QUIC 连接迁移后,推流中断时间从原来的 2-3 秒降到了 200ms 以内,观众几乎感觉不到卡顿。
QUIC PATH_CHALLENGE 和 QUIC PATH_RESPONSE 帧,就说明发生了连接迁移。注意看包的源 IP 和目的 IP 是否发生了变化。
2.3 多路复用:彻底干掉队头阻塞
HTTP/2 的多路复用有个致命伤——TCP 队头阻塞。
什么意思呢?HTTP/2 把多个请求放在一个 TCP 连接里,但如果其中一个包丢了,TCP 会阻塞后续所有请求,直到丢包重传完成。你想想看,一个页面里 100 个资源,就因为一张图片的包丢了,其他 99 个都得等着。
QUIC 的多路复用是在 Stream 级别 做的。每个 Stream 独立收发数据,一个 Stream 丢包只影响它自己,其他 Stream 照常传输。
// QUIC Stream 帧结构(简化)
Stream Frame {
Stream ID: 可变长度整数 // 标识属于哪个流
Offset: 可变长度整数 // 数据在流中的偏移量
Length: 可变长度整数 // 数据长度
Data: ... // 实际数据
}
每个 Stream 都有自己的滑动窗口和重传机制。我抓包时经常看到这样的场景:Stream 5 在重传丢失的包,而 Stream 7 和 Stream 9 的数据包已经顺利到达了。这在 TCP 里根本不可能。
2.4 前向纠错:丢包不用等重传
前向纠错(FEC)这个特性,说实话,QUIC 规范里并没有强制要求实现。但很多 QUIC 实现(比如 Google 的版本)都加了它。
原理很简单:发送方在发送数据包时,额外生成一些 冗余包。接收方如果丢了某个数据包,可以用冗余包和其他收到的包一起,通过异或运算把丢失的数据恢复出来。
举个例子:
- 发送方要发 3 个数据包:A、B、C
- 生成一个冗余包:R = A ⊕ B ⊕ C(异或运算)
- 接收方收到 A、B、R,丢了 C
- 接收方计算:A ⊕ B ⊕ R = A ⊕ B ⊕ (A ⊕ B ⊕ C) = C
- 数据恢复成功,不需要重传!
我个人的经验是,FEC 在 高丢包率网络(比如无线网络、卫星链路)下效果特别明显。但要注意,冗余包会增加带宽开销。一般建议丢包率超过 5% 时才开启 FEC,否则反而浪费带宽。
好了,这四个特性讲完了。你可能会问:它们之间有没有什么关联?
当然有。0-RTT 让连接建立更快,连接迁移让连接更稳定,多路复用让传输更高效,前向纠错让丢包恢复更快。这四个特性加在一起,才让 QUIC 在弱网环境下比 TCP 强出一个量级。