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

第 8 章:C 语言双雄:msquic 与 ngtcp2——内核级 vs 万能适配

从两种建筑方式说起

想象你在城市里看到两种建筑:

msquic 大楼(微软总部):

ngtcp2 大楼(社区共建):

两栋楼都用 C 语言建造(为了最大兼容性),但一栋追求”自己搞定一切”(msquic),另一栋追求”适配一切环境”(ngtcp2)。


msquic:微软的 QUIC 统一引擎

基本信息

项目:msquic
GitHub: microsoft/msquic
Stars: ~4.2K
语言:C
定位:Windows + Linux 跨平台生产 QUIC
架构:分层设计 + 内核态支持
代码量:~700+ 文件(C/C++ 混合)
TLS 后端:Schannel (Windows) / OpenSSL (Linux)
维护者:微软网络团队 (Nick Banks 等)

msquic 的使命

msquic 不是一个实验项目——它是微软所有 QUIC 需求的统一引擎:

msquic 的部署场景:

1. Windows 内核(kernel mode)
   - Windows Server 2022+ 内置 QUIC 支持
   - SMB over QUIC(文件共享走 QUIC)
   - HTTP.sys(Windows 内置 HTTP 服务器)

2. 用户态应用
   - .NET(ASP.NET Core 的 Kestrel 服务器)
   - Microsoft Teams
   - Xbox 游戏网络
   - Azure 各服务

3. Linux 服务器
   - Azure 的 CDN 和负载均衡器
   - 跨平台微服务

一套代码 → 内核态 + 用户态 + Windows + Linux + 嵌入式

msquic 的分层架构

┌─────────────────────────────────────────────────────────────────┐
│                         应用层                                    │
│  .NET / C++ / Rust (via FFI) / PowerShell                       │
├─────────────────────────────────────────────────────────────────┤
│                       msquic API 层                              │
│  MsQuicOpen → Registration → Configuration → Listener/Connection │
├─────────────────────────────────────────────────────────────────┤
│                      协议核心层                                   │
│  ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐          │
│  │ 连接管理 │ │  流管理  │ │ 丢包恢复 │ │ 拥塞控制 │          │
│  └──────────┘ └──────────┘ └──────────┘ └──────────┘          │
├─────────────────────────────────────────────────────────────────┤
│                        TLS 抽象层                                │
│  ┌────────────────┐  ┌────────────────┐  ┌────────────────┐    │
│  │   Schannel     │  │    OpenSSL     │  │   自定义 TLS   │    │
│  │  (Windows)     │  │   (Linux)      │  │   (测试用)     │    │
│  └────────────────┘  └────────────────┘  └────────────────┘    │
├─────────────────────────────────────────────────────────────────┤
│                      平台抽象层 (PAL)                             │
│  ┌────────────────┐  ┌────────────────┐  ┌────────────────┐    │
│  │ Windows User   │  │ Windows Kernel │  │     Linux      │    │
│  │    Mode        │  │    Mode        │  │   (epoll)      │    │
│  └────────────────┘  └────────────────┘  └────────────────┘    │
└─────────────────────────────────────────────────────────────────┘

msquic 的 API 设计:对象模型

msquic 使用一种独特的”对象层次”API 设计:

// msquic 的使用流程

// 1. 打开库(获取函数表)
const QUIC_API_TABLE* MsQuic = NULL;
MsQuicOpen2(&MsQuic);

// 2. 注册应用
HQUIC Registration;
MsQuic->RegistrationOpen(&RegConfig, &Registration);

// 3. 创建配置(安全参数 + 传输参数)
HQUIC Configuration;
MsQuic->ConfigurationOpen(Registration, Alpn, AlpnCount, 
                          &Settings, SettingsSize, NULL, 
                          &Configuration);
MsQuic->ConfigurationLoadCredential(Configuration, &CredConfig);

// 4. 创建监听器(服务端)或连接(客户端)
HQUIC Listener;
MsQuic->ListenerOpen(Registration, ListenerCallback, NULL, &Listener);
MsQuic->ListenerStart(Listener, Alpn, AlpnCount, &Address);

