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

第 10 章:深入 TQUIC——腾讯的多路径 QUIC 引擎

从快递公司说起

想象一家”下一代快递公司”(TQUIC),它有几个与众不同的特点:

多线路并发配送

调度员可以换

每条路有独立管理员

这就是 TQUIC 的核心竞争力:多路径是一等公民,不是后加的补丁


TQUIC 基本信息

项目:TQUIC
GitHub: Tencent/tquic
Stars: ~1K
语言:Rust (核心) + C FFI
定位:高性能多路径 QUIC,移动端优化
架构:sans-io + 可插拔组件
代码量:~250 文件
TLS 后端:BoringSSL
维护者:腾讯网络平台部
RFC 合规:RFC 9000 + Multipath QUIC draft
上线环境:微信、QQ、腾讯云 CDN、腾讯游戏

TQUIC 的诞生背景

腾讯的网络挑战:

1. 移动端双栈
   - 手机同时有 WiFi 和蜂窝网络
   - 用户在地铁中从 WiFi 切到 4G,连接不能断
   - 最好能同时用两条路提高速度

2. 高密度场景
   - 微信红包:除夕夜数十亿条消息
   - QQ 音视频:实时性要求极高
   - 腾讯游戏:延迟 > 50ms 就影响体验

3. 网络质量差异大
   - 中国 WiFi 经常丢包率 > 5%
   - 4G 在高铁上抖动严重
   - 需要在恶劣条件下保证体验

TQUIC 的目标:
  用多路径解决 → 一条路不行时另一条补上
  用可插拔设计 → 不同场景用不同策略
  用 Rust → 性能接近 C,安全性远超 C

TQUIC 的整体架构

┌──────────────────────────────────────────────────────────────┐
│                        应用层                                  │
│  C FFI / iOS SDK / Android SDK / Rust native                  │
├──────────────────────────────────────────────────────────────┤
│                      TQUIC API 层                              │
│  Connection / Stream / Endpoint                               │
├──────────────────────────────────────────────────────────────┤
│                     多路径调度层                               │
│  ┌──────────────────────────────────────────────────────┐    │
│  │            MultipathScheduler trait                    │    │
│  │  ┌─────────┐  ┌───────────┐  ┌──────────────┐       │    │
│  │  │ MinRtt  │  │ Redundant │  │ RoundRobin   │       │    │
│  │  │ (最快路)│  │ (全冗余) │  │ (轮询)       │       │    │
│  │  └─────────┘  └───────────┘  └──────────────┘       │    │
│  └──────────────────────────────────────────────────────┘    │
├──────────────────────────────────────────────────────────────┤
│                      路径管理层                                │
│  ┌──────────────────────────────────────────────────────┐    │
│  │                  PathMap                               │    │
│  │  ┌────────┐  ┌────────┐  ┌────────┐                 │    │
│  │  │ Path 0 │  │ Path 1 │  │ Path N │  ...            │    │
│  │  │ WiFi   │  │  4G    │  │  5G    │                 │    │
│  │  │ CC: BBR│  │CC:CUBIC│  │CC:BBR3 │                 │    │
│  │  │ RTT:20 │  │ RTT:80 │  │ RTT:10 │                 │    │
│  │  └────────┘  └────────┘  └────────┘                 │    │
│  └──────────────────────────────────────────────────────┘    │
├──────────────────────────────────────────────────────────────┤
│                     协议核心层                                 │
│  连接管理 / 流管理 / 帧处理 / 丢包恢复                        │
├──────────────────────────────────────────────────────────────┤
│                      TLS 层                                    │
│  BoringSSL (TLS 1.3)                                          │
├──────────────────────────────────────────────────────────────┤
│                     平台 I/O 层                               │
│  UDP socket (sans-io: 应用负责实际收发)                       │
└──────────────────────────────────────────────────────────────┘

MultipathScheduler:TQUIC 的核心创新

trait 定义

// MultipathScheduler trait(简化自 TQUIC 源码)
pub trait MultipathScheduler: Send + Sync {
    /// 选择下一个要发送数据的路径
    /// 
    /// 参数:
    /// - paths: 所有可用路径的信息
    /// - frame: 要发送的数据帧信息
    ///
    /// 返回:选中的路径 ID,或 None(所有路径都不可用)
    fn on_select_path(
        &mut self,
        paths: &PathMap,
        frame: &FrameInfo,
    ) -> Option<PathId>;
    
