第 7 章:社区标杆:quinn 与 quiche——极简 vs 全能
从两种餐厅说起
想象两家餐厅:
quinn 餐厅(精品小店):
- 菜单只有 20 道菜,但每道都是招牌
- 厨房小而精,一眼能看清所有操作
- 点菜流程极简:坐下、点、吃、走
- 适合”我就想快速吃顿好的”的人
- 如果你想学做菜,这里是最好的观摩地
quiche 餐厅(五星级酒店):
- 菜单 200 道,中西日法韩都有
- 厨房巨大,分成多个独立工作站
- 点菜流程:预约、选套餐、配酒、主菜、甜点…
- 适合”我要办正式宴会”的人
- 每天接待上千桌(Cloudflare 规模的生产环境)
两家都用 Rust 做菜(写代码),都很好吃(质量高),但设计哲学完全不同:一个追求极简可读,一个追求全能生产级。
quinn:最佳学习入口
基本信息
项目:quinn
GitHub: quinn-rs/quinn
Stars: ~5.2K
语言:Rust
定位:最简洁的 QUIC 实现,适合学习和嵌入
架构:sans-io 核心 + async 运行时集成
代码量:~150 文件
TLS 后端:rustls(纯 Rust TLS)
维护者:社区驱动(主要维护者 djc / Ralith)
三层架构:quinn 的核心设计
quinn 把代码分成了三个独立的 crate(Rust 的库单位),每层职责明确:
┌─────────────────────────────────────────┐
│ quinn (顶层) │
│ 异步 API + tokio 运行时集成 │
│ 用户直接使用的接口 │
│ Endpoint, Connection, SendStream... │
├─────────────────────────────────────────┤
│ quinn-proto (协议核心) │
│ 纯逻辑层,无 I/O,无异步 │
│ 状态机 + 包处理 + 拥塞控制 │
│ Connection, Endpoint (内部版本) │
├─────────────────────────────────────────┤
│ quinn-udp (平台层) │
│ 平台相关的 UDP socket 操作 │
│ GSO/GRO 优化、recvmmsg/sendmmsg │
└─────────────────────────────────────────┘
为什么分三层?
分层的好处:
1. quinn-proto 是纯逻辑——可以用在任何环境
- 嵌入式系统?用 quinn-proto + 你自己的 I/O
- 不想用 tokio?用 quinn-proto + 其他 async 运行时
- 想做测试?直接驱动 quinn-proto,不需要真实网络
2. quinn(顶层)提供开箱即用体验
- 大多数用户只需要 `use quinn::*`
- 自动管理 socket、异步调度、超时
3. quinn-udp 封装平台差异
- Linux 有 sendmmsg(批量发送),macOS 没有
- GSO/GRO 加速只在特定内核版本可用
- 应用层代码不需要关心这些
sans-io 设计模式
quinn-proto 使用”sans-io”模式。这是什么意思?
传统设计(带 I/O):
fn send_data(socket: &UdpSocket, data: &[u8]) {
let packet = build_packet(data);
socket.send(&packet); // ← 直接做 I/O
}
问题:
- 无法测试(需要真实网络)
- 无法嵌入(绑定了特定的 socket API)
- 无法在不同运行时之间切换
sans-io 设计(无 I/O):
fn send_data(state: &mut Connection, data: &[u8]) -> Vec<u8> {
build_packet(state, data) // ← 只返回字节,不做 I/O
}
调用者负责实际的 I/O:
let packet = proto_conn.send_data(data);
socket.send(&packet); // ← I/O 在外面做
好处:
- 可测试(直接比较输出字节)
- 可嵌入(调用者决定如何发送)
- 可在任何运行时使用
quinn-proto 的核心接口:
// quinn-proto 的连接接口(简化)
impl Connection {
/// 处理收到的 UDP 数据报
pub fn handle(&mut self, now: Instant, remote: SocketAddr,
data: BytesMut) -> Result<(), ConnectionError> {
// 解析、解密、处理包
// 不做任何 I/O!只更新内部状态
}
/// 获取需要发送的数据(调用者负责实际发送)
pub fn poll_transmit(&mut self, now: Instant) -> Option<Transmit> {
// 返回要发送的 UDP 数据报
// Transmit 包含:目标地址 + 数据
}
/// 获取下一个超时时间(调用者负责设定时器)
pub fn poll_timeout(&self) -> Option<Instant> {
// 返回最早的超时时间
// 调用者需要在这个时间调用 handle_timeout()
}
/// 处理超时
pub fn handle_timeout(&mut self, now: Instant) {
// 检查哪些超时触发了(PTO、idle timeout 等)
}
}
这种设计让 quinn-proto 变成了一个”纯状态机”——给它输入(收到的包、时间),它产出输出(要发的包、事件),中间不做任何 I/O。
quinn 的代码结构
quinn/
├── quinn/ ← 顶层 crate(异步 API)
│ ├── src/
│ │ ├── connection.rs ← 异步 Connection(包装 proto)
│ │ ├── endpoint.rs ← 异步 Endpoint
│ │ ├── send_stream.rs ← 发送流
│ │ ├── recv_stream.rs ← 接收流
│ │ └── ...
│
├── quinn-proto/ ← 协议核心(sans-io)
│ ├── src/
│ │ ├── connection/
│ │ │ ├── mod.rs ← 连接主逻辑
│ │ │ ├── streams.rs ← 流管理
│ │ │ ├── spaces.rs ← 包号空间
│ │ │ └── ...
│ │ ├── endpoint.rs ← Endpoint 逻辑
│ │ ├── congestion/ ← 拥塞控制
│ │ │ ├── cubic.rs
│ │ │ ├── new_reno.rs
│ │ │ └── ...
│ │ ├── crypto/ ← 加密抽象层
│ │ └── ...
│
├── quinn-udp/ ← UDP socket 封装
│ ├── src/
│ │ ├── unix.rs ← Linux/macOS 实现
│ │ └── windows.rs ← Windows 实现
│
└── bench/ ← 性能基准测试
为什么 quinn 是学习 QUIC 的最佳入口
学习友好的原因:
1. 代码量少:~150 文件 vs quiche ~300+ 文件
→ 更容易建立全局认知
2. 纯 Rust:没有 C FFI、没有跨语言调用
→ 跳转定义一路到底,不会突然跳到 C 代码
3. 类型系统即文档:
- Connection 的状态用 enum 表示
- 非法状态转换编译时报错
- 看类型签名就知道这个函数能做什么
4. 测试清晰:
- 由于 sans-io,测试直接驱动状态机
- 不需要模拟网络
- 测试代码就是使用示例
5. 社区活跃:
- GitHub Discussions 有大量讨论
- Issue 回复及时
- 代码注释质量高
quinn 的局限性
不适合的场景:
1. 不支持多路径 QUIC
- 没有 MultipathScheduler 或 PathMap
2. 拥塞控制算法少
- 只有 CUBIC 和 NewReno
- 没有 BBR/BBR3/COPA
3. 没有 C/C++ FFI
- 纯 Rust 生态,无法被 C 应用调用
4. 没有在超大规模生产环境验证
- 有生产用户但规模远不如 Cloudflare (quiche)
quiche:Cloudflare 的生产利器
基本信息
项目:quiche
GitHub: cloudflare/quiche
Stars: ~11.4K
语言:Rust (核心) + C (FFI)
定位:生产级 QUIC + HTTP/3 实现
架构:sans-io 核心 + 多语言 FFI
代码量:~300+ 文件
TLS 后端:BoringSSL(Google 的 OpenSSL 分支)
维护者:Cloudflare 工程团队
quiche 的 sans-io 与 quinn 的 sans-io 有何不同
两者都是 sans-io,但设计哲学不同:
quinn 的 sans-io:
目标:让用户可以选择不同的异步运行时
顶层提供 tokio 集成,但 proto 层是运行时无关的
面向 Rust 生态
quiche 的 sans-io:
目标:让 QUIC 库可以被任何语言调用
核心库不假设任何 I/O 模型
通过 C FFI 暴露给 C/C++/Python/Go/...
面向多语言生态
区别:
quinn: sans-io 为了"Rust 运行时灵活性"
quiche: sans-io 为了"跨语言 + 跨平台"
quiche 的 API 风格:
// quiche 的使用方式(Rust 侧,简化)
let mut conn = quiche::connect(None, &scid, local, peer, &mut config)?;
// 应用负责 I/O 循环
loop {
// 1. 从 socket 读取数据
let (len, from) = socket.recv_from(&mut buf)?;
// 2. 喂给 quiche 处理
conn.recv(&mut buf[..len], quiche::RecvInfo { from })?; // ← sans-io!
// 3. 从 quiche 取出要发的数据
loop {
let (len, send_info) = match conn.send(&mut out) { // ← sans-io!
Ok(v) => v,
Err(quiche::Error::Done) => break,
Err(e) => return Err(e),
};
socket.send_to(&out[..len], send_info.to)?;
}
// 4. 处理超时
conn.on_timeout();
}
注意:quiche 没有像 quinn 那样提供”开箱即用”的异步顶层。你必须自己写 I/O 循环。这是设计选择——给调用者最大控制权。
quiche 的双拥塞控制引擎
quiche 独特的地方:它有两套拥塞控制实现:
┌──────────────────────────────────────────────────┐
│ quiche 拥塞控制 │
├──────────────────────┬───────────────────────────┤
│ Native Engine │ Chromium Engine │
├──────────────────────┼───────────────────────────┤
│ 纯 Rust 实现 │ 从 Chromium 移植的 C++ 代码│
│ CUBIC, NewReno │ BBR, BBR2 │
│ Hystart++ │ │
│ │ │
│ 优点: │ 优点: │
│ - 代码清晰 │ - 经过亿级用户验证 │
│ - 易于修改 │ - 行为与Chrome一致 │
│ - 纯Rust依赖 │ - 包含所有边界情况处理 │
│ │ │
│ 缺点: │ 缺点: │
│ - 验证不如Chromium多 │ - 代码风格是C++移植 │
│ │ - 要跟踪Chromium更新 │
│ │ - 包含Chromium的bug │
└──────────────────────┴───────────────────────────┘
Cloudflare 在生产环境中使用 Chromium BBR 引擎——因为它已经在 Chrome 上被数十亿用户验证过了。
quiche 的 HTTP/3 支持
quiche 内置了完整的 HTTP/3 实现(quiche::h3 模块),这对 Cloudflare 的 CDN 场景至关重要:
// quiche 的 HTTP/3 使用(简化)
let mut h3_conn = quiche::h3::Connection::with_transport(
&mut conn, &h3_config
)?;
// 发送 HTTP/3 请求
let headers = vec![
quiche::h3::Header::new(b":method", b"GET"),
quiche::h3::Header::new(b":path", b"/index.html"),
quiche::h3::Header::new(b":authority", b"example.com"),
];
h3_conn.send_request(&mut conn, &headers, true)?;
// 接收响应
loop {
match h3_conn.poll(&mut conn) {
Ok((stream_id, quiche::h3::Event::Headers { list, .. })) => {
// 处理响应头
}
Ok((stream_id, quiche::h3::Event::Data)) => {
// 读取响应体
h3_conn.recv_body(&mut conn, stream_id, &mut body)?;
}
Ok((_, quiche::h3::Event::Finished)) => break,
Err(quiche::h3::Error::Done) => break,
_ => {}
}
}
quiche 的 C FFI:跨语言的桥梁
quiche 提供了完整的 C API,让非 Rust 项目也能使用:
// C 语言使用 quiche(简化)
quiche_config *config = quiche_config_new(QUICHE_PROTOCOL_VERSION);
quiche_config_set_application_protos(config, protos, protos_len);
quiche_config_set_max_idle_timeout(config, 5000);
// 创建连接
quiche_conn *conn = quiche_connect(
"example.com", scid, scid_len,
local, local_len, peer, peer_len, config
);
// 处理收到的数据
quiche_recv_info recv_info = { from, from_len, to, to_len };
ssize_t done = quiche_conn_recv(conn, buf, buf_len, &recv_info);
// 获取要发送的数据
quiche_send_info send_info;
ssize_t written = quiche_conn_send(conn, out, out_len, &send_info);
这使得 quiche 被用在了很多非 Rust 项目中,比如 nginx 的 QUIC 模块(通过 FFI 调用 quiche)和 curl 的 HTTP/3 支持(quiche 作为后端之一)。
quiche 的代码结构
quiche/
├── quiche/ ← 核心库
│ ├── src/
│ │ ├── lib.rs ← 主入口,Connection 实现
│ │ ├── packet.rs ← 包解析和构造
│ │ ├── stream.rs ← 流管理
│ │ ├── ranges.rs ← ACK Range 管理
│ │ ├── recovery/ ← 丢包恢复
│ │ │ ├── mod.rs
│ │ │ ├── cubic.rs
│ │ │ └── ...
│ │ ├── h3/ ← HTTP/3 实现
│ │ │ ├── mod.rs
│ │ │ ├── frame.rs
│ │ │ └── ...
│ │ ├── ffi.rs ← C FFI 接口
│ │ └── ...
│ ├── include/
│ │ └── quiche.h ← C 头文件
│
├── quiche4nginx/ ← nginx 集成
├── apps/ ← 示例应用
│ ├── src/
│ │ ├── client.rs ← 示例客户端
│ │ └── server.rs ← 示例服务端
│
└── tools/ ← 工具和测试
quinn vs quiche:详细对比
架构对比
quinn quiche
┌──────────────────────────┐ ┌──────────────────────────┐
│ quinn (async, tokio) │ │ 应用自己的 I/O 循环 │
│ ┌────────────────────┐ │ │ ┌────────────────────┐ │
│ │ quinn-proto │ │ │ │ quiche │ │
│ │ (sans-io) │ │ │ │ (sans-io core) │ │
│ │ ┌──────────────┐ │ │ │ │ ┌──────────────┐ │ │
│ │ │ rustls │ │ │ │ │ │ BoringSSL │ │ │
│ │ └──────────────┘ │ │ │ │ └──────────────┘ │ │
│ └────────────────────┘ │ │ └────────────────────┘ │
│ ┌────────────────────┐ │ │ ┌────────────────────┐ │
│ │ quinn-udp │ │ │ │ C FFI layer │ │
│ └────────────────────┘ │ │ └────────────────────┘ │
└──────────────────────────┘ └──────────────────────────┘
功能对比
| 维度 | quinn | quiche |
|---|---|---|
| crate 数 | 3 (quinn, proto, udp) | 1 (单体) |
| 异步支持 | 内置 tokio | 无(sans-io only) |
| TLS 库 | rustls | BoringSSL |
| HTTP/3 | 无(用第三方如 h3 crate) | 内置 |
| C FFI | 无 | 完整 |
| 拥塞控制 | CUBIC, NewReno | CUBIC, NewReno, BBR, BBR2 |
| 多路径 | 不支持 | 不支持 |
| 代码风格 | 惯用 Rust | Rust + C 移植风格 |
| 学习曲线 | 低 | 中高 |
| 生产规模 | 中等 | 超大(Cloudflare CDN) |
| 文档质量 | 优秀 | 良好 |
TLS 后端选择的影响
rustls(quinn 使用):
✓ 纯 Rust,内存安全保证
✓ 不需要 C 编译工具链
✓ 更小的二进制体积
✗ 不支持某些旧的加密算法
✗ 生态成熟度略低于 OpenSSL
BoringSSL(quiche 使用):
✓ Google 维护,经过 Chrome 验证
✓ 支持所有 TLS 特性
✓ 与 Chrome 行为一致
✗ 需要 C/C++ 编译器
✗ 编译慢(需要编译整个 BoringSSL)
✗ 跨平台编译更复杂
选择影响:
如果你的项目是纯 Rust → quinn + rustls
如果需要与 C/C++ 互操作 → quiche + BoringSSL
如果需要在 nginx/curl 等 C 项目中集成 → quiche
性能对比
基准测试结果(典型场景):
批量传输(goodput):
quinn: ~8 Gbps(理想条件)
quiche: ~10 Gbps(理想条件)
差异原因:quiche 的 BoringSSL 有 AES-NI 硬件加速
quinn 的 rustls 也支持但优化程度稍低
连接建立速率:
quinn: ~50K connections/sec
quiche: ~60K connections/sec
差异原因:quiche 的 sans-io 设计零分配更彻底
内存占用(每连接):
quinn: ~20-30 KB
quiche: ~15-25 KB
注意:这些数字高度依赖配置和测试环境
实际差异对大多数应用不显著
sans-io 模式的深入理解
sans-io 是 quinn 和 quiche 共同采用的核心架构模式。让我们更深入地理解它为什么重要:
sans-io 的测试优势
// quinn-proto 的测试示例(简化)
#[test]
fn test_handshake() {
let mut client = Connection::new_client(...);
let mut server = Connection::new_server(...);
// 不需要网络!直接传递字节
let client_initial = client.poll_transmit(now).unwrap();
// 把客户端的包"喂给"服务端
server.handle(now, client_addr, client_initial.data.into());
let server_response = server.poll_transmit(now).unwrap();
// 把服务端的包"喂给"客户端
client.handle(now, server_addr, server_response.data.into());
// 验证握手完成
assert!(client.is_established());
assert!(server.is_established());
}
没有 socket、没有网络、没有异步——就是纯函数调用。这让测试变得极其简单和可靠。
sans-io 的嵌入优势
场景:在嵌入式设备上运行 QUIC
没有 tokio?没有 epoll?只有一个裸金属板子?
用 quinn-proto:
1. 你自己写收发 UDP 的代码(maybe 直接操作网卡寄存器)
2. 你自己写时钟(maybe 用硬件定时器中断)
3. quinn-proto 不关心这些——它只处理字节和时间
伪代码:
loop {
// 你的 I/O
let packet = read_from_nic_register();
// quinn-proto 处理
proto_conn.handle(hardware_timer(), remote_addr, packet);
// 取出要发的
if let Some(transmit) = proto_conn.poll_transmit(hardware_timer()) {
write_to_nic_register(transmit.destination, transmit.data);
}
// 检查超时
if let Some(timeout) = proto_conn.poll_timeout() {
set_hardware_timer(timeout);
}
}
sans-io 的模拟测试
// 可以模拟各种网络条件
struct SimulatedNetwork {
loss_rate: f64,
delay: Duration,
jitter: Duration,
}
impl SimulatedNetwork {
fn transfer(&self, packet: &[u8]) -> Option<(Vec<u8>, Duration)> {
// 模拟丢包
if random::<f64>() < self.loss_rate {
return None; // 丢了
}
// 模拟延迟 + 抖动
let actual_delay = self.delay + random_jitter(self.jitter);
Some((packet.to_vec(), actual_delay))
}
}
// 用模拟网络测试 QUIC 行为
fn test_congestion_under_loss() {
let network = SimulatedNetwork {
loss_rate: 0.05, // 5% 丢包
delay: Duration::from_millis(50),
jitter: Duration::from_millis(10),
};
// 驱动 client/server 通过模拟网络通信
// 验证拥塞控制行为
}
学习建议:如何阅读 quinn 和 quiche 的代码
阅读 quinn 的路径
推荐顺序:
第1步:quinn/examples/
→ 看一个最简单的 client + server 怎么用
→ 建立"用户视角"
第2步:quinn/src/connection.rs
→ 看异步 Connection 如何包装 quinn-proto
→ 理解"胶水层"的角色
第3步:quinn-proto/src/endpoint.rs
→ 看 Endpoint 如何管理多个连接
→ 理解"sans-io 怎么被驱动"
第4步:quinn-proto/src/connection/mod.rs
→ 核心!连接状态机的主逻辑
→ handle() 和 poll_transmit() 的实现
第5步:quinn-proto/src/congestion/
→ 看拥塞控制的 trait 定义和 CUBIC 实现
→ 理解"可插拔"设计
关键文件清单(按重要性排序):
1. quinn-proto/src/connection/mod.rs (连接核心)
2. quinn-proto/src/endpoint.rs (端点管理)
3. quinn-proto/src/connection/streams.rs (流管理)
4. quinn-proto/src/congestion/mod.rs (拥塞控制接口)
5. quinn/src/connection.rs (异步封装)
阅读 quiche 的路径
推荐顺序:
第1步:quiche/apps/src/client.rs
→ 示例客户端的 I/O 循环
→ 理解 quiche 的 API 风格
第2步:quiche/src/lib.rs(重点前 1000 行)
→ Connection 结构体定义
→ recv() 和 send() 的核心逻辑
第3步:quiche/src/recovery/mod.rs
→ 丢包恢复的主逻辑
→ 拥塞控制的调用入口
第4步:quiche/src/h3/mod.rs
→ HTTP/3 层如何建立在 QUIC 之上
→ 理解"分层"的实际效果
第5步:quiche/src/ffi.rs
→ C FFI 是怎么包装 Rust API 的
→ 理解跨语言设计
注意:quiche 的核心逻辑大部分在 lib.rs 一个文件里
文件很大(5000+ 行),但结构清晰(按功能分区)
代码风格对比
错误处理
// quinn-proto: 使用 Rust 的 Result + 自定义错误类型
pub fn handle_initial(
&mut self, packet: Packet
) -> Result<(), TransportError> {
let payload = self.decrypt_initial(&packet)
.map_err(|_| TransportError::PROTOCOL_VIOLATION)?;
// ...
}
// quiche: 使用 Error 枚举 + C 风格的返回码(FFI 友好)
pub fn recv(&mut self, buf: &mut [u8], info: RecvInfo) -> Result<usize> {
// 内部使用 Result
let len = self.do_recv(buf, info)?;
Ok(len)
}
// FFI 层转换为 C 错误码
#[no_mangle]
pub extern fn quiche_conn_recv(...) -> ssize_t {
match conn.recv(buf, info) {
Ok(v) => v as ssize_t,
Err(e) => e.to_c() as ssize_t, // 负数表示错误
}
}
配置方式
// quinn: Builder 模式 + 类型安全
let server_config = ServerConfig::with_crypto(Arc::new(crypto_config));
let endpoint = Endpoint::server(server_config, addr)?;
// 传输参数通过 TransportConfig
let mut transport = TransportConfig::default();
transport.max_idle_timeout(Some(Duration::from_secs(30).try_into()?));
// quiche: 函数调用配置(C FFI 友好)
let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION)?;
config.set_application_protos(&[b"h3"])?;
config.set_max_idle_timeout(30_000);
config.set_initial_max_data(10_000_000);
config.set_cc_algorithm(quiche::CongestionControlAlgorithm::BBR);
本章小结
| 维度 | quinn | quiche |
|---|---|---|
| 定位 | 学习入口 + Rust 嵌入 | 生产部署 + 多语言集成 |
| 架构 | 3-crate 分层 | 单体 + FFI |
| sans-io 目的 | 运行时灵活性 | 跨语言可用性 |
| TLS | rustls(纯 Rust) | BoringSSL(C) |
| HTTP/3 | 无(第三方) | 内置 |
| 适合谁 | Rust 开发者、学习者 | 需要生产级 QUIC 的团队 |
| 社区 | 社区驱动 | 企业(Cloudflare)驱动 |
一句话总结:quinn 是你学习 QUIC 源码的最佳入口——代码少、设计清晰、类型系统即文档。quiche 是你部署生产 QUIC 的首选之一——功能全面、经过 Cloudflare CDN 规模验证、多语言可用。两者都采用 sans-io 架构,但目标受众不同。
读完本章你能做什么
- 解释 sans-io 设计模式的三个好处(可测试、可嵌入、运行时无关)
- 画出 quinn 的三层架构图,标注每层职责
- 说出 quiche 双拥塞控制引擎(Native + Chromium)的设计原因
- 比较 rustls 和 BoringSSL 的优劣
- 给出”选 quinn 还是 quiche”的判断依据
- 描述阅读 quinn 源码的推荐路径
常见误区
误区一:sans-io 意味着不能做网络 I/O
正确理解:sans-io 的意思是”核心库不做 I/O”,不是”整个系统不做 I/O”。I/O 由调用者(或上层包装层)负责。quinn 的顶层 crate 就是一个做 I/O 的包装层——用 tokio 来收发 UDP 包,然后驱动 quinn-proto 这个 sans-io 核心。
误区二:quiche 没有异步支持所以不能用在异步程序中
正确理解:quiche 的 API 是同步的(recv/send 不是 async),但你完全可以在异步程序中使用它。只需要把 quiche 的 recv/send 包装到你的 async I/O 循环中。事实上 Cloudflare 的生产系统就是这么做的——他们有自己的异步框架包装 quiche。
误区三:quinn 和 quiche 是竞争对手
正确理解:它们面向不同的使用场景。quinn 更像一个”Rust 生态的 QUIC 库”——纯 Rust、类型安全、tokio 集成。quiche 更像一个”通用 QUIC 引擎”——C FFI、多语言、生产级。很多人在学习时用 quinn,部署时用 quiche(或者反过来:如果你的项目全是 Rust,quinn 也是很好的生产选择)。
导读目录:README.md 上一章:第 6 章 · 多路径 QUIC——同时走两条路 下一章:第 8 章 · C 语言双雄:msquic 与 ngtcp2——内核级 vs 万能适配