// 5. 在回调中处理事件
QUIC_STATUS ListenerCallback(
    HQUIC Listener, void* Context,
    QUIC_LISTENER_EVENT* Event
) {
    switch (Event->Type) {
        case QUIC_LISTENER_EVENT_NEW_CONNECTION:
            // 新连接到达
            MsQuic->ConnectionSetConfiguration(
                Event->NEW_CONNECTION.Connection, Configuration);
            break;
    }
    return QUIC_STATUS_SUCCESS;
}

关键特点:事件驱动 + 回调模型。msquic 不使用 sans-io 模式——它内部管理所有 I/O,通过回调通知应用层发生了什么。

为什么 msquic 不用 sans-io?

msquic 的设计决策:

sans-io 适合的场景:
  - 库开发者想让用户自由选择 I/O 模型
  - 主要面向 Rust/Python 等有强异步生态的语言
  
msquic 的场景不同:
  - 需要在 Windows 内核态运行(内核没有"你来做 I/O"的自由)
  - 需要统一管理线程池(内核态线程是共享资源)
  - 需要在各种平台上提供一致的行为
  - 性能优化需要控制 I/O 路径(比如 io_uring on Linux)

所以 msquic 选择了"我全包"模式:
  - 内部管理线程池
  - 内部管理 socket
  - 内部管理 I/O 完成通知
  - 通过回调通知应用层

好处:应用代码极简,性能优化集中
坏处:灵活性低,不能自定义 I/O 模型

msquic 的内核态支持

msquic 最独特的能力:它可以在 Windows 内核态运行。这意味着什么?

用户态 QUIC(普通模式):
  应用层 → 系统调用 → 内核收包 → 拷贝到用户空间 → 解密处理

内核态 QUIC(msquic kernel mode):
  内核直接收包 → 解密处理 → 零拷贝传递给应用
  
                     用户态         │     内核态
                                    │
  ┌──────────┐    ┌──────────┐     │   ┌──────────┐
  │ 应用程序 │    │  msquic  │     │   │  msquic  │
  │          │◄──►│  (user)  │     │   │ (kernel) │
  └──────────┘    └──────────┘     │   └────┬─────┘
                       │            │        │
                       ▼            │        ▼
                ┌─────────────┐    │   ┌─────────────┐
                │   kernel    │    │   │   NIC 驱动   │
                │  (syscall)  │    │   │  (直接访问)  │
                └─────────────┘    │   └─────────────┘
                                    │
  延迟:~10μs 系统调用开销          │   延迟:~0μs(同在内核)
  吞吐:受限于用户/内核拷贝         │   吞吐:零拷贝,极高

使用场景:
  - SMB over QUIC(Windows 文件共享)
  - HTTP.sys(IIS/Kestrel 的内核 HTTP 栈)
  - Azure 负载均衡器(每秒百万连接级别)

msquic 的安全保障

作为微软的核心网络库,msquic 有极其严格的安全要求:

安全措施:

1. 持续 Fuzzing
   - OSS-Fuzz 集成(Google 的持续模糊测试服务)
   - 每天数十亿次随机输入测试
   - 覆盖所有解析路径

2. 静态分析
   - SAL 注解(Microsoft 的静态注解语言)
   - 每个指针标注 _In_, _Out_, _In_opt_ 等
   - 编译器自动检查缓冲区溢出风险

3. 内存安全
   - 自定义内存池(避免碎片和 use-after-free)
   - ASAN/MSAN 集成测试
   - Windows Driver Verifier 检查(内核态)

4. 形式化验证
   - 部分关键路径使用 Dafny 验证
   - 状态机转换有数学证明

5. 代码审查
   - 所有变更需要至少 2 人审查
   - 安全相关变更需要安全团队审查

msquic 的代码结构