    /// 当路径状态变化时通知调度器
    fn on_path_updated(&mut self, path_id: PathId, event: PathEvent);
    
    /// 当数据被确认时通知调度器
    fn on_ack(&mut self, path_id: PathId, acked_bytes: usize);
    
    /// 当数据丢失时通知调度器  
    fn on_loss(&mut self, path_id: PathId, lost_bytes: usize);
}

三种内置调度器

MinRtt 调度器

// MinRtt:选择 RTT 最小的路径
// 适用场景:延迟敏感(游戏、音视频)
pub struct MinRttScheduler;

impl MultipathScheduler for MinRttScheduler {
    fn on_select_path(
        &mut self,
        paths: &PathMap,
        _frame: &FrameInfo,
    ) -> Option<PathId> {
        paths.iter()
            .filter(|p| p.is_active() && p.can_send())
            .min_by_key(|p| p.rtt())     // ← 选 RTT 最小的
            .map(|p| p.id())
    }
}

// 工作原理:
// 
//   Path 0 (WiFi):  RTT = 20ms, 可用 ✓  ← 选这条!
//   Path 1 (4G):    RTT = 80ms, 可用 ✓
//   Path 2 (5G):    RTT = 10ms, 不可用 ✗(窗口满了)
//
// 每次要发数据时:看看谁最快且还能发 → 选它
// 
// 优点:延迟最优
// 缺点:可能让快路径过载,慢路径闲置

Redundant 调度器

// Redundant:在所有可用路径上发送同样的数据
// 适用场景:可靠性极高要求(关键控制消息)
pub struct RedundantScheduler;

impl MultipathScheduler for RedundantScheduler {
    fn on_select_path(
        &mut self,
        paths: &PathMap,
        _frame: &FrameInfo,
    ) -> Option<PathId> {
        // 返回所有可用路径(实际实现中会发送到每条路径)
        // 简化:这里返回第一条,但会标记"需要冗余"
        paths.iter()
            .filter(|p| p.is_active() && p.can_send())
            .next()
            .map(|p| p.id())
    }
    
    fn should_replicate(&self) -> bool {
        true  // 标记:数据需要在所有路径上复制
    }
}

// 工作原理:
//
//   要发送数据 "Hello"
//   Path 0 (WiFi): 发送 "Hello"  ←
//   Path 1 (4G):   发送 "Hello"  ←  同样的数据!
//
//   收端:谁先到用谁,重复的丢弃
//
// 优点:只要有一条路通,数据就能到达
// 缺点:带宽利用率低(每条路都在传相同数据)
// 适合:小数据量但极高可靠性(如游戏的操作指令)

RoundRobin 调度器

// RoundRobin:轮流在各路径上发送
// 适用场景:带宽最大化(大文件下载)
pub struct RoundRobinScheduler {
    next_path_index: usize,
}

impl MultipathScheduler for RoundRobinScheduler {
    fn on_select_path(
        &mut self,
        paths: &PathMap,
        _frame: &FrameInfo,
    ) -> Option<PathId> {
        let active_paths: Vec<_> = paths.iter()
            .filter(|p| p.is_active() && p.can_send())
            .collect();
        
        if active_paths.is_empty() {
            return None;
        }
        
        // 轮流选择
        let selected = &active_paths[self.next_path_index % active_paths.len()];
        self.next_path_index += 1;
        Some(selected.id())
    }
}

// 工作原理:
//
//   数据块 1 → Path 0 (WiFi)
//   数据块 2 → Path 1 (4G)
//   数据块 3 → Path 0 (WiFi)
//   数据块 4 → Path 1 (4G)
//   ...
//
// 优点:简单,带宽利用率高
// 缺点:如果两条路 RTT 差异大,接收端需要等慢路径的数据
//       → 导致"头部阻塞"(快路径的数据到了但要等慢路径的才能交付)

调度器的选择策略

场景分析:

