4. QUIC 的可靠传输机制:Packet Number 与 Nonce、ACK 帧与 ACK Range、丢失检测与重传策略、RTT 与 RTO 计算

聊到 QUIC 的可靠传输,很多人第一反应就是「这不就是 TCP 那套东西换个皮吗?」

说实话,我刚接触 QUIC 时也这么想。但真正抓包分析过几次后,我发现 QUIC 的可靠机制和 TCP 有本质区别。它不是在 TCP 基础上修修补补,而是重新设计了一套更适应现代网络环境的方案。

这一章,我就带你把这套机制拆开来看。

4.1 Packet Number 与 Nonce:告别 TCP 的歧义

TCP 的序列号有个老毛病——重传歧义。你发一个包丢了,重传时用的是同样的序列号。收到 ACK 后,你根本分不清这个 ACK 对应的是原始包还是重传包。这会导致 RTT 计算不准,进而影响 RTO。

QUIC 怎么解决的?每个包都有一个唯一的 Packet Number(PN),单调递增,绝不重复。

关键区别:

  • TCP:序列号可重复(重传时相同)
  • QUIC:Packet Number 永远递增,重传包使用新的 PN

举个例子。我发 PN=10 的包,丢了。重传时,我不会再用 PN=10,而是用 PN=11。收到 ACK 后,如果 ACK 确认的是 PN=10,那就是原始包到了;如果确认的是 PN=11,那就是重传包到了。清清楚楚,没有歧义。

那 Nonce 是干嘛的?

QUIC 早期版本(Google QUIC,gQUIC)在 PN 之外还带了一个 Nonce 字段,用于加密上下文。到了标准化版本(RFC 9000),Nonce 被移除了,因为 PN 本身已经足够唯一,加密上下文可以通过其他方式推导。

我个人习惯在抓包时关注 PN 的增长模式。如果看到 PN 跳跃很大,或者出现大量连续的 PN 重传,基本可以判断网络有丢包。

抓包小技巧:在 Wireshark 中过滤 quic.packet_number,可以快速定位特定 PN 的包。配合 quic.frames.type == 0x02 过滤 ACK 帧,能清晰看到哪些 PN 被确认了。

4.2 ACK 帧与 ACK Range:高效的确认方式

TCP 的 SACK 选项虽然解决了部分确认问题,但它的格式比较笨重。QUIC 的 ACK 帧设计得更紧凑、更灵活。

一个 ACK 帧包含:

  • Largest Acknowledged:已确认的最大 PN
  • ACK Range:一系列 [开始, 结束] 区间,表示连续确认的 PN 范围
  • ACK Delay:接收方从收到包到发送 ACK 的延迟

举个例子,假设接收方收到了 PN=1,2,3,5,6,8,但没收到 4 和 7。那么 ACK 帧会这样描述:

字段 值 说明
Largest Acknowledged 8 最大已确认 PN
First ACK Range 0 从 8 往前 0 个包,即确认 PN=8
Gap=1, Range=1 5-6 跳过 PN=7,确认 PN=5,6
Gap=0, Range=2 1-3 跳过 PN=4,确认 PN=1,2,3

你看,用很少的字节就描述了复杂的接收情况。这在 TCP 里需要多个 SACK 块才能做到。

我曾经在分析一个视频流卡顿问题时,发现客户端频繁发送 ACK 帧,但每个 ACK 的 Range 都很短。这说明接收方收到了大量乱序包,网络抖动很严重。后来定位到是中间路由器的 buffer 太小导致的。

注意:QUIC 的 ACK 帧只确认 Packet Number,不确认数据内容。数据内容的可靠性由 Stream 层保证。PN 的确认只用于丢包检测和拥塞控制。

4.3 丢失检测与重传策略:更精细的判定

TCP 的丢包检测主要靠超时(RTO)和快速重传(3 个重复 ACK)。QUIC 在此基础上做了很多改进。

QUIC 的丢包检测基于两个核心参数:

  • Packet Threshold:包序号阈值,默认 3。如果某个包之后的 3 个包都被确认了,那这个包就判定为丢失。
  • Time Threshold:时间阈值,默认 1.25 × RTT。如果某个包超过这个时间还没被确认,就判定为丢失。

这两个条件只要满足一个,就会触发重传。你想想看,这比 TCP 的「等 3 个重复 ACK」要灵活得多。尤其是在高延迟链路(比如卫星网络)上,时间阈值能更快触发重传。

重传策略方面,QUIC 支持两种模式:

  • 立即重传:检测到丢包后立即发送重传包
  • 基于拥塞控制的重传:等待拥塞窗口允许后再发送

我个人建议在丢包率较高的场景下,优先使用立即重传。我在一个跨国游戏加速项目中就遇到过,默认的拥塞控制重传导致玩家延迟飙升,改成立即重传后体验明显改善。

重传包的特殊标记:QUIC 的重传包在帧头会携带 Packet Number 和 Stream Offset,接收方可以根据这些信息判断这是重传数据,并丢弃已经收到的部分。

4.4 RTT 与 RTO 计算:更精准的估算

RTT 计算是可靠传输的基石。TCP 的 RTT 计算受重传歧义影响,精度有限。QUIC 因为 PN 唯一,可以精确计算每个包的 RTT。

QUIC 的 RTT 计算流程:

  1. 发送方记录每个包的发送时间
  2. 收到 ACK 后,用当前时间减去对应 PN 的发送时间
  3. 减去 ACK Delay(接收方处理延迟),得到真实的 RTT

RTO 的计算则基于 RTT 的统计值:

  • SRTT(平滑 RTT):加权移动平均
  • RTTVAR(RTT 方差):衡量 RTT 的抖动
  • RTO = SRTT + 4 × RTTVAR

嗯,这里要注意。QUIC 的 RTO 计算和 TCP 类似,但因为 RTT 更精确,RTO 的波动也更小。我在一次跨国直播项目中对比过,QUIC 的 RTO 比 TCP 平均低 15% 左右,这意味着更快的丢包恢复。

调优建议:如果网络环境抖动较大(比如移动网络),可以适当增大 RTTVAR 的权重,让 RTO 更保守。反之,在稳定的有线网络中,可以减小权重,让 RTO 更激进。

4.5 知识体系总览

下面这张图总结了 QUIC 可靠传输的核心逻辑,我建议你保存下来,分析问题时对照着看。

QUIC 可靠传输机制总览 发送方 接收方 数据包 (PN=1,2,3...) ACK 帧 (Largest, Range, Delay) Packet Number 分配 丢失检测 重传策略 RTT/RTO 计算 PN 去重与排序 ACK Range 生成 ACK Delay 计算 Stream 数据重组 核心优势 • 唯一 PN 消除重传歧义 • ACK Range 高效确认 • 双阈值丢包检测更灵敏

从这张图可以看出,QUIC 的可靠传输是一个闭环系统。发送方分配 PN、发送数据、检测丢包、计算 RTT;接收方去重排序、生成 ACK Range、计算 ACK Delay。双方配合,才能实现高效可靠的传输。