犀牛鸟 2026 研究笔记 estelledc.github.io

第 3 章:QUIC vs TCP——一张图看懂本质区别

从快递系统到网络协议

上一章我们用”寄快递”来类比 TCP。现在让我们把这个类比升级,用它来理解 QUIC 和 TCP 的根本差异。

想象两家快递公司:

TCP 快递公司(老牌、可靠、但规矩多):

QUIC 快递公司(新兴、灵活、安全):

这就是 QUIC 和 TCP 最核心的差异。下面我们逐一展开。


总览对比图

┌─────────────────────────────────────────────────────────────────┐
│                     协议栈对比                                    │
├─────────────────────────────┬───────────────────────────────────┤
│       TCP + TLS 1.3         │           QUIC                    │
├─────────────────────────────┼───────────────────────────────────┤
│  ┌───────────────────────┐  │  ┌─────────────────────────────┐  │
│  │      HTTP/2           │  │  │        HTTP/3               │  │
│  ├───────────────────────┤  │  ├─────────────────────────────┤  │
│  │      TLS 1.3          │  │  │         QUIC                │  │
│  ├───────────────────────┤  │  │  (传输+加密+多路复用 一体)    │  │
│  │       TCP             │  │  ├─────────────────────────────┤  │
│  ├───────────────────────┤  │  │         UDP                 │  │
│  │       IP              │  │  ├─────────────────────────────┤  │
│  └───────────────────────┘  │  │         IP                  │  │
│                             │  └─────────────────────────────┘  │
│  4 层各管各的               │  QUIC 是一个"超级层"              │
└─────────────────────────────┴───────────────────────────────────┘

关键区别:TCP 世界里,传输(TCP)和加密(TLS)是分开的两层,各有各的握手。QUIC 把这两件事合成了一层——这就是它能减少握手 RTT 的根本原因。


差异一:连接建立

TCP + TLS 1.3:最少 2 个 RTT

客户端                                         服务端
  |                                             |
  |──────── TCP SYN ───────────────────────────>|
  |<─────── TCP SYN+ACK ───────────────────────-|  ← 1 RTT (TCP握手)
  |──────── TCP ACK + TLS ClientHello ─────────>|
  |<─────── TLS ServerHello + Finished ────────-|  ← 2 RTT (TLS握手)
  |──────── TLS Finished + HTTP Request ───────>|
  |<─────── HTTP Response ─────────────────────-|  ← 3 RTT (首字节)
  |                                             |
  
  总计:2 RTT 后才能发送第一个应用数据
        3 RTT 后收到第一个响应

注意:TCP Fast Open (TFO) 和 TLS 1.3 0-RTT 可以优化到 1-2 RTT,但在实践中部署率很低,因为中间设备不兼容。

QUIC:首次 1 RTT,重连 0 RTT

首次连接:
客户端                                         服务端
  |                                             |
  |── Initial(ClientHello + 传输参数) ─────────>|
  |<─ Initial(ServerHello) + Handshake ────────-|  ← 1 RTT
  |── Handshake + 1-RTT(HTTP请求) ────────────->|
  |<─ 1-RTT(HTTP响应) ─────────────────────────-|  ← 2 RTT (首字节)
  |                                             |

重连(0-RTT):
客户端                                         服务端
  |                                             |
  |── Initial + 0-RTT(HTTP请求) ───────────────>|  ← 第一个包就有数据!
  |<─ Initial + Handshake + 1-RTT(HTTP响应) ───-|  ← 1 RTT (首字节!)
  |                                             |

  首次:1 RTT 后就能发应用数据
  重连:0 RTT!第一个包就能带应用数据

为什么能做到?

QUIC 把传输握手和 TLS 握手合并了。TCP 需要先完成三次握手确认”连接建立”,然后才能在这个连接上做 TLS 握手。QUIC 的 Initial 包同时完成了两件事:确认对方存在(传输层)+ 交换加密参数(TLS)。

0-RTT 的原理:第一次连接成功后,服务端会给客户端一个”会话票据”(Session Ticket)。下次客户端用这个票据,不需要重新协商加密参数,直接用上次协商好的密钥加密数据。

0-RTT 的代价

0-RTT 数据有重放攻击风险——攻击者可以截获 0-RTT 数据包并重新发送。因此 0-RTT 只适合幂等操作(GET 请求等),不适合有副作用的操作(POST 转账等)。这是安全性和速度的 trade-off。