┌───────────────────────────────────────────────────────┐
│ 场景               │ 推荐调度器  │ 原因               │
├───────────────────────────────────────────────────────┤
│ 游戏实时对战       │ MinRtt     │ 延迟是第一优先      │
│ 音视频通话         │ MinRtt     │ 卡顿比码率更重要    │
│ 关键控制消息       │ Redundant  │ 必须到达            │
│ 大文件下载         │ RoundRobin │ 总带宽最大化        │
│ 视频流播放         │ 自定义*    │ 需要平衡延迟和带宽  │
│ 弱网环境           │ Redundant  │ 保证可用性          │
└───────────────────────────────────────────────────────┘

*自定义调度器示例:加权 RoundRobin
  - 根据每条路径的带宽比例分配数据量
  - WiFi 带宽 100Mbps, 4G 带宽 30Mbps
  - 那 WiFi 分 77% 的数据,4G 分 23%
  - 这就是犀牛鸟竞赛可以做的方向!

PathMap:路径管理的核心数据结构

PathMap 的设计

// PathMap 管理所有路径(简化)
pub struct PathMap {
    /// 所有路径
    paths: Vec<Path>,
    /// 当前活跃路径的数量
    active_count: usize,
    /// 主路径 ID
    primary_path: PathId,
}

// 单条路径的完整信息
pub struct Path {
    /// 路径唯一标识
    id: PathId,
    /// 四元组(本地地址:端口 + 远端地址:端口)
    local_addr: SocketAddr,
    remote_addr: SocketAddr,
    /// 路径状态
    state: PathState,
    /// RTT 估计器
    rtt_estimator: RttEstimator,
    /// 独立的拥塞控制器
    congestion_controller: Box<dyn CongestionController>,
    /// 独立的丢包恢复
    recovery: Recovery,
    /// 路径 MTU
    mtu: u16,
    /// 路径验证 token
    validation: PathValidation,
    /// 统计信息
    stats: PathStats,
}

#[derive(Clone, Copy)]
pub enum PathState {
    /// 新创建,未验证
    New,
    /// 正在验证(PATH_CHALLENGE 已发)
    Validating,
    /// 已验证,可用
    Active,
    /// 暂时不可用(例如检测到严重丢包)
    Degraded,
    /// 已关闭
    Closed,
}

PathMap 的操作

impl PathMap {
    /// 添加新路径
    pub fn add_path(&mut self, local: SocketAddr, remote: SocketAddr, 
                    cc: Box<dyn CongestionController>) -> PathId {
        let id = PathId(self.paths.len());
        self.paths.push(Path {
            id,
            local_addr: local,
            remote_addr: remote,
            state: PathState::New,
            rtt_estimator: RttEstimator::new(),
            congestion_controller: cc,
            recovery: Recovery::new(),
            mtu: 1200,  // 初始 MTU
            validation: PathValidation::new(),
            stats: PathStats::default(),
        });
        // 自动发起路径验证
        self.initiate_validation(id);
        id
    }
    
    /// 获取所有活跃路径
    pub fn active_paths(&self) -> impl Iterator<Item = &Path> {
        self.paths.iter().filter(|p| p.state == PathState::Active)
    }
    
    /// 路径上报丢包
    pub fn on_path_loss(&mut self, path_id: PathId, lost_bytes: usize) {
        let path = &mut self.paths[path_id.0];
        path.congestion_controller.on_loss(lost_bytes, Instant::now());
        path.stats.bytes_lost += lost_bytes as u64;
        
        // 如果丢包率过高,标记路径为降级
        if path.stats.loss_rate() > 0.30 {
            path.state = PathState::Degraded;
            self.active_count -= 1;
        }
    }
}

Per-Path 拥塞控制

为什么每条路径需要独立的拥塞控制

场景:手机同时连 WiFi 和 4G

如果共享一个拥塞控制器:
  WiFi 丢包 → 整体窗口减小 → 4G 也被限制了
  → 4G 明明没堵,却不能全速发送
  → 浪费带宽

如果每路径独立拥塞控制:
  WiFi 丢包 → WiFi 窗口减小 → 4G 不受影响
  → 4G 继续全速
  → 总带宽 = WiFi 当前带宽 + 4G 全速带宽

                 共享 CC                    Per-path CC
            
  WiFi: ████████░░░░                WiFi: ████████░░░░
  4G:   ████░░░░░░░░  (被连累)      4G:   ████████████  (独立运行)
  总计: 12 单位                      总计: 20 单位  ← 多出 67%!

TQUIC 的 per-path CC 实现