msquic/
├── src/
│   ├── core/                ← 协议核心
│   │   ├── connection.c     ← 连接管理
│   │   ├── stream.c         ← 流管理
│   │   ├── loss_detection.c ← 丢包检测
│   │   ├── congestion_control.c ← 拥塞控制
│   │   ├── crypto.c         ← 加密帧处理
│   │   ├── packet.c         ← 包构造/解析
│   │   └── ...
│   │
│   ├── platform/            ← 平台抽象层 (PAL)
│   │   ├── platform_winuser.c  ← Windows 用户态
│   │   ├── platform_winkernel.c ← Windows 内核态
│   │   ├── platform_linux.c    ← Linux
│   │   ├── datapath_winuser.c  ← Windows UDP
│   │   ├── datapath_linux.c    ← Linux UDP (epoll)
│   │   └── ...
│   │
│   ├── tls/                 ← TLS 抽象
│   │   ├── tls_schannel.c   ← Windows Schannel
│   │   ├── tls_openssl.c    ← OpenSSL
│   │   └── ...
│   │
│   ├── bin/                 ← 可执行文件
│   │   ├── quicreach/       ← 连接测试工具
│   │   └── ...
│   │
│   └── generated/           ← 自动生成的代码
│
├── inc/                     ← 公共头文件
│   ├── msquic.h             ← 主 API 头文件
│   └── msquic_posix.h       ← POSIX 平台定义
│
├── submodules/              ← 依赖
│   └── openssl/
│
└── scripts/                 ← 构建和测试脚本

msquic 的性能优化

关键优化技术:

1. RSS(Receive Side Scaling)
   - 多核 CPU 并行处理收包
   - 每个核一个 I/O 队列
   - 避免核间通信开销

2. 批量发送
   - Windows: WSASendMsg 批量
   - Linux: sendmmsg 一次发多个包
   - 减少系统调用次数

3. io_uring 支持(Linux 5.1+)
   - 异步 I/O,避免 epoll 的 syscall 开销
   - 批量提交和完成
   - 零拷贝发送

4. 连接 ID 路由
   - 服务端可以基于 Connection ID 路由到特定核
   - 避免跨核锁争用
   - 支持多核扩展

5. 内存池
   - 预分配常用大小的内存块
   - 避免运行时 malloc/free
   - 减少内存碎片

性能数据(微软内部测试):
  - 单核吞吐:~5 Gbps
  - 多核吞吐:~100 Gbps (128 核)
  - 连接建立:~100K connections/sec/core
  - 内存占用:~8 KB/idle connection

ngtcp2:万能适配器

基本信息

项目:ngtcp2
GitHub: ngtcp2/ngtcp2
Stars: ~900
语言:C
定位:可嵌入的 QUIC 库 + TLS 库中立
架构:回调式 sans-io
代码量:~100 文件(核心)
TLS 后端:可选 OpenSSL / GnuTLS / wolfSSL / BoringSSL / Picotls
维护者:Tatsuhiro Tsujikawa(一人维护)
特色:nghttp2 的成功设计经验的延续

ngtcp2 的背景:nghttp2 的成功

ngtcp2 的作者 Tatsuhiro Tsujikawa 此前创建了 nghttp2——目前最广泛使用的 HTTP/2 C 库:

nghttp2 的成功:
  - curl 的默认 HTTP/2 后端
  - Apache httpd 的 HTTP/2 模块 (mod_http2)
  - Python 的 hyper/h2 参考了它的设计
  - 几乎所有 Linux 发行版都内置

ngtcp2 继承的设计原则:
  1. TLS 库中立(不绑定任何一个 TLS 库)
  2. C 语言(最大可移植性)
  3. 回调式 API(灵活,嵌入友好)
  4. 一个人维护但质量极高
  5. 优秀的文档和示例

ngtcp2 的 TLS 中立设计

ngtcp2 最大的特色:它不绑定任何 TLS 库。

为什么这很重要?

不同环境有不同的 TLS 需求:
  - 企业环境:必须用 OpenSSL(合规要求)
  - 嵌入式:必须用 wolfSSL(体积小)
  - Apple 平台:可能想用 BoringSSL
  - GPL 项目:可能需要 GnuTLS(许可证兼容)
  - 前沿研究:可能用 Picotls(Fastly 的实验 TLS)

ngtcp2 的解决方案:TLS 回调接口
  ┌─────────────────────────────────────────────────┐
  │                  ngtcp2 核心                      │
  │  ┌────────────────────────────────────────────┐ │
  │  │           TLS 回调接口                      │ │
  │  │  - client_initial()                        │ │
  │  │  - recv_crypto_data()                      │ │
  │  │  - encrypt()                               │ │
  │  │  - decrypt()                               │ │
  │  │  - hp_mask() (Header Protection)           │ │
  │  └─────────────────┬──────────────────────────┘ │
  └─────────────────────┼──────────────────────────────┘
                        │ 由适配层实现
        ┌───────────────┼───────────────────┐
        │               │                   │
  ┌─────▼─────┐  ┌─────▼─────┐  ┌─────────▼─────────┐
  │  OpenSSL  │  │  GnuTLS   │  │  wolfSSL/BoringSSL │
  │  adapter  │  │  adapter  │  │     adapter        │
  └───────────┘  └───────────┘  └─────────────────────┘