差异二:数据传输模型

TCP:单一字节流

TCP 把所有数据视为一条连续的字节流。应用层写入的数据,TCP 负责按顺序、可靠地交付给对端。

应用层写入:
  write("Hello")   → 字节 0-4
  write("World")   → 字节 5-9
  write("Foo")     → 字节 10-12

TCP 看到的:
  [H][e][l][l][o][W][o][r][l][d][F][o][o]
   0  1  2  3  4  5  6  7  8  9  10 11 12
   ←────────── 一条连续的字节流 ──────────→

网络上的 TCP 段:
  Segment 1: SEQ=0,  数据="Hello"
  Segment 2: SEQ=5,  数据="World"  ← 假设这个丢了
  Segment 3: SEQ=10, 数据="Foo"    ← 已到达但不能交给应用

应用层读取:
  read() → "Hello" (可以读)
  read() → 阻塞等待... (字节5-9还没到)
  即使 "Foo" 已经在内核缓冲区里了,也读不到!

这就是 TCP 队头阻塞的根本原因:单一的序列号空间决定了所有数据必须有序交付。

QUIC:独立的多流

QUIC 原生支持多个流(Stream),每个流是独立的字节流,有自己的序列号空间:

应用层写入:
  Stream 0: write("CSS data")
  Stream 4: write("JS code")
  Stream 8: write("Image bytes")

QUIC 看到的:
  Stream 0: [C][S][S][ ][d][a][t][a]     独立的字节流
  Stream 4: [J][S][ ][c][o][d][e]         独立的字节流
  Stream 8: [I][m][a][g][e]               独立的字节流

网络上的 QUIC 包:
  Packet 1: {Stream 0, offset=0, "CSS "} + {Stream 4, offset=0, "JS"}
  Packet 2: {Stream 8, offset=0, "Imag"}  ← 假设这个丢了
  Packet 3: {Stream 0, offset=4, "data"} + {Stream 4, offset=2, " code"}

应用层读取:
  Stream 0: read() → "CSS data"  ✓ 完整到达,直接交付!
  Stream 4: read() → "JS code"   ✓ 完整到达,直接交付!
  Stream 8: read() → 等待重传... (只有这个流被阻塞)

关键区别:Stream 8 的丢包不影响 Stream 0 和 Stream 4 的交付。在 HTTP/3 中,CSS、JS、图片各自使用不同的 Stream,一个资源的丢包不会卡住其他资源。

流的类型和编号

QUIC 流通过 ID 标识,ID 的最低 2 位有特殊含义:

Stream ID 最低2位:
  0b00 (0, 4, 8, ...)   → 客户端发起的双向流
  0b01 (1, 5, 9, ...)   → 服务端发起的双向流
  0b10 (2, 6, 10, ...)  → 客户端发起的单向流
  0b11 (3, 7, 11, ...)  → 服务端发起的单向流

这个设计很巧妙——不需要额外的协商就能判断一个流是谁发起的、是单向还是双向。


差异三:包号与确认机制

TCP:序列号可复用

TCP 的 SEQ(序列号)表示”这是字节流中第几个字节”。重传时,同一个 SEQ 会被重新使用:

TCP 重传场景:
  发送: SEQ=1000, 数据="abc"  (第一次)
  超时,没收到 ACK
  重传: SEQ=1000, 数据="abc"  (重传,同一个SEQ!)

问题:收到 ACK=1003 时,无法判断是在确认第一次发送还是重传
  → RTT 估算不准确(重传歧义问题)

QUIC:包号单调递增

QUIC 的 Packet Number 是单调递增的,永远不会重复。重传的数据会用新的包号:

QUIC 重传场景:
  发送: Packet 100, 数据="abc"  (第一次)
  超时,没收到 ACK
  重传: Packet 150, 数据="abc"  (新包号!)

收到 ACK 确认 Packet 150 时:
  → 明确知道是在确认重传的那个包
  → RTT = 当前时间 - Packet 150 的发送时间
  → 精准!

为什么这很重要?

准确的 RTT 估算是拥塞控制的基础。如果 RTT 估错了,拥塞控制算法就会做出错误决策——要么太保守(浪费带宽),要么太激进(导致更多丢包)。

ACK 机制的差异

TCP 使用累积确认(Cumulative ACK):ACK=1000 表示”字节 1000 之前的都收到了”。