// 每条路径创建时选择自己的拥塞控制算法
fn create_path_with_cc(
    local: SocketAddr, 
    remote: SocketAddr,
    cc_algorithm: CcAlgorithm
) -> Path {
    let cc: Box<dyn CongestionController> = match cc_algorithm {
        CcAlgorithm::Cubic => Box::new(CubicController::new()),
        CcAlgorithm::Bbr => Box::new(BbrController::new()),
        CcAlgorithm::Bbr3 => Box::new(Bbr3Controller::new()),
        CcAlgorithm::Copa => Box::new(CopaController::new()),
    };
    
    Path {
        congestion_controller: cc,
        // ...
    }
}

// 甚至可以每条路径用不同的算法!
// 例如:WiFi 路径用 BBR(带宽探测好),4G 路径用 CUBIC(公平性好)

Per-path 丢包恢复

// 每条路径独立的丢包恢复
pub struct Recovery {
    /// 已发送但未确认的包
    sent_packets: BTreeMap<PacketNumber, SentPacket>,
    /// PTO 超时计算
    pto_count: u32,
    /// 最大 ACK 的包号
    largest_acked: Option<PacketNumber>,
    /// 丢包检测阈值
    loss_threshold: Duration,
}

impl Recovery {
    /// 在特定路径上检测丢包
    pub fn detect_lost_packets(&mut self, now: Instant) -> Vec<LostPacket> {
        let mut lost = Vec::new();
        
        if let Some(largest_acked) = self.largest_acked {
            for (pn, pkt) in self.sent_packets.iter() {
                // 时间阈值:超过 loss_threshold 没被 ACK → 丢了
                if now - pkt.time_sent > self.loss_threshold {
                    lost.push(LostPacket { pn: *pn, bytes: pkt.size });
                }
                // 包号阈值:比最大 ACK 小 3 个以上 → 丢了
                if largest_acked - *pn > 3 {
                    lost.push(LostPacket { pn: *pn, bytes: pkt.size });
                }
            }
        }
        
        lost
    }
}

TQUIC 的 sans-io 设计

TQUIC 和 quiche 类似,使用 sans-io 模式:

// TQUIC 的 API 风格(简化)
let mut conn = Connection::new(config)?;

// 应用的 I/O 循环
loop {
    // 1. 从 socket 读取数据,喂给 TQUIC
    let (len, from) = socket.recv_from(&mut buf)?;
    conn.recv(&mut buf[..len], RecvInfo { from })?;
    
    // 2. 处理应用层事件
    for event in conn.events() {
        match event {
            Event::StreamReadable { stream_id } => {
                let mut data = vec![0u8; 4096];
                let len = conn.stream_recv(stream_id, &mut data)?;
                // 处理收到的流数据
            }
            Event::StreamWritable { stream_id } => {
                conn.stream_send(stream_id, b"response", false)?;
            }
            Event::Connected => {
                println!("Connected!");
            }
            _ => {}
        }
    }
    
    // 3. 从 TQUIC 获取要发送的数据
    loop {
        let (len, send_info) = match conn.send(&mut out) {
            Ok(v) => v,
            Err(Error::Done) => break,
            Err(e) => return Err(e),
        };
        socket.send_to(&out[..len], send_info.to)?;
    }
    
    // 4. 处理超时
    if let Some(timeout) = conn.timeout() {
        // 设置定时器
    }
}

TQUIC vs quiche 的 API 对比

相似点:
  - 都是 sans-io
  - 都用 recv() + send() 驱动
  - 都用 BoringSSL 做 TLS
  - 应用都需要自己管理 I/O 循环

不同点:
  TQUIC 额外有:
  - 多路径相关 API(add_path, set_scheduler)
  - 更丰富的事件类型(PathAdded, PathRemoved)
  - per-path 统计信息

  conn.add_path(local_wifi, remote)?;     // 添加 WiFi 路径
  conn.add_path(local_cellular, remote)?; // 添加蜂窝路径
  conn.set_multipath_scheduler(MinRttScheduler::new())?;  // 设置调度策略

TQUIC 的 C FFI

TQUIC 提供完整的 C API,用于 iOS/Android 等移动端集成:

// C 语言使用 TQUIC(简化)