ngtcp2 的回调式 API

ngtcp2 使用回调来通知应用层各种事件:

// ngtcp2 的使用方式

// 1. 设置回调函数表
ngtcp2_callbacks callbacks = {
    .client_initial = client_initial_cb,
    .recv_crypto_data = recv_crypto_data_cb,
    .encrypt = encrypt_cb,
    .decrypt = decrypt_cb,
    .hp_mask = hp_mask_cb,
    .recv_stream_data = recv_stream_data_cb,
    .acked_stream_data_offset = acked_stream_data_offset_cb,
    .stream_open = stream_open_cb,
    .stream_close = stream_close_cb,
    .rand = rand_cb,
    .get_new_connection_id = get_new_connection_id_cb,
    // ... 更多回调
};

// 2. 创建连接
ngtcp2_conn *conn;
ngtcp2_conn_client_new(&conn, &dcid, &scid, &path, version,
                       &callbacks, &settings, &params, 
                       NULL, user_data);

// 3. I/O 循环
for (;;) {
    // 读取 UDP 数据
    nread = recvfrom(fd, buf, sizeof(buf), 0, ...);
    
    // 喂给 ngtcp2(类似 sans-io 的 input 端)
    ngtcp2_conn_read_pkt(conn, &path, &pi, buf, nread, timestamp);
    
    // 从 ngtcp2 获取要发送的数据
    nwrite = ngtcp2_conn_write_pkt(conn, &path, &pi, 
                                    buf, sizeof(buf), timestamp);
    if (nwrite > 0) {
        sendto(fd, buf, nwrite, 0, ...);
    }
    
    // 处理超时
    expiry = ngtcp2_conn_get_expiry(conn);
    // 设置定时器...
}

注意:ngtcp2 的 API 风格是”回调 + 轮询”混合。核心逻辑不做 I/O(类似 sans-io),但通过回调把部分控制交给应用层。

ngtcp2 的流数据处理

// 发送流数据
ngtcp2_ssize nwrite;
ngtcp2_vec datav = {data, datalen};

// write_stream 同时做"写流数据"和"生成 QUIC 包"
nwrite = ngtcp2_conn_writev_stream(
    conn, &path, &pi,
    buf, sizeof(buf),        // 输出缓冲区
    &ndatalen,               // 实际写入的流数据长度
    NGTCP2_WRITE_STREAM_FLAG_NONE,
    stream_id,               // 目标流 ID
    &datav, 1,               // 要写的数据
    timestamp
);
// nwrite = QUIC 包的总长度(包含头部、加密等)
// ndatalen = 实际被打包进去的流数据长度

// 接收流数据(通过回调)
int recv_stream_data_cb(
    ngtcp2_conn *conn,
    uint32_t flags,
    int64_t stream_id,
    uint64_t offset,
    const uint8_t *data,
    size_t datalen,
    void *user_data,
    void *stream_user_data
) {
    // 应用层处理收到的流数据
    printf("Stream %ld received %zu bytes\n", stream_id, datalen);
    // 返回 0 表示成功
    return 0;
}

ngtcp2 与 nghttp3:分层组合

ngtcp2 只做 QUIC 层,HTTP/3 由独立的 nghttp3 库实现:

┌─────────────────────┐
│      应用层          │
├─────────────────────┤
│     nghttp3         │  ← HTTP/3 帧处理
│  (独立库)           │
├─────────────────────┤
│     ngtcp2          │  ← QUIC 协议
│  (核心库)           │
├─────────────────────┤
│  TLS adapter        │  ← 可选 TLS 后端
│  (OpenSSL/...)      │
├─────────────────────┤
│    UDP socket       │  ← 应用负责
└─────────────────────┘

优点:
  - 各层独立升级
  - 不需要 HTTP/3 时不链接 nghttp3
  - nghttp3 也可以用在其他 QUIC 库之上

这和 quinn 的理念类似:
  quinn 没有内置 HTTP/3 → 用第三方 h3 crate
  ngtcp2 没有内置 HTTP/3 → 用 nghttp3