QUIC 使用 ACK Range:明确列出收到了哪些包号范围:

QUIC ACK Frame:
  Largest Acknowledged: 150
  ACK Ranges: [100-120], [130-140], [145-150]
  
  意思是:包 100-120、130-140、145-150 都收到了
          包 121-129、141-144 没收到(可能丢了)

这让发送端能精确知道哪些包丢了,而不是像 TCP 那样通过 SACK(选择性确认)来猜测。


差异四:加密范围

TCP:明文头部

TCP 段格式(简化):
┌───────────────────────────────────────┐
│ 源端口 (16bit) │ 目标端口 (16bit)     │ ← 明文
├───────────────────────────────────────┤
│          序列号 (32bit)                │ ← 明文
├───────────────────────────────────────┤
│         确认号 (32bit)                 │ ← 明文
├───────────────────────────────────────┤
│ 头长│保留│标志│ 窗口大小 (16bit)       │ ← 明文
├───────────────────────────────────────┤
│       校验和 │ 紧急指针                │ ← 明文
├───────────────────────────────────────┤
│              数据                      │ ← TLS加密(如果用了HTTPS)
└───────────────────────────────────────┘

中间设备能看到:端口、序列号、窗口大小、标志位
  → 防火墙可以根据这些信息做决策
  → 运营商可以做"TCP优化"(篡改窗口大小等)
  → 协议骨化:改不动了

QUIC:几乎全部加密

QUIC 包格式(1-RTT 包,简化):
┌─────────────────────────────────┐
│ Header Form (1bit)              │ ← 明文(必须的)
│ Fixed Bit (1bit)                │ ← 明文
│ Spin Bit (1bit)                 │ ← 明文(用于RTT测量)
│ Reserved (2bit)                 │ ← 加密保护
│ Key Phase (1bit)                │ ← 加密保护
│ Packet Number Length (2bit)     │ ← 加密保护
├─────────────────────────────────┤
│ Destination Connection ID       │ ← 明文(路由需要)
├─────────────────────────────────┤
│ Packet Number (1-4 bytes)       │ ← 头部保护(加密)
├─────────────────────────────────┤
│                                 │
│         Payload                 │ ← 完全加密
│   (STREAM frames,               │
│    ACK frames,                  │
│    所有控制信息...)              │
│                                 │
└─────────────────────────────────┘

中间设备能看到:几乎只有 Connection ID
  → 无法读取序列号(包号被加密)
  → 无法看到 ACK 信息
  → 无法做"优化"或篡改
  → 协议可以自由演进!

Header Protection(头部保护)

QUIC 有一个独特的设计——用包内容的一部分来加密包号。这叫 Header Protection。它的目的是让攻击者无法通过观察包号变化来推断流量模式。

加密前:[Header] [Packet Number] [Encrypted Payload]
                  ↑ 明文
                  
Header Protection 后:
  1. 取 Payload 前几个字节作为 mask
  2. 用 mask XOR Packet Number 的字节
  结果:[Header] [Protected PN] [Encrypted Payload]
                  ↑ 无法直接读取

差异五:连接标识

TCP:四元组绑定

TCP 连接标识 = (源IP, 源端口, 目的IP, 目的端口)

场景:手机从 WiFi 切到 4G
  WiFi 下: (192.168.1.100, 12345, 93.184.216.34, 443)
  4G 下:   (10.0.0.50,     54321, 93.184.216.34, 443)
            ↑ 变了           ↑ 变了

服务端:这是两个完全不同的连接!
  → 旧连接超时关闭
  → 新连接需要重新握手(TCP 3次 + TLS)
  → 应用层状态丢失(正在下载的文件、视频播放位置等)

QUIC:Connection ID

QUIC 连接标识 = Connection ID(一个随机的字节序列)

场景:手机从 WiFi 切到 4G
  WiFi 下: UDP(192.168.1.100:12345) + CID=0x1a2b3c4d
  4G 下:   UDP(10.0.0.50:54321)     + CID=0x1a2b3c4d
            ↑ 变了       ↑ 变了       ↑ 没变!

服务端:CID 对上了,还是同一个连接
  → 不需要重新握手
  → 应用层状态完整保留
  → 用户无感知切换

Connection ID 的高级用法

实际上,QUIC 连接会有多个 Connection ID。客户端和服务端各自可以为连接分配多个 CID:

为什么需要多个 CID?