// 创建配置
tquic_config_t *config = tquic_config_new();
tquic_config_set_max_idle_timeout(config, 30000);
tquic_config_enable_multipath(config, true);
tquic_config_set_multipath_algorithm(config, TQUIC_MULTIPATH_MINRTT);

// 创建连接
tquic_conn_t *conn = tquic_connect(
    "example.com", &scid, scid_len,
    local, local_len, peer, peer_len, config
);

// 添加第二条路径
tquic_conn_add_path(conn, local_wifi, local_wifi_len, peer, peer_len);

// I/O 循环
while (!tquic_conn_is_closed(conn)) {
    // 收
    ssize_t nread = recvfrom(fd, buf, sizeof(buf), 0, ...);
    tquic_conn_recv(conn, buf, nread, &recv_info);
    
    // 发
    ssize_t nwrite;
    tquic_send_info_t send_info;
    while ((nwrite = tquic_conn_send(conn, out, sizeof(out), &send_info)) > 0) {
        sendto(fd, out, nwrite, 0, send_info.to, send_info.to_len);
    }
}

// 释放
tquic_conn_free(conn);
tquic_config_free(config);

移动端集成

iOS 集成:
  1. TQUIC 编译为 .framework(通过 cargo-lipo)
  2. Objective-C/Swift 通过 C FFI 调用
  3. 在 NetworkExtension 中管理 socket
  4. 自动检测 WiFi/Cellular 切换 → add_path / remove_path

Android 集成:
  1. TQUIC 编译为 .so(通过 cargo-ndk)
  2. Java/Kotlin 通过 JNI + C FFI 调用
  3. 使用 ConnectivityManager 监听网络变化
  4. 自动管理多路径

微信的使用方式(推测):
  - 后台服务监听网络变化
  - WiFi 连接 → add_path(wifi_addr)
  - 4G 恢复 → add_path(cellular_addr)
  - WiFi 断开 → 只剩 4G 路径,无缝切换
  - 用户无感知!

TQUIC 的拥塞控制算法

TQUIC 支持多种拥塞控制算法:

TQUIC 支持的拥塞控制算法:

1. CUBIC(默认)
   - 标准的 TCP 友好算法
   - 适合大多数场景
   - 与其他 TCP/QUIC 流公平共存

2. BBR
   - 基于带宽探测
   - 适合长距离高带宽链路
   - 可能对 CUBIC 流不公平

3. BBR3
   - BBR 的最新版本
   - 修复了与 CUBIC 的公平性问题
   - 增加了 ECN 支持

4. COPA
   - 基于延迟的算法
   - 特别适合实时应用
   - 目标:维持队列延迟接近零
   - 腾讯的研究贡献

可插拔设计:
  pub trait CongestionControlAlgorithm: Send + Sync {
      fn on_packet_sent(&mut self, bytes: usize, now: Instant);
      fn on_ack(&mut self, bytes: usize, rtt: Duration, now: Instant);
      fn on_loss(&mut self, bytes: usize, now: Instant);
      fn congestion_window(&self) -> usize;
      fn pacing_rate(&self) -> Option<u64>;
      fn in_slow_start(&self) -> bool;
      fn in_recovery(&self) -> bool;
  }

TQUIC 的代码结构

tquic/
├── src/
│   ├── lib.rs              ← 主入口
│   ├── connection/         ← 连接管理
│   │   ├── mod.rs          ← Connection 主逻辑
│   │   ├── stream.rs       ← 流管理
│   │   ├── path.rs         ← 路径管理 (PathMap)
│   │   ├── space.rs        ← 包号空间
│   │   └── ...
│   │
│   ├── multipath/          ← 多路径核心
│   │   ├── mod.rs          ← MultipathScheduler trait
│   │   ├── minrtt.rs       ← MinRtt 调度器
│   │   ├── redundant.rs    ← Redundant 调度器
│   │   ├── roundrobin.rs   ← RoundRobin 调度器
│   │   └── ...
│   │
│   ├── congestion_control/ ← 拥塞控制
│   │   ├── mod.rs          ← CC trait 定义
│   │   ├── cubic.rs        ← CUBIC
│   │   ├── bbr.rs          ← BBR
│   │   ├── bbr3.rs         ← BBR3
│   │   ├── copa.rs         ← COPA
│   │   └── ...
│   │
│   ├── recovery/           ← 丢包恢复
│   │   ├── mod.rs
│   │   └── ...
│   │
│   ├── frame/              ← QUIC 帧
│   │   ├── mod.rs
│   │   └── ...
│   │
│   ├── packet/             ← 包处理
│   │   ├── mod.rs
│   │   └── ...
│   │
│   ├── tls/                ← TLS 集成 (BoringSSL)
│   │   └── ...
│   │
│   └── ffi.rs              ← C FFI 接口
│
├── include/
│   └── tquic.h             ← C 头文件
│
├── tools/                  ← 工具
│   ├── tquic_client/       ← 示例客户端
│   └── tquic_server/       ← 示例服务端
│
└── benches/                ← 性能基准测试