ngtcp2 在 curl 中的应用

curl 是最广泛使用的 HTTP 客户端工具,它使用 ngtcp2 作为 QUIC 后端之一:

curl 的 HTTP/3 后端选择:

  curl --with-ngtcp2     → ngtcp2 + nghttp3
  curl --with-quiche     → quiche (内含 HTTP/3)
  curl --with-msh3       → msquic + msh3

为什么 curl 选择 ngtcp2 作为首选:
  1. TLS 中立:curl 支持 12+ 种 TLS 库,ngtcp2 是唯一能适配它们的
  2. C 语言:curl 本身是 C,零语言隔阂
  3. 设计理念一致:都是"最小依赖、最大兼容"
  4. nghttp2 的成功先例:curl 与 nghttp2 已合作多年
  
使用关系:
  curl → libcurl → nghttp3 → ngtcp2 → OpenSSL/GnuTLS/wolfSSL

ngtcp2 的代码结构

ngtcp2/
├── lib/                     ← 核心库
│   ├── ngtcp2_conn.c        ← 连接主逻辑(最大文件)
│   ├── ngtcp2_conn.h
│   ├── ngtcp2_pkt.c         ← 包解析/构造
│   ├── ngtcp2_crypto.c      ← 加密抽象
│   ├── ngtcp2_strm.c        ← 流管理
│   ├── ngtcp2_rtb.c         ← 重传缓冲区
│   ├── ngtcp2_cc.c          ← 拥塞控制
│   ├── ngtcp2_bbr.c         ← BBR 实现
│   ├── ngtcp2_bbr2.c        ← BBR2 实现
│   └── ...
│
├── crypto/                  ← TLS 适配层
│   ├── openssl/             ← OpenSSL 适配
│   ├── gnutls/              ← GnuTLS 适配
│   ├── wolfssl/             ← wolfSSL 适配
│   ├── boringssl/           ← BoringSSL 适配
│   └── shared.c             ← 共享加密工具
│
├── examples/                ← 示例程序
│   ├── client.cc            ← C++ 示例客户端
│   ├── server.cc            ← C++ 示例服务端
│   └── ...
│
├── tests/                   ← 测试
│   └── ngtcp2_conn_test.c
│
└── doc/                     ← API 文档

文件数量比 msquic 少很多,但每个文件都很”密实”——ngtcp2_conn.c 单文件就有数千行,包含了大部分连接逻辑。


msquic vs ngtcp2:详细对比

设计哲学对比

维度 msquic ngtcp2
开发团队 微软团队(10+ 人) 一人维护
代码量 ~700 文件 ~100 文件
I/O 模型 全包(内部管理) 回调式 sans-io
TLS 后端 Schannel / OpenSSL 5 种可选
HTTP/3 无(用 msh3) 无(用 nghttp3)
平台 Windows + Linux POSIX + Windows
内核态 支持 不支持
多语言 C API + .NET / Rust 绑定 纯 C API
安全审计 极严格(fuzzing + SAL) 作者自审 + 社区

API 风格对比

// msquic: 对象模型 + 事件回调
// 特点:申请对象 → 设置回调 → 启动 → 在回调中处理事件
MsQuic->ConnectionOpen(Registration, ConnectionCallback, NULL, &Connection);
MsQuic->ConnectionStart(Connection, Configuration, AF_UNSPEC, "example.com", 443);
// 之后一切发生在 ConnectionCallback 里

// ngtcp2: 轮询 + 回调混合
// 特点:应用主循环调用 read_pkt/write_pkt,部分事件通过回调通知
ngtcp2_conn_read_pkt(conn, &path, &pi, buf, nread, ts);
nwrite = ngtcp2_conn_write_pkt(conn, &path, &pi, out, outlen, ts);
// 流数据等通过回调获取

线程模型对比

msquic:
  - 内部维护线程池
  - 每个 CPU 核一个 worker 线程
  - 连接固定绑定到特定核
  - 回调在 worker 线程中执行
  - 应用几乎不需要考虑线程问题

  ┌─────────┐ ┌─────────┐ ┌─────────┐
  │ Worker 0│ │ Worker 1│ │ Worker N│
  │  CPU 0  │ │  CPU 1  │ │  CPU N  │
  │ Conn A  │ │ Conn B  │ │ Conn Z  │
  │ Conn C  │ │ Conn D  │ │         │
  └─────────┘ └─────────┘ └─────────┘
  