隐私保护:
  如果永远用同一个 CID,网络窃听者可以追踪你的移动轨迹:
  "CID=0x1a2b 先出现在 WiFi,然后出现在 4G——是同一个人在移动!"

解决方案:
  每次网络切换时使用新的 CID:
  WiFi: CID=0x1a2b3c4d
  4G:   CID=0x5e6f7a8b  ← 不同的 CID!
  
  窃听者看不出这是同一个连接
  但服务端内部知道:两个 CID 都映射到同一个连接状态

差异六:多路复用的层次

HTTP/2 over TCP:应用层多路复用,传输层单流

HTTP/2 + TCP 的现实:

应用层(HTTP/2)认为自己有多个流:
  Stream 1: GET /index.html
  Stream 3: GET /style.css
  Stream 5: GET /image.png

但 TCP 只看到一条字节流:
  [S1数据][S3数据][S5数据][S1数据][S3数据]...
  
  TCP 丢了一个包 → 所有 HTTP/2 Stream 全部阻塞
  
  讽刺:HTTP/2 的多路复用设计,在 TCP 上反而可能比
        HTTP/1.1 的多连接(每个资源一条连接)更慢!

HTTP/3 over QUIC:传输层原生多流

HTTP/3 + QUIC 的现实:

QUIC 传输层原生支持多个流:
  Stream 0: GET /index.html  ← 独立序列号空间
  Stream 4: GET /style.css   ← 独立序列号空间  
  Stream 8: GET /image.png   ← 独立序列号空间

QUIC 丢了 Stream 8 的一个包:
  Stream 0: 继续交付 ✓
  Stream 4: 继续交付 ✓
  Stream 8: 等待重传 ✗ (只有这个阻塞)
  
  终于实现了"真正的"多路复用!

差异七:拥塞控制的灵活性

TCP:内核固化,升级困难

TCP 的拥塞控制实现在操作系统内核中。要换一个拥塞控制算法(比如从 CUBIC 换到 BBR),你需要:

  1. 升级操作系统内核(或加载内核模块)
  2. 修改系统配置
  3. 重启服务

而且不同客户端(Windows/macOS/Linux/iOS/Android)各有各的内核实现,行为可能不同。

QUIC:用户态实现,随应用更新

QUIC 运行在用户态(不是内核里),拥塞控制作为应用的一部分:

TCP 拥塞控制部署:
  需要升级 OS 内核 → 全球设备升级周期以年计

QUIC 拥塞控制部署:
  应用自带 → 更新 App 就能用新算法
  A/B 测试 → 同一服务器对不同用户用不同算法
  自定义 → 可以根据业务特点定制

这就是为什么 QUIC 实现里拥塞控制的多样性远超 TCP:

算法 TCP 支持 QUIC 支持
CUBIC Linux/Windows/macOS 默认 quiche, s2n-quic, TQUIC
BBR Linux 4.9+ quiche (Chromium port), TQUIC
BBR2 Linux 5.x+ 实验 quiche
BBR3 Linux 6.x+ TQUIC (唯一)
COPA 未进入主线内核 TQUIC (唯一)

差异八:可演进性(Anti-Ossification)

TCP 的骨化教训

过去 20 年,多少 TCP 扩展失败了:

失败案例:
- TCP Fast Open (TFO):~7% 的网络路径上被中间设备阻断
- ECN (显式拥塞通知):部署了 20+ 年,实际使用率仍然很低
- 新的 TCP Option:很多防火墙会丢弃包含"不认识"选项的包
- Multipath TCP (MPTCP):中间设备可能修改 TCP 头部,破坏完整性

根本原因:TCP 头部是明文的,中间设备可以检查和修改

QUIC 的防骨化设计

QUIC 从设计之初就把”防止骨化”作为核心目标:

策略1:加密一切
  → 中间设备看不到内部结构,无法形成依赖

策略2:GREASE(随机生成无意义的扩展)
  → 如果中间设备遇到不认识的值就丢包,
    那部署 GREASE 后这些设备会被发现并修复
  → 防止"未来扩展被阻断"的问题

策略3:Version Negotiation
  → 协议版本号可以更新(已有 QUIC v2 = RFC 9369)
  → 新版本可以改变任何加密后的内部格式

策略4:最少暴露
  → 只暴露路由必需的信息(Connection ID)
  → Spin Bit 是唯一允许的可观测性让步(用于 RTT 测量)