犀牛鸟竞赛的切入点

自定义 MultipathScheduler

竞赛最核心的方向:实现一个新的 MultipathScheduler。

// 示例:带权重的自适应调度器
pub struct AdaptiveScheduler {
    /// 每条路径的权重(基于估计带宽)
    weights: HashMap<PathId, f64>,
    /// 累计未发送字节
    pending: usize,
}

impl MultipathScheduler for AdaptiveScheduler {
    fn on_select_path(
        &mut self,
        paths: &PathMap,
        frame: &FrameInfo,
    ) -> Option<PathId> {
        // 策略:根据每条路径的可用带宽分配权重
        let active: Vec<_> = paths.active_paths().collect();
        if active.is_empty() {
            return None;
        }
        
        // 计算每条路径的"得分"
        // 得分 = 估计带宽 / (RTT * (1 + loss_rate))
        let scores: Vec<(PathId, f64)> = active.iter()
            .map(|p| {
                let bw = p.estimated_bandwidth();
                let rtt = p.rtt().as_secs_f64();
                let loss = p.loss_rate();
                let score = bw / (rtt * (1.0 + loss));
                (p.id(), score)
            })
            .collect();
        
        // 选得分最高的路径
        scores.into_iter()
            .max_by(|a, b| a.1.partial_cmp(&b.1).unwrap())
            .map(|(id, _)| id)
    }
    
    fn on_path_updated(&mut self, path_id: PathId, event: PathEvent) {
        match event {
            PathEvent::RttUpdated(new_rtt) => {
                // 路径 RTT 变化 → 重新计算权重
            }
            PathEvent::CongestionEvent => {
                // 路径拥塞 → 降低权重
            }
            _ => {}
        }
    }
}

竞赛可以探索的方向

方向 1:基于机器学习的调度器
  - 收集路径状态特征(RTT, loss, bandwidth, queue_delay)
  - 训练一个小模型决定数据分配比例
  - 挑战:模型推理不能太慢(< 1ms per decision)

方向 2:基于 ECN 的调度器
  - 利用 ECN (Explicit Congestion Notification) 信息
  - 某路径收到 ECN CE 标记 → 预判即将拥塞 → 提前减少该路径流量
  - 比丢包反馈更早知道拥塞

方向 3:应用感知调度器
  - 视频流:I 帧走可靠路径,P 帧走快路径
  - 游戏:操作指令走所有路径(冗余),状态同步走快路径
  - 需要上层应用提供"数据优先级"信息

方向 4:联合拥塞控制 + 调度
  - 不只是"选路径",还动态调整每路径的拥塞窗口
  - 目标:总吞吐最大化 + 延迟最小化的帕累托最优
  - 这是学术界的前沿课题(Coupled CC + Scheduling)

方向 5:对 Head-of-Line Blocking 的优化
  - 多路径的核心难题:快路径数据到了,等慢路径
  - 优化方案:预测哪些包在慢路径容易丢,提前冗余发送
  - 或者:根据乱序容忍度动态调整调度比例

TQUIC 与其他实现的对比

维度 TQUIC quinn quiche s2n-quic msquic ngtcp2
多路径 一等公民 不支持 不支持 不支持 不支持 不支持
调度器 3 种 + 可插拔 N/A N/A N/A N/A N/A
Per-path CC 完整 部分 部分 部分
CC 算法 CUBIC/BBR/BBR3/COPA CUBIC/NR CUBIC/BBR/BBR2 CUBIC/BBR CUBIC/BBR NR/CUBIC/BBR/BBR2
C FFI
移动端 iOS + Android
sans-io 是(Provider)
TLS BoringSSL rustls BoringSSL s2n-tls Schannel/OpenSSL 5种