ngtcp2:
  - 不管理线程
  - 应用自己决定线程模型
  - 可以单线程(简单场景)
  - 可以每连接一线程
  - 可以用线程池 + epoll
  
  ┌─────────────────────────────────┐
  │        应用的线程模型            │
  │  (ngtcp2 不关心你怎么组织)      │
  │                                 │
  │  可以是 event loop             │
  │  可以是 thread-per-connection  │
  │  可以是 async runtime          │
  └─────────────────────────────────┘

拥塞控制对比

msquic 的拥塞控制:
  - CUBIC(默认)
  - BBR(实验性)
  - 自定义接口(可插入新算法)
  
  特点:
  - 针对 Windows 网络栈优化
  - 与 RSS 联动(每核独立 CC 状态)
  - 支持 HyStart++

ngtcp2 的拥塞控制:
  - NewReno
  - CUBIC
  - BBR
  - BBR2
  
  特点:
  - 实现严格遵循 RFC/论文
  - 代码清晰,适合参考
  - BBR2 是少数实现了最新版本的库之一

连接状态机对比

msquic 的连接状态

msquic 连接状态机(简化):

  ┌───────────┐
  │ Allocated │ ← ConnectionOpen() 创建
  └─────┬─────┘
        │ ConnectionStart() / 收到 Initial
        ▼
  ┌───────────┐
  │ Handshake │ ← TLS 握手进行中
  │  (client/ │
  │   server) │
  └─────┬─────┘
        │ 握手完成
        ▼
  ┌───────────┐
  │ Connected │ ← 正常数据传输
  │           │   可以开流、发数据
  └─────┬─────┘
        │ 超时 / 收到 CONNECTION_CLOSE / 应用关闭
        ▼
  ┌───────────┐
  │ Shutdown  │ ← 发送 CONNECTION_CLOSE
  │ Pending   │   等待 ACK
  └─────┬─────┘
        │ 确认或超时
        ▼
  ┌───────────┐
  │  Closed   │ ← 资源释放
  └───────────┘

msquic 通过回调通知状态变化:
  QUIC_CONNECTION_EVENT_CONNECTED        → 握手完成
  QUIC_CONNECTION_EVENT_SHUTDOWN_INITIATED_BY_TRANSPORT → 传输层关闭
  QUIC_CONNECTION_EVENT_SHUTDOWN_INITIATED_BY_PEER     → 对端关闭
  QUIC_CONNECTION_EVENT_SHUTDOWN_COMPLETE              → 完全关闭

ngtcp2 的连接状态

ngtcp2 连接状态(通过 API 查询):

  ngtcp2_conn_get_handshake_completed(conn)
    → 0 = 握手中, 1 = 握手完成
  
  ngtcp2_conn_in_closing_period(conn)
    → 正在关闭(发送了 CONNECTION_CLOSE,等待排空)
  
  ngtcp2_conn_in_draining_period(conn)
    → 排空期(收到了对端的 CONNECTION_CLOSE,静默等待)

ngtcp2 不用 enum 表示状态,而是用一系列布尔查询函数
  → 更灵活但不如 enum 直观
  → 应用需要自己追踪完整状态

在 TQUIC 上下文中的意义

理解 msquic 和 ngtcp2 对学习 TQUIC 有什么帮助?

msquic 对 TQUIC 的参考价值:
  1. 内部线程池管理 → TQUIC 也有类似设计
  2. 平台抽象层 → TQUIC 也需要跨平台
  3. 性能优化技术(RSS, io_uring)→ TQUIC 可借鉴
  4. 内核态可能性 → 学术讨论的方向

ngtcp2 对 TQUIC 的参考价值:
  1. TLS 中立设计 → TQUIC 的 TLS trait 抽象
  2. 拥塞控制算法实现 → BBR2 的参考实现
  3. 一人维护的代码质量 → 代码简洁性的标杆
  4. 与 curl 的集成方式 → 如何设计嵌入式 API

