QUIC协议详解1
本篇:QUIC 系列 ②/④ · QUIC协议详解1 · 系列总览见 QUIC协议系列导读
QUIC 跑在 UDP 上,但逻辑单元是 Packet(包) 与 Frame(帧) :一个 UDP datagram 可含一个或多个 QUIC 包,每个包内可叠多个帧。可靠传输靠 Packet Number(PN) 与 ACK Range 确认,丢包后 帧内容重封装进新 PN,而非 TCP 式原包重发。
速览
- Long Header :Initial / Handshake / 0-RTT;Short Header:1-RTT 数据。
- PN 分 Initial / Handshake / Application 三个空间,单调递增、加密隐藏。
- 丢包靠 ACK Range + 时间阈值 + PTO;无 triple dup-ACK 快重传。
- 流控:MAX_STREAM_DATA (单流)+ MAX_DATA (连接)+ MAX_STREAMS(并发)。
目录
一、报文模型
- [1. UDP → Packet → Frame](#1. UDP → Packet → Frame)
- [2. 包类型与 Long Header](#2. 包类型与 Long Header)
- [3. Short Header 与 PN 隐藏](#3. Short Header 与 PN 隐藏)
- [4. 常用帧类型](#4. 常用帧类型)
二、可靠传输与流控
- [5. PN 空间与 ACK Range](#5. PN 空间与 ACK Range)
- [6. 丢包判定与 PTO 重传](#6. 丢包判定与 PTO 重传)(含时间阈值/TLP)
- [7. 连接级与 Stream 级流控](#7. 连接级与 Stream 级流控)
- [8. 面试要点](#8. 面试要点)
1. UDP → Packet → Frame
text
UDP Datagram
└── QUIC Packet (Long 或 Short Header)
├── Frame: CRYPTO
├── Frame: ACK
└── Frame: STREAM
| 层级 | 职责 |
|---|---|
| UDP | 端到端 datagram 投递(不可靠) |
| QUIC Packet | 加密单元、PN、CID |
| QUIC Frame | 语义载荷(数据、握手、控制) |
实现上常见 一个 UDP 包只放一个 QUIC 包;规范允许合并,但不常见。
2. 包类型与 Long Header
| 类型 | Header | 阶段 | 典型内容 |
|---|---|---|---|
| Initial | Long | 建连 | ClientHello(CRYPTO) |
| Handshake | Long | 建连 | ServerHello、Finished |
| 0-RTT | Long | 复用 | 早期应用数据 |
| Short | Short | 1-RTT | STREAM、ACK 等 |
| Retry | Long | 特殊 | 服务端要求重试 + Token |
| Version Negotiation | 特殊 | 版本 | 不支持时的版本列表 |
Long Header 位图(RFC 9000 §17.2,概念):
text
|1| Type(7) | ← Header Form = 1
| Version (32) |
| DCID Len | Destination CID (*) |
| SCID Len | Source CID (*) |
| Type-specific (Token / Length 等,因 Initial/HS/0-RTT 而异) |
| [AEAD Protected Payload:PN + 帧列表] |
| 字段 | 说明 |
|---|---|
| Header Form = 1 | 长头标识 |
| Type | Initial=0x00 / 0-RTT=0x01 / Handshake=0x02 / Retry=0x03 |
| Version | QUIC 版本号 |
| DCID / SCID | 目的 / 源 Connection ID |
| Token | Initial 中可选(Retry 后) |
| Length | Payload 长度 |
| Payload | AEAD 加密(含 PN + 帧列表) |
Long Header 除 Retry/VN 外 :头部字段明文,Payload 密文。Initial / Handshake 包不得与 1-RTT 应用数据混在同一 QUIC 包类型规则下混用(实现上分开发)。
Short Header
Long Header
复用场景
Initial
Handshake
0-RTT
1-RTT App Data
3. Short Header 与 PN 隐藏
Short Header(1-RTT,RFC 9000 §17.3):
text
|0|C|K|1|P| Reserved | ← Form=0;C=Key Phase,K 保留,P=Spin Bit
| Destination CID |
| [Packet Number,加密/掩码,1/2/4 字节] |
| AEAD Protected Payload |
| 部分 | 说明 |
|---|---|
| Header Form = 0 | 短头 |
| C(Key Phase) | 指示当前 / 下一套密钥(Key Update ,见 QUIC协议详解2) |
| DCID | 定位连接(无 Version、无 SCID) |
| PN | 1/2/4 字节,加密/掩码 |
| Payload | AEAD 保护 |
PN 要点:
- 每个 加密级别 独立 PN 空间,严格单调递增。
- PN 用于:丢包检测、ACK 引用、AEAD nonce、防重放。
- 不能像 TCP SEQ 那样明文抓包直读------需解密或 key log。
Spin Bit(可选):用于 RTT 采样,部分网络会禁用。
4. 常用帧类型
帧通用格式:Type(变长整数)+ 类型相关字段。
核心帧(数据与可靠):
| 帧 | 作用 |
|---|---|
| STREAM | 应用/HTTP 数据;含 Stream ID、Offset、FIN |
| CRYPTO | TLS 握手片段;按 Offset 连续重组 |
| ACK | 确认 PN;支持多 ACK Range |
流控与连接管理:
| 帧 | 作用 |
|---|---|
| MAX_DATA | 连接级接收窗口上限 |
| MAX_STREAM_DATA | 单 Stream 接收上限 |
| MAX_STREAMS_BIDI / _UNI | 允许打开的流数量 |
| NEW_CONNECTION_ID | 下发可用 CID(迁移 / 防追踪 / LB) |
| RETIRE_CONNECTION_ID | 退役不再使用的 CID |
| RESET_STREAM / STOP_SENDING | 异常中止 / 停止发送 |
| HANDSHAKE_DONE | 服务端握手确认 |
| PING | 保活 / PTO 探测 |
| CONNECTION_CLOSE | 关闭连接 |
| PATH_CHALLENGE / PATH_RESPONSE | 路径验证(迁移) |
STREAM 帧(概念):
text
Type | Stream ID | [Offset] | [Length] | Data... | FIN?
- 同一 Stream 内靠 Offset 有序;FIN=1 表示发送结束。
- CRYPTO 帧 无 Stream ID,绑定当前 PN 空间的隐式 TLS 流,Offset 必须连续。
注意:
- 帧 不跨 QUIC 包重组;Stream 内靠 Offset 排序。
- PN 被加密,Wireshark 需 SSLKEYLOGFILE 解密(见 QUIC应用实践)。
5. PN 空间与 ACK Range
| PN Space | 包类型 |
|---|---|
| Initial | Initial |
| Handshake | Handshake |
| Application Data | Short Header |
规则:ACK 只引用同一 PN 空间;不同空间 PN 互不影响。
ACK 帧携带:
- Largest Acknowledged
- ACK Delay(接收处理延迟,用于 RTT)
- ACK Range Count + 多个区间
示例:
text
Largest Acked = 105
Ranges: [100--105], [90--95]
→ 确认 PN 90--95 与 100--105
表达力 ≥ TCP SACK ;发送端据此判断哪些 PN lost。
6. 丢包判定与 PTO 重传
6.1 判丢(简化)
| 机制 | 说明 |
|---|---|
| PN 超前 + 时间阈值 | PN 远小于 Largest Acked − threshold → lost |
| PTO | 超时未收到任何 ACK → 进入 Probe |
没有 TCP 式 triple duplicate ACK 快重传。
6.2 PTO 与重传
PTO(Probe Timeout) 近似(RFC 9000 §6.2,此处为简化写法;完整算法含 指数退避 、握手未确认前禁止发 1-RTT 数据、各 PN 空间独立计时 等):
text
PTO = smoothed_rtt + max(4 × rttvar, granularity)
触发后:
- 发 新 PN 的 probe 包(非原 PN 重发)。
- 内容常为 PING ,或未 ACK 的 STREAM/CRYPTO 帧重新封装。
- 连续 PTO 失败 → 指数退避 → 最终 CONNECTION_CLOSE。
是
PTO
发 PN=N
ACK?
清除 in_flight
新 PN + PING/重封装帧
与 TCP 对比:
| 项 | TCP | QUIC |
|---|---|---|
| 确认 | SEQ + ACK | PN + ACK Range |
| 快重传 | dup ACK | 无;靠 Range + 定时器 |
| 重传形态 | 同 SEQ 重发 | 帧进新 PN 包 |
6.3 时间阈值判丢与尾丢(TLP 思路)
除 PTO 外,RFC 9000 丢包检测还有 时间阈值(time threshold,常取 9/8 × smoothed_rtt) :某 PN 长期未被 ACK 且小于 Largest Acknowledged 时,可判为 lost 并重封装发送------工程上常称 TLP(Tail Loss Probe)思路 ,在整段 PTO 超时之前处理 尾部单包久未确认(尾丢、ACK 丢失等)。与 PTO 互补:
| 机制 | 典型触发 | 作用 |
|---|---|---|
| 时间阈值 / TLP 思路 | 有 ACK 进展,但尾部 PN 卡住 | 尽早重传疑似丢失帧 |
| PTO | 该 PN 空间 完全无 ACK | 发 probe(PING 或重封装帧) |
7. 连接级与 Stream 级流控
流控与可靠传输 解耦:流控答「还能不能收」;ACK 答「有没有收到」。
7.1 MAX_STREAM_DATA(单 Stream)
接收方通告:该 Stream 上发送方 Offset 不得超过 的上限。
text
初始 Max = 1MB → 发到 1MB 停等
消费 512KB → 发 MAX_STREAM_DATA 提到 1.5MB → 继续发
7.2 MAX_DATA(连接级)
所有 Stream 累计未消费字节 不得超过连接级上限。
7.3 MAX_STREAMS
限制对端可打开的 双向 / 单向 Stream 数量;与 Stream ID 编码配合(详见 QUIC协议详解2)。
| 控制 | 粒度 |
|---|---|
| MAX_STREAM_DATA | 单 Stream Offset |
| MAX_DATA | 全连接 |
| MAX_STREAMS | 并发 Stream 个数 |
8. 面试要点
| 问题 | 要点 |
|---|---|
| PN 与 TCP SEQ? | 分空间、加密、重传进 新 PN |
| 如何判丢? | ACK Range + 时间阈值(TLP 思路) + PTO |
| Initial 为何要 padding? | 防 UDP 放大攻击(未验证前限制响应大小) |
| 帧与包关系? | 多帧一包;Stream 靠 Offset 有序 |
发送/接收路径(小结)
text
发送:app → STREAM 帧 → 检查 MAX_* → 封装 Packet(PN) → in_flight
接收:解密 → 按 Offset 入缓冲 → ACK → 更新 MAX_STREAM_DATA
一句话 :QUIC协议详解1 钉死两件事------线上长什么样 (Long/Short、帧),丢了怎么办 (PN、ACK Range、PTO、流控)。连接语义与 TLS 见 QUIC协议详解2 ;HTTP/3 见 QUIC应用实践。