综合对比表

维度 TCP QUIC
所在层 内核传输层 用户态(UDP 之上)
首次连接 2-3 RTT (TCP+TLS) 1 RTT
重复连接 1-2 RTT (TCP+TLS恢复) 0 RTT
数据模型 单一字节流 多独立流
队头阻塞 有(流间+流内) 仅流内
包号 可复用(重传歧义) 单调递增(精确 RTT)
确认方式 累积 ACK + SACK ACK Range
加密 可选(TLS 是上层) 强制(内置 TLS 1.3)
头部保护 无(明文) 有(Header Protection)
连接标识 四元组 Connection ID
连接迁移 不支持 原生支持
拥塞控制 内核实现 用户态,可插拔
可演进性 骨化严重 防骨化设计
NAT 穿透 依赖端口映射 CID 不依赖端口
中间设备干预 常见且难以避免 几乎无法干预

代码视角:quinn 中的 Stream vs TCP Socket

让我们看看在代码层面,QUIC 的多流是怎么体现的:

// TCP: 一个连接就是一个 socket,读写都在同一个流上
let tcp_stream = TcpStream::connect("server:443").await?;
tcp_stream.write_all(b"request 1").await?;  // ← 写入唯一的字节流
tcp_stream.write_all(b"request 2").await?;  // ← 还是同一个字节流
let response = tcp_stream.read(&mut buf).await?;

// QUIC (quinn): 一个连接可以开多个流
let connection = endpoint.connect(addr, "server")?.await?;

// 并行打开多个流,互不影响
let (mut send1, recv1) = connection.open_bi().await?;  // ← 流1
let (mut send2, recv2) = connection.open_bi().await?;  // ← 流2

send1.write_all(b"request on stream 1").await?;  // ← 独立!
send2.write_all(b"request on stream 2").await?;  // ← 独立!
// 流1的丢包不会阻塞流2的读取

核心区别:TCP 是”一个连接一个流”,QUIC 是”一个连接多个流”。HTTP/2 在应用层模拟了多流(但底层还是 TCP 的单流),HTTP/3 则使用 QUIC 的原生多流。


代码视角:Connection ID 在 quiche 中的实现

// quiche 中连接的创建(简化)
pub fn accept(
    scid: &ConnectionId,   // ← 服务端选择的 Connection ID
    odcid: Option<&ConnectionId>,
    local: SocketAddr,
    peer: SocketAddr,
    config: &mut Config,
) -> Result<Connection> {
    // Connection ID 是连接的唯一标识
    // 注意:这里虽然也传入了 local/peer 地址,
    // 但地址变化时连接不会断开——靠 CID 识别
}

// 当客户端 IP 变化时(连接迁移)
// quiche 内部会:
// 1. 发现包的源地址变了
// 2. 但 CID 匹配 → 还是同一个连接
// 3. 验证路径(发 PATH_CHALLENGE)
// 4. 确认新路径可用后继续传输

实际性能差异:数据说话

Google 在 2017 年的论文中给出了对比数据(SIGCOMM 2017):

场景:YouTube 视频播放

网络条件          TCP 重缓冲率    QUIC 重缓冲率    改善
─────────────────────────────────────────────────────
丢包率 1%          2.3%           1.6%          30% ↓
丢包率 3%          7.1%           4.8%          32% ↓
丢包率 5%          12.4%          8.2%          34% ↓

搜索首字节时间 (TTFB):
网络条件          TCP TTFB       QUIC TTFB      改善
─────────────────────────────────────────────────────
良好网络           ~50ms          ~35ms         30% ↓
中等网络           ~200ms         ~120ms        40% ↓
弱网              ~800ms         ~400ms         50% ↓

关键发现:网络越差,QUIC 的优势越明显。在良好网络下差异较小,因为 TCP 的缺陷被掩盖了。


但 QUIC 不是银弹

QUIC 的劣势和挑战

1. CPU 开销更高

QUIC 在用户态实现,加密/解密每个包都需要 CPU 参与。TCP 在内核态有硬件卸载(TSO、GRO、checksum offload)的支持:

TCP:  应用 → 内核 → 网卡(硬件加速)
QUIC: 应用(加密+封装) → 内核 → 网卡

QUIC 的额外开销:
- 每个包的 AEAD 加密/解密
- Header Protection 的加密/解密
- 用户态到内核态的上下文切换更多