阅读 TQUIC 源码的推荐路径

第 1 步:tools/tquic_client/ 和 tools/tquic_server/
  → 看示例程序如何使用 TQUIC API
  → 理解 I/O 循环的写法

第 2 步:src/multipath/mod.rs
  → MultipathScheduler trait 的定义
  → 这是你要实现的接口!

第 3 步:src/multipath/minrtt.rs
  → 最简单的调度器实现
  → 理解调度器如何被调用

第 4 步:src/connection/path.rs
  → PathMap 和 Path 的定义
  → 理解调度器能访问什么信息

第 5 步:src/congestion_control/mod.rs
  → CC trait 定义
  → 理解拥塞控制如何与路径关联

第 6 步:src/congestion_control/bbr3.rs
  → 最新的 CC 算法实现
  → 理解 BBR3 的状态机

第 7 步:src/connection/mod.rs
  → 连接主逻辑
  → 理解调度器在整体流程中的位置
  → send() 函数如何调用 scheduler.on_select_path()

关键搜索词(grep 这些找到关键逻辑):
  "MultipathScheduler"   → trait 定义和使用点
  "on_select_path"       → 调度决策点
  "PathMap"              → 路径管理
  "congestion_controller" → 每路径 CC
  "add_path"             → 路径添加逻辑

本章小结

维度 TQUIC 的特点
核心创新 多路径是一等公民
调度器 MultipathScheduler trait(可插拔)
内置算法 MinRtt / Redundant / RoundRobin
路径管理 PathMap + per-path CC + per-path recovery
CC 算法 CUBIC / BBR / BBR3 / COPA
平台 Linux + iOS + Android
竞赛方向 实现新的 MultipathScheduler

一句话总结:TQUIC 是目前唯一将多路径作为一等公民设计的开源 QUIC 实现。它的 MultipathScheduler trait 让你可以在不改动任何底层代码的情况下,实现和测试自己的调度策略。这正是犀牛鸟 2026 竞赛的核心赛道——你需要理解 PathMap 提供的信息、三种内置调度器的思路,然后设计一个在特定场景下表现更优的新调度器。


读完本章你能做什么

  1. 画出 TQUIC 的整体架构图(从 API 到路径管理到 CC)
  2. 实现一个最简单的 MultipathScheduler(哪怕只是 random)
  3. 解释为什么 per-path CC 比共享 CC 更好
  4. 对比三种内置调度器的适用场景
  5. 给出犀牛鸟竞赛中自定义调度器的设计方向
  6. 描述阅读 TQUIC 源码的推荐路径

常见误区

误区一:多路径 QUIC 就是”哪条路好就用哪条”

正确理解:”哪条路好就用哪条”只是 MinRtt 调度器——多路径的最简单用法。多路径的真正价值是同时使用多条路径提高总带宽(RoundRobin/加权调度),或者同时发送提高可靠性(Redundant)。不同场景需要不同策略,这就是为什么调度器需要可插拔。

误区二:多路径一定比单路径快

正确理解:如果调度不当,多路径反而可能比单路径慢!经典问题:数据块 1 走快路径(20ms),数据块 2 走慢路径(200ms),接收端必须按顺序交付 → 数据块 1 到了但要等数据块 2 → 整体延迟受慢路径制约。这就是 “Head-of-Line Blocking”。好的调度器必须考虑这个问题——要么避免乱序(只走快路径),要么容忍乱序(应用层能处理),要么预测并补偿(冗余发送可能丢的包)。

误区三:实现 MultipathScheduler 很简单,竞赛拿高分轻松

正确理解:实现 trait 确实简单(就几个函数),但设计一个”好”的调度器非常难。你需要理解:网络测量的噪声(RTT 估计可能不准)、拥塞控制的交互(你的调度决策影响 CC 的判断)、公平性(不能让一条路径饿死)、收敛性(策略需要在变化的网络中稳定)。竞赛比的不是代码量,是对问题的理解深度和算法的创新性。


导读目录:README.md 上一章:第 9 章 · 安全与性能:s2n-quic——AWS 的 Rust 工程标杆 下一章:第 11 章 · 六大实现横评——该选谁?