This repository has been archived on 2026-05-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
all-in-kingsoft/hhs/NETWORK/10-前沿与进阶/01-QUIC协议深度解析.md
T
2026-05-17 22:27:07 +08:00

186 lines
6.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
tags: [计算机网络, QUIC, HTTP/3, ConnectionID, 0-RTT, QPACK, NAT迁移]
create time: 2026-05-18 05:25
---
# QUIC 协议深度解析
## 概述
QUIC (Quick UDP Internet Connections) 正在静默地替代 TCP——Google、Cloudflare、Microsoft 的流量中已有超过 40% 走的是 QUIC。理解 QUIC 的设计哲学和具体机制,是把握下一代网络协议的钥匙。
## 为什么需要 QUIC?
```mermaid
flowchart TD
Problem["TCP 的三大历史包袱"] --> P1["• 拥塞控制算法写死在内核<br/>无法按需调整"]
Problem --> P2["• IP/port 变化 → 四元组变化<br/>连接必须重建"]
Problem --> P3["• TLS in TCP → 双重握手延迟<br/>三次握手 + TLS 握手 = 最少 2 RTT"]
Solution["QUIC 的三个解答"] --> S1["✅ 用户态实现 BBRv2 等算法<br/>应用层可编程、随时升级"]
Solution --> S2["✅ Connection ID 独立于 IP/port<br/>WiFi↔5G 无缝切换"]
Solution --> S3["✅ 内置 TLS 1.3 + 0-RTT<br/>首次握手即加密, 续连零延迟"]
style Problem fill:#FFD700,color:#000
style Solution fill:#98FB98,color:#000
```
### TCP vs QUIC 核心对比
| 维度 | TCP + TLS | QUIC + TLS 1.3 |
|------|-----------|---------------|
| 传输协议 | TCP (可靠,面向字节流) | UDP (不可靠,自带可靠性层) |
| 拥塞控制 | 内核固定 (cubic/bbr) | 用户态可插拔 (BBRv2, NewReno...) |
| 连接标识 | 四元组 (src_ip, src_port, dst_ip, dst_port) | Connection ID (16 bytes, 自定义) |
| 握手延迟 | 1 RTT (TCP) + 1 RTT (TLS 1.3) = 2 RTT | 1 RTT 完成 TCP+TLS (0-RTT 可选) |
| 多路复用 | 应用层自己搞 (HTTP/2) | 原生支持多 Stream,零队头阻塞 |
| NAT 穿透 | 断开重连 | CID 不变,自动迁移 |
| 头部压缩 | HPACK (HTTP/2) / QPACK (HTTP/3) | QPACK(解决队头阻塞) |
## QUIC 的连接管理
### Connection ID —— NAT 迁移的关键
```
TCP 连接标识 = (src_ip, src_port, dst_ip, dst_port)
→ IP 变了 → 四元组变 → 连接断了 ❌
QUIC 连接标识 = Connection ID (16 bytes,由端方自定义)
→ IP/port 变了 → CID 不变 → 连接继续 ✅
典型场景: iPhone 从 WiFi 切换到 5G
旧 IP: fe80::abcd → 新 IP: fe80::efgh
TCP: 连接断开,需重连 (RTT × 2)
QUIC: CID 不变,数据包自动迁移到新 IP
```
```
Connection ID 的生命周期:
─────────────────────────
Client → Server: Initial Packet {CID: client_cid}
Server → Client: Initial Packet {CID: server_cid}
Server → Client: Handshake Packet {CID: server_cid}
Client → Server: Handshake Packet {CID: client_cid}
Client → Server: 1-RTT Packet {CID: client_cid} ← 加密载荷, 业务数据
```
> [!tip] 为什么 QUIC 用 UDP 而不是直接改 TCP?
>
> 修改 TCP 需要在操作系统内核层面做全球部署,这几乎是不可能的(每个发行版都有不同)。而把可靠性搬到用户态用 UDP 承载——虽然看起来是"倒退"——但部署极其灵活:SDK 更新即可生效。Google 的 QUIC 实现就是作为一个 Chrome 扩展库无声更新的。
### QUIC Stream 独立性 —— 消除队头阻塞
```
QUIC Stream 独立性:
┌──────────────┐
│ Stream 0 │ ← 控制通道 (HTTP/3 信令)
│ Stream 1 │ ← Request A [✓ Data ✓ ACK] ✅ 正常
│ Stream 2 │ ← Request B [✓ Data ✓ ACK] ✅ 正常
│ Stream 3 │ ← Request C [✗ Lost 🔄 仅重传此流!]
│ Stream 4 │ ← Response A [✓ Data ✓ ACK] ←不受 Stream 3 影响!
│ Stream 5 │ ← Request D [🔄 正在发送...]
└──────────────┘
对比 HTTP/2 (共用 TCP 流):
请求 C 丢包 → 所有后续请求(包括 D、响应A)都要等 C 重传完成!
这就是 HTTP/2 在弱网下不如 HTTP/3 的原因。
```
## 0-RTT 握手与重放攻击
### 0-RTT 的工作原理
```mermaid
sequenceDiagram
participant C as Client
participant S as Server
Note over C,S: 首次连接: 1-RTT handshake
C->>S: ClientHello + HelloRetryRequest + Finished
S-->>C: 1-RTT Ready
Note over C,S: Session 缓存 (Session Ticket)
C->>S: ClientHello + 0-RTT data + Finished
Note over S: 验证 Session Ticket + 重放检测
alt 允许 0-RTT
S-->>C: Early data accepted ✅
else 拒绝 0-RTT
S-->>C: 0-RTT rejected ⚠️ (但仍处理后续数据)
end
```
```
⚠️ 0-RTT 的重放攻击风险:
─────────────────────────
攻击者截获了客户端的 0-RTT 请求 (如 POST /transfer?to=hacker&amount=10000)
然后把这个请求重新发给服务器
服务器认为是新的合法请求 → 重复转账 💸
防御措施:
1. 服务端对 0-RTT 请求标记 "early data"
2. 幂等操作 (GET/HEAD/PUT 删除) 可以安全使用 0-RTT
3. 非幂等操作需在应用层加防重放 Token
4. QUIC 的 MaxEarlyData 字段限制重放窗口大小
```
### HTTP/3 的 QPACK —— 区别于 HPACK
```
HPACK (HTTP/2) 的问题:
• Header 压缩表与数据共享同一 TCP 流
• 如果 TCP 数据包丢失 → 解压失败 → 所有后续 header 阻塞
• 这就是 HTTP/2 的队头阻塞在头部压缩层面的体现
QPACK (HTTP/3) 的解法:
• 压缩表独立于数据流 (unblocked streams)
• 即使某个 QUIC stream 丢了,header 仍能正确解码
• 通过 encoder/decoder 两侧维护独立的动态表实现
```
## QUIC 连接生命周期
```
QUIC 连接的建立:
─────────────────
1. Client → Send Initial Packet (无 CID 或临时 CID)
2. Server → Send Initial Packet (含 server_cid)
3. Server → Send Handshake Packet
4. Client → Send Handshake Packet
5. Both → Send 1-RTT Packets (加密载荷)
6. Connection established ✅
关闭:
─────────────────
• QUIC 没有 RST/FIN 标志位
• 用 CLOSE_CONNECTION_FRAME 通知对方
• 双方确认后关闭
• 类似 TCP 的四次挥手但在应用层实现
```
## Go 中的 QUIC 实现
```go
import (
"github.com/quic-go/quic-go"
)
// QUIC 服务器
config := &quic.Config{
Tracer: quic.TraceWriter, // 可观测性
}
listener, err := quic.Listen(nil, nil, config)
conn, err := listener.Accept(ctx) // 接受新连接
stream, err := conn.OpenStream() // 打开一个独立的 Stream
stream.Write([]byte("hello")) // Stream 3 的数据不会影响 Stream 5
```
> [!tip] 为什么 Go 原生不支持 QUIC?
> Go 的 net/http 标准库目前仍基于 TCP。第三方实现 `quic-go` (by @malgorithms) 是最成熟的 Go QUIC 库,已在多个生产环境使用。Go 官方对加入 QUIC 支持持开放态度。
## 关联笔记
- [[hhs/NETWORK/HTTPS与TLS握手]] — TLS 1.3 是 QUIC 的安全基础
- [[hhs/NETWORK/TCP段结构与状态机]] — QUIC 要替代的正是 TCP 的这些机制
- [[hhs/NETWORK/前沿与进阶综述]] — QUIC 在更广泛前沿技术中的位置