这就是为什么 msquic 要做 XDP(内核旁路)、s2n-quic 要做 io_uring——都在优化这个瓶颈。

2. UDP 可能被阻断

有些企业防火墙、老旧路由器会阻断 UDP 流量。QUIC 实现通常需要一个回退机制:如果 UDP 不通,退回到 TCP+TLS。

QUIC 连接尝试:
  1. 尝试 QUIC (UDP:443)
  2. 如果超时(可能被阻断)
  3. 回退到 HTTP/2 (TCP:443)
  
  实际数据:约 2-5% 的网络环境无法使用 QUIC

3. 实现复杂度高

QUIC 把 TCP + TLS + HTTP/2 的多路复用合在一起,实现难度远超单独的 TCP:

协议复杂度对比:
  TCP (RFC 793): ~85 页
  TLS 1.3 (RFC 8446): ~160 页
  QUIC 核心 (RFC 9000): ~151 页
  QUIC TLS (RFC 9001): ~52 页  
  QUIC 恢复 (RFC 9002): ~42 页
  ─────────────────────────────
  QUIC 总计: ~245 页(且相互交织)

4. 调试更困难

TCP 是明文的,Wireshark 直接能看到所有细节。QUIC 加密后,你需要密钥才能解密看到内部信息。虽然有 SSLKEYLOGFILE 机制(和 TLS 一样),但确实增加了调试难度。


什么时候该用 QUIC,什么时候 TCP 够用?

场景 推荐 原因
公网 Web 应用 QUIC 丢包改善、0-RTT、连接迁移
移动端 App QUIC 网络切换频繁,连接迁移是刚需
弱网/高丢包 QUIC 无跨流 HOL,性能优势显著
内网微服务 TCP 网络好、延迟低,QUIC 优势不明显
数据库连接 TCP 长连接、低丢包,TCP 更成熟
需要硬件加速 TCP 网卡 offload 支持更完善
低 CPU 资源设备 TCP QUIC 加密开销更大

本章小结

维度 TCP 的设计 QUIC 的设计 为什么 QUIC 更好
连接建立 分层握手 合并握手 少 1-2 个 RTT
数据模型 单字节流 多独立流 消除跨流 HOL
包号 可复用 单调递增 精确 RTT 估算
加密 可选/分层 强制/内置 防骨化+隐私
连接标识 IP+端口 Connection ID 支持迁移
升级部署 改内核 改应用 快速迭代

一句话总结:QUIC 通过在用户态重新设计传输层,解决了 TCP 40 年积累的三大问题(HOL 阻塞、握手延迟、无法迁移),同时通过全程加密保持了协议的可演进性。


读完本章你能做什么

  1. 画出 TCP+TLS vs QUIC 的连接建立时序图,标注 RTT 数
  2. 解释为什么 HTTP/2 的多路复用在 TCP 上是”伪多路复用”
  3. 说出 QUIC 包号单调递增对 RTT 估算的好处
  4. 解释 Connection ID 如何实现连接迁移
  5. 列出 QUIC 相比 TCP 的三个劣势
  6. 给出”什么场景用 QUIC、什么场景用 TCP”的判断标准

常见误区

误区一:QUIC 完全没有队头阻塞

正确理解:QUIC 消除了跨流的队头阻塞,但流内的队头阻塞仍然存在。如果 Stream 4 的第一个包丢了,Stream 4 内部后续的数据仍然要等重传。区别在于:Stream 0 和 Stream 8 不受影响。

误区二:0-RTT 可以用在所有场景

正确理解:0-RTT 数据有重放攻击风险。攻击者可以截获 0-RTT 包并重发,让服务器执行两次同样的操作。因此 0-RTT 只应该用于幂等操作(GET、HEAD),不能用于有副作用的操作(POST 转账、下单)。服务端需要实现重放检测或限制 0-RTT 只接受幂等请求。

误区三:QUIC 比 TCP 快是因为用了 UDP

正确理解:UDP 本身不提供任何性能优势——它只是一个”空壳”,让 QUIC 有地方放自己的协议。QUIC 快的原因是更好的连接建立(合并握手)、消除跨流 HOL、以及更先进的拥塞控制实现。QUIC 在 UDP 之上做的可靠性保证一点都不比 TCP 少。


导读目录:README.md 上一章:第 2 章 · 阅读路线图与前置知识 下一章:第 4 章 · 连接建立与握手——从陌生人到老朋友