TQUIC 的定位对比:
  msquic → 微软生态的 QUIC
  ngtcp2 → 万能适配的 QUIC  
  TQUIC  → 多路径 + 高性能的 QUIC(腾讯)
  
  TQUIC 独特之处:
  - 多路径是一等公民(msquic/ngtcp2 都不支持)
  - Rust 实现但有 C/FFI + 移动端支持
  - 拥塞控制可插拔且支持 per-path

构建和体验

构建 msquic

# 克隆(包含子模块)
git clone --recursive https://github.com/microsoft/msquic.git
cd msquic

# Linux 构建
mkdir build && cd build
cmake -G 'Unix Makefiles' ..
cmake --build .

# 运行示例
./bin/Release/quicsample -server -cert_file:cert.pem -key_file:key.pem
./bin/Release/quicsample -client -target:localhost

# 注意:msquic 的构建比较重
# 需要 CMake 3.16+, Perl (for OpenSSL), 以及较新的 GCC/Clang

构建 ngtcp2

# 克隆
git clone https://github.com/ngtcp2/ngtcp2.git
cd ngtcp2
git submodule update --init

# 使用 OpenSSL 构建
autoreconf -i
./configure --with-openssl
make
make check  # 运行测试

# 运行示例
./examples/server 0.0.0.0 4433 cert.pem key.pem
./examples/client 127.0.0.1 4433

# ngtcp2 的构建相对简单
# 主要依赖:autotools, 选择的 TLS 库

对比总结表

维度 msquic ngtcp2
一句话定位 微软全平台 QUIC 引擎 万能 TLS 适配的轻量 QUIC
代码量 大(700+ 文件) 小(100 文件)
开发团队 微软团队 一人(Tatsuhiro Tsujikawa)
I/O 模型 内部全管理 应用自行管理
TLS Schannel + OpenSSL 5 种可选
内核态 支持 不支持
拥塞控制 CUBIC, BBR NewReno, CUBIC, BBR, BBR2
学习价值 性能优化、平台抽象 API 设计、拥塞控制实现
适合场景 Windows 生态、超大规模 curl 等工具、多 TLS 环境
对 TQUIC 性能优化参考 BBR2 实现参考、API 设计参考

读完本章你能做什么

  1. 解释 msquic 为什么不用 sans-io 模式(内核态需求 + 全管理哲学)
  2. 描述 ngtcp2 的 TLS 中立设计如何工作(回调接口 + 适配层)
  3. 比较”对象模型 + 回调”(msquic)和”轮询 + 回调”(ngtcp2)两种 API 风格
  4. 说出 msquic 内核态运行的性能优势(零拷贝、零系统调用开销)
  5. 解释 ngtcp2 在 curl 中的角色和为什么被选为首选后端
  6. 将 msquic 和 ngtcp2 的设计特点与 TQUIC 关联

常见误区

误区一:msquic 只能在 Windows 上运行

正确理解:msquic 从一开始就是跨平台设计。它在 Linux 上有完整的支持(使用 OpenSSL 作为 TLS 后端,使用 epoll/io_uring 做 I/O)。Azure 的很多 Linux 服务都在用 msquic。”Windows 内核态”只是它的额外能力,不是唯一运行方式。

误区二:ngtcp2 因为一个人维护所以不可靠

正确理解:Tatsuhiro Tsujikawa 也是 nghttp2 的作者——那个库被 curl、Apache、几乎所有 Linux 发行版使用。一人维护不等于低质量——反而因为风格统一、设计一致,代码质量极高。curl 的创建者 Daniel Stenberg 多次公开赞扬 ngtcp2 的代码质量。但确实有”bus factor”风险(如果作者不能继续维护怎么办)。

误区三:C 语言写网络库不安全

正确理解:C 语言确实没有 Rust 的内存安全保证,但 msquic 通过 SAL 注解 + 持续 fuzzing + ASAN 来弥补。ngtcp2 通过精心的编码规范和测试来保证。关键不是语言本身,而是开发实践。用 C 的好处是:最大的跨平台兼容性、最小的运行时开销、最广的语言互操作性。这就是为什么 TQUIC 选择了 Rust(安全性)但提供 C FFI(兼容性)——两者兼顾。


导读目录:README.md 上一章:第 7 章 · 社区标杆:quinn 与 quiche——极简 vs 全能 下一章:第 9 章 · 安全与性能:s2n-quic——AWS 的 Rust 工程标杆