6.9 KiB
6.9 KiB
tags, create time
| tags | create time | |||||||
|---|---|---|---|---|---|---|---|---|
|
2026-05-18 05:25 |
QUIC 协议深度解析
概述
QUIC (Quick UDP Internet Connections) 正在静默地替代 TCP——Google、Cloudflare、Microsoft 的流量中已有超过 40% 走的是 QUIC。理解 QUIC 的设计哲学和具体机制,是把握下一代网络协议的钥匙。
为什么需要 QUIC?
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 的工作原理
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 实现
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 在更广泛前沿技术中的位置