第 13 章:犀牛鸟竞赛实战指南
竞赛速查(P1 竞赛速通者先看这里)
### 调研元信息 | 项目 | 值 | |------|-----| | 调研时间 | 2026-06-24 | | 目标项目 | TQUIC(腾讯开源 QUIC 协议实现) | | 主要语言 | Rust | | 竞赛形式 | 开源贡献(提交 PR) | | 活跃度警告 | 最近半年无 push(最后 ~2025.12),参赛前确认 issue 响应 | ### 环境矩阵 | 环境 | 能做什么 | 不能做什么 / 注意 | |------|---------|-------------------| | Linux (2C4G+ 推荐) | 完整编译 + `cargo test` + `tc netem` 网络模拟 + perf | — | | Mac | 编译 + 单测 + 基本开发 | 无 `tc netem`(无法做网络损伤测试) | | 云服务器 | 同 Linux + 多机组网测试 | 费用;建议用 2 台以上做端到端 | ### 贡献路径分级 | 级别 | 方向 | 预估工时 | 竞赛价值 | |------|------|---------|---------| | L1 零门槛 | 文档/typo/cargo clippy 修复 | 2-3 天 | 低(热身+破冰) | | L2 低门槛 | 补全单元测试 / bench 用例 | 1 周 | 中 | | L3 中门槛 | 多路径调度器改进(MinRTT 基线 +10%) | 2-3 周 | 中高 | | L4 高门槛 | 自定义拥塞控制算法实现 | 4+ 周 | 高 | ### 第一周行动清单 | 天 | 目标 | 产出 | |----|------|------| | Day 1 | `cargo build` + `cargo test` 通过 + `cargo doc --open` | 编译成功 + 本地文档 | | Day 2 | 画模块依赖图 + 找到 connection 入口 | 手绘架构图 | | Day 3 | 追踪 send/receive 路径 + 加 logging | 理解数据流 | | Day 4 | 精读目标模块(调度器 or 拥塞控制)| 确定贡献方向 | | Day 5 | 提交第一个 PR(typo/lint/小测试) | 破冰 + 熟悉 CI | ### 已知风险与边界 | 风险 | 应对 | |------|------| | TQUIC 近半年不活跃 | 先提 issue 观察 1-2 周响应;无回复则考虑换赛道 | | Rust 学习曲线 | 至少需要《The Rust Book》前 10 章基础 | | 过度复杂化首版(DRL/GPU 模型) | 先超越 MinRTT 基线 10% 再加复杂度 | | 无基线对比 | 每个改进必须有量化 benchmark(A/B 对比) | ### 读完你应能 - [ ] 用 `cargo build` 编译 TQUIC 并跑通全部单测 - [ ] 说清楚 QUIC 相比 TCP 的三个核心优势 - [ ] 画出 TQUIC 的模块依赖图(connection / stream / frame / scheduler) - [ ] 用 `tc netem` 设置丢包/延迟环境并跑 benchmark - [ ] 解释多路径调度的 MinRTT 策略及其局限从游戏攻略说起
想象你要参加一场「城市建设」游戏大赛:
新手的做法:
- 打开游戏,随便建几栋楼
- 发现缺钱,赶紧想办法赚钱
- 发现缺电,赶紧建发电厂
- 手忙脚乱,到比赛截止时城市一团糟
高手的做法:
- 赛前研究地图、规则、评分标准
- 制定分阶段建设计划
- 先打基础设施(水电路),再建上层建筑
- 每个阶段有明确里程碑和验收标准
- 最后冲刺阶段优化细节拿高分
开源竞赛也是一样——不是上来就写代码,而是先理解项目、理解评分、理解社区,然后有节奏地推进。
本章就是你的”犀牛鸟竞赛攻略”。
13.1 竞赛全景:犀牛鸟 2026 与 TQUIC
13.1.1 什么是犀牛鸟开源竞赛
犀牛鸟(Rhino-Bird)是腾讯开源联合高校举办的开源人才培养计划。核心模式:
学生 ──> 选择开源项目 ──> 导师指导 ──> 完成任务 ──> 提交成果 ──> 评审打分
│ │ │
▼ ▼ ▼
项目理解 技术深入 代码贡献
社区互动 方案设计 文档完善
问题发现 实现验证 成果展示
13.1.2 TQUIC 项目在竞赛中的定位
TQUIC 是腾讯开源的 QUIC 协议实现,用 Rust 编写。在竞赛中,它通常会出以下方向的题目:
方向一:多路径调度算法优化
- 实现新的调度策略(如基于强化学习的调度)
- 优化现有调度器在特定场景下的表现
- 对比不同策略的性能
方向二:拥塞控制改进
- 实现新的拥塞控制算法
- 优化多路径场景下的联合拥塞控制
- 解决特定网络环境(如卫星链路)的拥塞问题
方向三:协议扩展
- 实现 IETF 最新 draft 的功能
- 添加新的传输特性
- 提升与其他实现的互操作性
方向四:性能优化
- 减少内存分配
- 优化关键路径延迟
- 提升高并发场景下的吞吐
13.1.3 竞赛评分维度(通用)
┌──────────────────────────────────────────────────────────────┐
│ 竞赛评分框架 │
├──────────────┬───────────┬───────────────────────────────────┤
│ 维度 │ 权重 │ 说明 │
├──────────────┼───────────┼───────────────────────────────────┤
│ 技术深度 │ 30% │ 方案是否有技术含量 │
│ 完成度 │ 25% │ 代码是否可用、测试是否通过 │
│ 创新性 │ 20% │ 是否有新思路、新方法 │
│ 文档质量 │ 15% │ 设计文档、使用说明是否清晰 │
│ 社区贡献 │ 10% │ PR 质量、Issue 讨论、代码风格 │
└──────────────┴───────────┴───────────────────────────────────┘
关键洞察:完成度 + 文档质量 = 40%。很多同学过于追求技术深度和创新性,忽略了”把东西做完并写清楚”这个基本功。一个 80% 完成度的方案远胜过一个 40% 完成度但更”创新”的方案。
13.2 赛前准备:从零到能动手(第 1-2 周)
13.2.1 环境搭建清单
# 1. Rust 工具链
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup default stable
rustup component add clippy rustfmt
# 2. 克隆 TQUIC
git clone https://github.com/Tencent/tquic.git
cd tquic
# 3. 构建验证
cargo build
cargo test
# 4. 运行示例(如果有)
cargo run --example server
cargo run --example client
# 5. 开发工具
cargo install cargo-watch # 自动重编译
cargo install cargo-flamegraph # 性能分析
cargo install cargo-criterion # 基准测试
13.2.2 代码阅读路线图
不要一上来就读全部代码。按这个顺序:
第 1 天:宏观结构
├── 读 README.md、CONTRIBUTING.md
├── 看 Cargo.toml 了解模块划分
├── 跑 `cargo doc --open` 看 API 文档
└── 画出模块依赖图
第 2-3 天:核心数据流
├── 找到连接建立的入口
├── 跟踪一个数据包从发送到接收的完整路径
├── 理解 Connection 结构体的字段含义
└── 找到事件驱动的 poll/process 循环
第 4-5 天:你的方向
├── 如果是多路径:读 MultipathScheduler trait + 现有实现
├── 如果是拥塞控制:读 CongestionController trait + 算法
├── 如果是协议扩展:读 frame 编解码 + 状态机
└── 记录不理解的点,准备问导师
第 6-7 天:动手实验
├── 写一个最简单的修改(如加一行日志)
├── 跑测试确认没破坏东西
├── 尝试写一个最小的 benchmark
└── 提交一个 typo-fix PR 熟悉流程
13.2.3 必读材料优先级
优先级 P0(必须读):
├── TQUIC 的 README + 架构文档
├── RFC 9000(QUIC 核心)的 1-4 章
├── 你的竞赛题目描述和评分标准
└── 导师给的参考论文(如果有)
优先级 P1(应该读):
├── RFC 9000 全文
├── RFC 9002(丢失检测和拥塞控制)
├── 多路径 QUIC draft(如果相关)
└── TQUIC 的现有 PR 和 Issue
优先级 P2(有空就读):
├── 其他 QUIC 实现的架构文档
├── 相关学术论文
├── IETF 邮件列表讨论
└── 网络协议教材的相关章节
13.2.4 与导师的第一次沟通
准备一份一页纸的沟通提纲:
## 初次沟通提纲
### 我的背景
- 编程经验:[如实说]
- Rust 水平:[如实说]
- 网络协议基础:[如实说]
### 我对题目的理解
- 题目要求:[用自己的话复述]
- 我理解的关键难点:[列 2-3 个]
- 我的初步想法:[即使不成熟也说]
### 我想确认的问题
1. 评分最看重什么?
2. 有推荐的参考实现或论文吗?
3. 期望的最终交付物是什么形式?
4. 中间检查点是什么时候?交付什么?
### 我的时间计划
- 第 1-2 周:[环境搭建 + 代码阅读]
- 第 3-4 周:[方案设计]
- 第 5-8 周:[实现]
- 第 9-10 周:[测试优化 + 文档]
13.3 方案设计:想清楚再动手(第 3-4 周)
13.3.1 好方案的特征
好方案 烂方案
─────────────────────────────────────────────────────────
明确的问题定义 "我要优化调度"
可衡量的目标 "提升性能"
清晰的技术路线 "用 AI 解决"
考虑了边界情况 只考虑理想场景
有 fallback 计划 只有一条路
预估了工作量 "应该不难吧"
13.3.2 方案设计模板
# [方案名称] 设计文档
## 1. 问题定义
### 1.1 背景
- 当前 TQUIC 的 [X] 在 [Y] 场景下表现不佳
- 具体表现:[用数据说话]
### 1.2 目标
- 主目标:在 [场景] 下,将 [指标] 从 [当前值] 提升到 [目标值]
- 次目标:不降低 [其他指标] 超过 [X]%
- 约束:不修改公共 API、通过现有测试
## 2. 现状分析
### 2.1 现有实现
- [描述当前实现的方式]
- [为什么在目标场景下表现不好]
### 2.2 业界方案
- 方案 A:[描述],优点:[X],缺点:[Y]
- 方案 B:[描述],优点:[X],缺点:[Y]
- 方案 C:[描述],优点:[X],缺点:[Y]
## 3. 方案设计
### 3.1 整体架构
[ASCII 图]
### 3.2 关键算法
[伪代码或公式]
### 3.3 数据结构
[新增/修改的结构体]
### 3.4 接口设计
[trait 定义或 API 签名]
## 4. 实现计划
### 4.1 分阶段实现
- Phase 1(第 X 周):[最小可验证版本]
- Phase 2(第 X 周):[完整功能]
- Phase 3(第 X 周):[优化 + 边界处理]
### 4.2 测试计划
- 单元测试:[覆盖哪些函数]
- 集成测试:[覆盖哪些场景]
- 性能测试:[用什么工具、测什么指标]
## 5. 风险与 Fallback
- 风险 1:[描述],应对:[措施]
- 风险 2:[描述],应对:[措施]
- 最坏情况:如果 [X] 不可行,退回到 [Y]
13.3.3 以”多路径调度优化”为例的方案思路
假设题目是”为 TQUIC 实现一个自适应多路径调度器”:
问题分析:
当前 MinRtt 调度器的局限:
┌─────────────────────────────────────────────────────────────┐
│ MinRtt 策略:总是选 RTT 最小的路径发包 │
│ │
│ 问题 1:RTT 最小 ≠ 吞吐最大(带宽可能很小) │
│ 问题 2:测量滞后——当很多包涌入"最快"路径时,它变慢了 │
│ 问题 3:没有利用其他路径的空闲容量 │
│ 问题 4:突变网络(如 Wi-Fi→蜂窝切换)反应慢 │
└─────────────────────────────────────────────────────────────┘
方案选项对比:
┌─────────────┬────────────────┬───────────────┬──────────────┐
│ 方案 │ 核心思想 │ 优势 │ 劣势 │
├─────────────┼────────────────┼───────────────┼──────────────┤
│ 加权轮转 │ 按带宽比例分配 │ 简单可预测 │ 不自适应 │
│ BLEST │ 避免 HoL │ 减少延迟 │ 不最大化吞吐 │
│ ECF │ 最早完成优先 │ 理论最优 │ 需要精确估计 │
│ UCB/MAB │ 探索与利用 │ 自适应 │ 收敛慢 │
│ DRL │ 深度强化学习 │ 复杂场景强 │ 训练难部署难 │
└─────────────┴────────────────┴───────────────┴──────────────┘
推荐的渐进式方案:
Phase 1:加权 MinRtt(第 5 周)
─────────────────────────────────
- 在 MinRtt 基础上加入带宽权重
- 调度分数 = α × (1/RTT) + β × bandwidth
- 验证:比纯 MinRtt 在异构网络下提升 > 10%
Phase 2:带探索的自适应调度(第 6-7 周)
─────────────────────────────────
- 引入 UCB1 算法的探索机制
- 定期在次优路径上发少量探测包
- 根据实际性能动态调整权重
- 验证:在网络突变场景下恢复时间 < 2 RTT
Phase 3:HoL 感知(第 8 周)
─────────────────────────────────
- 考虑接收端重排序缓冲区状态
- 如果某路径 RTT 波动大,减少在它上面发首包
- 验证:P99 延迟降低 > 20%
Fallback:如果 Phase 2 效果不好
─────────────────────────────────
- 退回到 Phase 1 的加权方案
- 把 Phase 2 的分析作为"未来工作"写入文档
- 确保 Phase 1 是稳定可用的
13.3.4 方案评审 Checklist
在提交方案给导师前,自检:
□ 问题定义明确吗?有数据支撑吗?
□ 目标可衡量吗?"提升性能"不算,"吞吐提升 15%"算
□ 考虑了至少 3 个候选方案吗?说明了为什么选这个?
□ 有分阶段计划吗?每阶段有可交付物吗?
□ Phase 1 足够简单吗?能在 1 周内完成吗?
□ 有 Fallback 计划吗?最坏情况还能交付什么?
□ 测试计划明确吗?怎么证明方案有效?
□ 风险列出来了吗?各有应对措施吗?
□ 时间估计合理吗?留了 buffer 吗?(建议留 20% buffer)
□ 代码量估计了吗?大概多少行新代码?
13.4 实现阶段:写代码的节奏(第 5-8 周)
13.4.1 代码组织原则
以实现一个新的调度器为例:
tquic/src/
├── multipath/
│ ├── scheduler/
│ │ ├── mod.rs # trait 定义 + 工厂函数
│ │ ├── min_rtt.rs # 现有:MinRtt 调度器
│ │ ├── round_robin.rs # 现有:轮转调度器
│ │ ├── redundant.rs # 现有:冗余调度器
│ │ └── adaptive.rs # ★ 新增:你的自适应调度器
│ └── ...
├── ...
新增文件的结构:
//! 自适应多路径调度器
//!
//! 基于 UCB1 算法在多条路径间进行探索与利用的平衡,
//! 同时考虑 RTT 和带宽两个维度的路径质量。
//!
//! # 算法概述
//! 1. 每条路径维护一个"得分",综合 RTT、带宽、探索奖励
//! 2. 每次调度选得分最高的路径
//! 3. 定期更新得分,衰减旧测量值
//!
//! # 参考
//! - [UCB1] Auer et al., "Finite-time Analysis of the Multiarmed Bandit Problem"
//! - [BLEST] Ferlin et al., "BLEST: Blocking Estimation-based MPTCP Scheduler"
use std::time::{Duration, Instant};
use crate::multipath::path::PathId;
use crate::multipath::scheduler::MultipathScheduler;
use crate::connection::Connection;
/// 路径统计信息,用于调度决策
#[derive(Debug, Clone)]
struct PathStats {
/// 平滑 RTT(指数移动平均)
smoothed_rtt: Duration,
/// 估计带宽(bytes/sec)
estimated_bandwidth: u64,
/// 该路径被选中的总次数
selection_count: u64,
/// 该路径的累计"奖励"
cumulative_reward: f64,
/// 上次更新时间
last_updated: Instant,
}
/// 自适应调度器的配置参数
#[derive(Debug, Clone)]
pub struct AdaptiveConfig {
/// UCB 探索系数 c,越大越倾向探索
/// 推荐范围:0.5 - 2.0,默认 1.0
pub exploration_coefficient: f64,
/// RTT 权重 α
pub rtt_weight: f64,
/// 带宽权重 β
pub bandwidth_weight: f64,
/// 测量值衰减因子(每秒衰减多少)
pub decay_factor: f64,
/// 最小探测间隔
pub min_probe_interval: Duration,
}
impl Default for AdaptiveConfig {
fn default() -> Self {
Self {
exploration_coefficient: 1.0,
rtt_weight: 0.6,
bandwidth_weight: 0.4,
decay_factor: 0.95,
min_probe_interval: Duration::from_millis(100),
}
}
}
/// 自适应多路径调度器
pub struct AdaptiveScheduler {
config: AdaptiveConfig,
path_stats: HashMap<PathId, PathStats>,
total_selections: u64,
}
impl AdaptiveScheduler {
pub fn new(config: AdaptiveConfig) -> Self {
Self {
config,
path_stats: HashMap::new(),
total_selections: 0,
}
}
/// 计算路径的 UCB 得分
/// 得分 = 利用项 + 探索项
/// 利用项 = α × RTT得分 + β × 带宽得分
/// 探索项 = c × sqrt(ln(N) / n_i)
fn compute_score(&self, path_id: PathId) -> f64 {
let stats = match self.path_stats.get(&path_id) {
Some(s) => s,
None => return f64::MAX, // 未探索的路径优先
};
// 利用项:归一化的路径质量
let rtt_score = 1.0 / stats.smoothed_rtt.as_secs_f64().max(0.001);
let bw_score = stats.estimated_bandwidth as f64;
let exploitation = self.config.rtt_weight * rtt_score
+ self.config.bandwidth_weight * bw_score;
// 探索项:UCB1 公式
let exploration = if stats.selection_count > 0 && self.total_selections > 0 {
self.config.exploration_coefficient
* ((self.total_selections as f64).ln() / stats.selection_count as f64).sqrt()
} else {
f64::MAX
};
exploitation + exploration
}
/// 更新路径统计(在收到 ACK 后调用)
fn update_path_stats(&mut self, path_id: PathId, rtt: Duration, bytes_acked: u64) {
let stats = self.path_stats.entry(path_id).or_insert(PathStats {
smoothed_rtt: rtt,
estimated_bandwidth: 0,
selection_count: 0,
cumulative_reward: 0.0,
last_updated: Instant::now(),
});
// EWMA 更新 RTT
let alpha = 0.125;
stats.smoothed_rtt = Duration::from_secs_f64(
(1.0 - alpha) * stats.smoothed_rtt.as_secs_f64() + alpha * rtt.as_secs_f64(),
);
// 更新带宽估计
if rtt > Duration::ZERO {
let instant_bw = bytes_acked as f64 / rtt.as_secs_f64();
stats.estimated_bandwidth =
((1.0 - alpha) * stats.estimated_bandwidth as f64 + alpha * instant_bw) as u64;
}
stats.last_updated = Instant::now();
}
}
impl MultipathScheduler for AdaptiveScheduler {
fn on_schedule(&mut self, conn: &Connection) -> Option<PathId> {
let available_paths = conn.paths().available();
if available_paths.is_empty() {
return None;
}
// 选择得分最高的路径
let best_path = available_paths
.iter()
.max_by(|a, b| {
self.compute_score(*a)
.partial_cmp(&self.compute_score(*b))
.unwrap_or(std::cmp::Ordering::Equal)
})
.copied();
if let Some(path_id) = best_path {
self.total_selections += 1;
if let Some(stats) = self.path_stats.get_mut(&path_id) {
stats.selection_count += 1;
}
}
best_path
}
}
13.4.2 测试策略
测试金字塔:
/\
/ \ 集成测试(端到端场景)
/────\ 5-10 个关键场景
/ \
/────────\ 属性测试(边界/随机)
/ \ 用 proptest crate
/────────────\
/ \ 单元测试(每个函数)
/────────────────\ 覆盖正常 + 异常路径
单元测试示例:
#[cfg(test)]
mod tests {
use super::*;
/// 未探索的路径应该优先被选中
#[test]
fn test_unexplored_path_priority() {
let mut scheduler = AdaptiveScheduler::new(AdaptiveConfig::default());
// 模拟 path_0 已经被选了 100 次
scheduler.path_stats.insert(PathId(0), PathStats {
smoothed_rtt: Duration::from_millis(50),
estimated_bandwidth: 1_000_000,
selection_count: 100,
cumulative_reward: 80.0,
last_updated: Instant::now(),
});
// path_1 是新路径,从未被探索
// 它的得分应该是 f64::MAX,所以应该被优先选中
let score_0 = scheduler.compute_score(PathId(0));
let score_1 = scheduler.compute_score(PathId(1));
assert!(score_1 > score_0, "未探索路径应有更高得分");
}
/// RTT 更小的路径在利用阶段应该得分更高
#[test]
fn test_lower_rtt_higher_exploitation_score() {
let mut scheduler = AdaptiveScheduler::new(AdaptiveConfig {
exploration_coefficient: 0.0, // 关闭探索,只看利用
..Default::default()
});
scheduler.path_stats.insert(PathId(0), PathStats {
smoothed_rtt: Duration::from_millis(20),
estimated_bandwidth: 1_000_000,
selection_count: 50,
cumulative_reward: 40.0,
last_updated: Instant::now(),
});
scheduler.path_stats.insert(PathId(1), PathStats {
smoothed_rtt: Duration::from_millis(100),
estimated_bandwidth: 1_000_000,
selection_count: 50,
cumulative_reward: 30.0,
last_updated: Instant::now(),
});
scheduler.total_selections = 100;
let score_0 = scheduler.compute_score(PathId(0));
let score_1 = scheduler.compute_score(PathId(1));
assert!(score_0 > score_1, "RTT 更小的路径应得分更高");
}
/// 探索系数增大应该增加低探索路径的得分
#[test]
fn test_exploration_coefficient_effect() {
let mut scheduler_low = AdaptiveScheduler::new(AdaptiveConfig {
exploration_coefficient: 0.5,
..Default::default()
});
let mut scheduler_high = AdaptiveScheduler::new(AdaptiveConfig {
exploration_coefficient: 2.0,
..Default::default()
});
let stats = PathStats {
smoothed_rtt: Duration::from_millis(50),
estimated_bandwidth: 1_000_000,
selection_count: 5, // 很少被选
cumulative_reward: 3.0,
last_updated: Instant::now(),
};
scheduler_low.path_stats.insert(PathId(0), stats.clone());
scheduler_low.total_selections = 100;
scheduler_high.path_stats.insert(PathId(0), stats);
scheduler_high.total_selections = 100;
let score_low = scheduler_low.compute_score(PathId(0));
let score_high = scheduler_high.compute_score(PathId(0));
assert!(
score_high > score_low,
"更高的探索系数应给低探索路径更高得分"
);
}
/// RTT 更新应该是指数移动平均
#[test]
fn test_rtt_ewma_update() {
let mut scheduler = AdaptiveScheduler::new(AdaptiveConfig::default());
// 初始 RTT = 100ms
scheduler.update_path_stats(PathId(0), Duration::from_millis(100), 1000);
let rtt_1 = scheduler.path_stats[&PathId(0)].smoothed_rtt;
// 新测量 RTT = 20ms(大幅下降)
scheduler.update_path_stats(PathId(0), Duration::from_millis(20), 1000);
let rtt_2 = scheduler.path_stats[&PathId(0)].smoothed_rtt;
// EWMA 应该让 rtt_2 在 20 和 100 之间,且比 rtt_1 小
assert!(rtt_2 < rtt_1, "新的小 RTT 应拉低平滑值");
assert!(rtt_2 > Duration::from_millis(20), "EWMA 不会一步跳到新值");
}
}
关键观察:
alpha = 0.125是 RFC 6298 推荐值- 新测量值权重只有 1/8,历史惯性很大
- 这意味着突发的好/坏测量不会立刻改变调度决策
- 但持续的趋势会逐渐体现
13.2.2 里程碑 2:MinRTT 调度器
// 实现最简单但有效的调度策略
use std::collections::HashMap;
use std::time::Duration;
struct PathStats {
smoothed_rtt: Duration,
available_cwnd: usize, // 拥塞窗口剩余空间
is_active: bool,
}
struct MinRttScheduler {
paths: HashMap<u64, PathStats>,
}
impl MinRttScheduler {
fn select_path(&self) -> Option<u64> {
// 只在有可用窗口的活跃路径中选择
self.paths.iter()
.filter(|(_, stats)| stats.is_active && stats.available_cwnd > 0)
.min_by_key(|(_, stats)| stats.smoothed_rtt)
.map(|(id, _)| *id)
}
fn on_packet_sent(&mut self, path_id: u64, size: usize) {
if let Some(stats) = self.paths.get_mut(&path_id) {
stats.available_cwnd = stats.available_cwnd.saturating_sub(size);
}
}
fn on_ack_received(&mut self, path_id: u64, size: usize, rtt: Duration) {
if let Some(stats) = self.paths.get_mut(&path_id) {
// 释放窗口空间
stats.available_cwnd += size;
// 更新 RTT(简化版 EWMA)
let alpha = 0.125;
let new_rtt = Duration::from_secs_f64(
(1.0 - alpha) * stats.smoothed_rtt.as_secs_f64()
+ alpha * rtt.as_secs_f64()
);
stats.smoothed_rtt = new_rtt;
}
}
}
为什么从 MinRTT 开始:
- 实现最简单——只需比较一个指标
- 效果已经不错——大部分场景下低延迟路径确实更好
- TQUIC 的
MinRttScheduler就是这个逻辑 - 后续优化都是在此基础上加条件
13.2.3 里程碑 3:冗余调度器
// 冗余调度:同一数据发到所有路径
struct RedundantScheduler {
paths: HashMap<u64, PathStats>,
}
impl RedundantScheduler {
fn select_paths(&self) -> Vec<u64> {
// 返回所有有窗口的活跃路径
self.paths.iter()
.filter(|(_, stats)| stats.is_active && stats.available_cwnd > 0)
.map(|(id, _)| *id)
.collect()
}
fn schedule_packet(&self, data: &[u8]) -> Vec<(u64, Vec<u8>)> {
// 同一份数据复制到所有可用路径
self.select_paths().into_iter()
.map(|path_id| (path_id, data.to_vec()))
.collect()
}
}
冗余调度的价值:
- 延迟 = min(所有路径的延迟)
- 可靠性极高——任何一条路径成功就行
- 代价:带宽消耗翻倍(或更多)
- 适用场景:实时控制信号、游戏操作指令等对延迟极敏感的小数据
13.2.4 里程碑 4:自适应调度器
// 结合 RTT 和带宽的加权调度
struct AdaptiveScheduler {
paths: HashMap<u64, PathStats>,
// 每条路径的"评分"
scores: HashMap<u64, f64>,
}
struct PathStats {
smoothed_rtt: Duration,
estimated_bandwidth: f64, // bytes/sec
loss_rate: f64, // 0.0 - 1.0
available_cwnd: usize,
is_active: bool,
}
impl AdaptiveScheduler {
fn update_scores(&mut self) {
for (id, stats) in &self.paths {
if !stats.is_active {
self.scores.insert(*id, 0.0);
continue;
}
// 综合评分 = 带宽 / (RTT * (1 + loss_rate))
// 直觉:高带宽、低延迟、低丢包 = 高分
let rtt_sec = stats.smoothed_rtt.as_secs_f64().max(0.001);
let score = stats.estimated_bandwidth
/ (rtt_sec * (1.0 + stats.loss_rate * 10.0));
self.scores.insert(*id, score);
}
}
fn select_path(&mut self) -> Option<u64> {
self.update_scores();
self.paths.iter()
.filter(|(_, stats)| stats.is_active && stats.available_cwnd > 0)
.max_by(|(id_a, _), (id_b, _)| {
let score_a = self.scores.get(id_a).unwrap_or(&0.0);
let score_b = self.scores.get(id_b).unwrap_or(&0.0);
score_a.partial_cmp(score_b).unwrap()
})
.map(|(id, _)| *id)
}
}
渐进式改进:
- 里程碑 2 只看 RTT → 里程碑 4 综合 RTT + 带宽 + 丢包率
- 评分公式可以不断调优
- 这就是”做研究”的入口——不同的评分函数对应不同的论文思路
13.3 TQUIC 代码导读地图
13.3.1 项目结构概览
tquic/
├── src/
│ ├── connection/ # 连接管理
│ │ ├── mod.rs # Connection 主结构
│ │ ├── path.rs # 路径管理 ← 重点
│ │ ├── space.rs # 包号空间
│ │ └── stream.rs # 流管理
│ ├── congestion_control/ # 拥塞控制
│ │ ├── mod.rs # CongestionController trait
│ │ ├── cubic.rs # CUBIC 实现
│ │ ├── bbr/ # BBR 实现
│ │ │ ├── mod.rs
│ │ │ ├── bbr2.rs
│ │ │ └── bbr3.rs
│ │ └── copa.rs # COPA 实现
│ ├── multipath/ # 多路径 ← 竞赛核心
│ │ ├── mod.rs # MultipathScheduler trait
│ │ ├── minrtt.rs # 最小 RTT 调度
│ │ ├── redundant.rs # 冗余调度
│ │ └── roundrobin.rs # 轮询调度
│ ├── recovery/ # 丢包恢复
│ ├── tls/ # TLS 集成
│ └── lib.rs # 公共 API
├── tools/ # CLI 工具
├── ffi/ # C FFI 绑定
└── benches/ # 性能基准
13.3.2 核心 Trait 关系
┌─────────────────────────────────────────────────────┐
│ Connection │
│ │
│ ┌─────────────┐ ┌──────────────────────────┐ │
│ │ PathMap │ │ MultipathScheduler │ │
│ │ │ │ (trait object) │ │
│ │ path_0 ────┼────┤ │ │
│ │ path_1 ────┼────┤ fn on_packet_sent() │ │
│ │ path_2 ────┼────┤ fn on_packet_acked() │ │
│ │ │ │ fn select_send_path() │ │
│ └─────────────┘ └──────────────────────────┘ │
│ │
│ 每条 Path 包含: │
│ ┌──────────────────────────────────────────────┐ │
│ │ Path │ │
│ │ ├── CongestionController (独立) │ │
│ │ ├── RecoveryState (独立) │ │
│ │ ├── smoothed_rtt │ │
│ │ ├── cwnd / bytes_in_flight │ │
│ │ └── peer_addr / local_addr │ │
│ └──────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────┘
13.3.3 阅读路径建议
第一遍(2 天):理解接口
src/multipath/mod.rs— 读MultipathSchedulertrait 定义src/multipath/minrtt.rs— 最简实现,对照 trait 方法src/connection/path.rs—PathMap如何管理多条路径
第二遍(3 天):理解数据流
src/connection/mod.rs—send()如何调用 schedulersrc/recovery/— ACK 如何回传到 path statssrc/congestion_control/mod.rs— CC trait 与 Path 的关系
第三遍(5 天):准备修改
- 跑通
cargo test中 multipath 相关测试 - 加 tracing 日志观察调度决策过程
- 尝试写一个新的 scheduler(下节详细说)
13.4 实现自定义调度器
13.4.1 步骤总览
┌──────────────────────────────────────────────────────┐
│ 实现新调度器的 6 个步骤 │
├──────────────────────────────────────────────────────┤
│ │
│ 1. Fork tquic 仓库 │
│ └── git clone + cargo build 验证 │
│ │
│ 2. 创建新文件 src/multipath/my_scheduler.rs │
│ └── impl MultipathScheduler for MyScheduler │
│ │
│ 3. 在 mod.rs 中注册 │
│ └── pub mod my_scheduler; + 配置项 │
│ │
│ 4. 写单元测试 │
│ └── 模拟多路径场景验证行为 │
│ │
│ 5. 集成测试 │
│ └── 用 tquic 工具跑真实连接 │
│ │
│ 6. 性能评估 │
│ └── 对比现有调度器的吞吐/延迟/公平性 │
│ │
└──────────────────────────────────────────────────────┘
13.4.2 Trait 实现模板
// src/multipath/weighted_rtt.rs
// 加权 RTT 调度器:考虑 RTT 和可用窗口的比例
use super::{MultipathScheduler, SchedulerContext};
use crate::connection::path::PathId;
pub struct WeightedRttScheduler {
// 配置参数
rtt_weight: f64, // RTT 在评分中的权重
cwnd_weight: f64, // 可用窗口在评分中的权重
}
impl WeightedRttScheduler {
pub fn new() -> Self {
Self {
rtt_weight: 0.7,
cwnd_weight: 0.3,
}
}
fn score_path(&self, ctx: &SchedulerContext, path_id: PathId) -> f64 {
let path = match ctx.get_path(path_id) {
Some(p) => p,
None => return 0.0,
};
if !path.is_active() || path.available_cwnd() == 0 {
return 0.0;
}
// RTT 分数:越低越好,取倒数
let rtt_score = 1.0 / path.smoothed_rtt().as_secs_f64().max(0.001);
// 窗口分数:可用空间占比
let cwnd_score = path.available_cwnd() as f64
/ path.max_cwnd() as f64;
// 加权综合
self.rtt_weight * rtt_score + self.cwnd_weight * cwnd_score
}
}
impl MultipathScheduler for WeightedRttScheduler {
fn select_send_path(&self, ctx: &SchedulerContext) -> Option<PathId> {
ctx.active_paths()
.iter()
.filter(|&&pid| {
ctx.get_path(pid)
.map(|p| p.available_cwnd() > 0)
.unwrap_or(false)
})
.max_by(|&&a, &&b| {
let score_a = self.score_path(ctx, a);
let score_b = self.score_path(ctx, b);
score_a.partial_cmp(&score_b).unwrap_or(std::cmp::Ordering::Equal)
})
.copied()
}
fn on_packet_sent(&mut self, _ctx: &SchedulerContext, _path_id: PathId, _size: usize) {
// 基础版本不需要额外状态
// 高级版本可以在这里更新发送统计
}
fn on_packet_acked(&mut self, _ctx: &SchedulerContext, _path_id: PathId, _size: usize) {
// 基础版本不需要额外状态
// 高级版本可以在这里更新确认统计
}
fn on_packet_lost(&mut self, _ctx: &SchedulerContext, _path_id: PathId, _size: usize) {
// 基础版本不需要额外状态
// 高级版本可以在这里降低路径评分
}
}
13.4.3 测试策略
#[cfg(test)]
mod tests {
use super::*;
// 测试 1:基本选择——应选 RTT 最低的路径
#[test]
fn test_selects_lowest_rtt() {
let mut ctx = MockSchedulerContext::new();
ctx.add_path(PathId(0), PathStats {
smoothed_rtt: Duration::from_millis(50),
available_cwnd: 1000,
max_cwnd: 10000,
is_active: true,
});
ctx.add_path(PathId(1), PathStats {
smoothed_rtt: Duration::from_millis(100),
available_cwnd: 1000,
max_cwnd: 10000,
is_active: true,
});
let scheduler = WeightedRttScheduler::new();
assert_eq!(scheduler.select_send_path(&ctx), Some(PathId(0)));
}
// 测试 2:窗口耗尽时应跳过
#[test]
fn test_skips_exhausted_path() {
let mut ctx = MockSchedulerContext::new();
ctx.add_path(PathId(0), PathStats {
smoothed_rtt: Duration::from_millis(10), // 最低 RTT
available_cwnd: 0, // 但窗口用完了
max_cwnd: 10000,
is_active: true,
});
ctx.add_path(PathId(1), PathStats {
smoothed_rtt: Duration::from_millis(100),
available_cwnd: 5000,
max_cwnd: 10000,
is_active: true,
});
let scheduler = WeightedRttScheduler::new();
// 即使 path 0 RTT 更低,窗口为 0 也不能选
assert_eq!(scheduler.select_send_path(&ctx), Some(PathId(1)));
}
// 测试 3:大窗口可以弥补高 RTT
#[test]
fn test_large_cwnd_compensates_high_rtt() {
let mut ctx = MockSchedulerContext::new();
// 路径 0:RTT 低但窗口快满了
ctx.add_path(PathId(0), PathStats {
smoothed_rtt: Duration::from_millis(20),
available_cwnd: 100, // 只剩 1%
max_cwnd: 10000,
is_active: true,
});
// 路径 1:RTT 高但窗口很空
ctx.add_path(PathId(1), PathStats {
smoothed_rtt: Duration::from_millis(80),
available_cwnd: 9000, // 90% 空闲
max_cwnd: 10000,
is_active: true,
});
// 用高窗口权重的调度器
let scheduler = WeightedRttScheduler {
rtt_weight: 0.3,
cwnd_weight: 0.7,
};
// 窗口权重高时,应该选窗口更空的路径 1
assert_eq!(scheduler.select_send_path(&ctx), Some(PathId(1)));
}
// 测试 4:所有路径不可用时返回 None
#[test]
fn test_no_available_path() {
let mut ctx = MockSchedulerContext::new();
ctx.add_path(PathId(0), PathStats {
smoothed_rtt: Duration::from_millis(50),
available_cwnd: 0,
max_cwnd: 10000,
is_active: true,
});
ctx.add_path(PathId(1), PathStats {
smoothed_rtt: Duration::from_millis(100),
available_cwnd: 0,
max_cwnd: 10000,
is_active: false,
});
let scheduler = WeightedRttScheduler::new();
assert_eq!(scheduler.select_send_path(&ctx), None);
}
}
13.5 评估方法论
13.5.1 测试环境搭建
┌──────────────────────────────────────────────────────────────┐
│ 评估环境架构 │
├──────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────┐ tc netem ┌─────────┐ │
│ │ │◄──────────────────►│ │ │
│ │ TQUIC │ 路径 1 │ TQUIC │ │
│ │ Client │ (WiFi模拟) │ Server │ │
│ │ │ │ │ │
│ │ │ tc netem │ │ │
│ │ │◄──────────────────►│ │ │
│ └─────────┘ 路径 2 └─────────┘ │
│ (4G模拟) │
│ │
│ 工具: │
│ - tc netem: 模拟延迟、丢包、带宽限制 │
│ - netns: Linux 网络命名空间隔离路径 │
│ - iperf3: 基准带宽测量 │
│ - wireshark: 抓包分析 │
│ │
└──────────────────────────────────────────────────────────────┘
13.5.2 场景设计
#!/bin/bash
# setup-test-env.sh - 创建双路径测试环境
# 场景 1:对称路径(两条路径差不多)
setup_symmetric() {
# 路径 1:50ms RTT, 10Mbps, 0% loss
tc qdisc add dev veth0 root netem delay 25ms rate 10mbit
# 路径 2:60ms RTT, 10Mbps, 0% loss
tc qdisc add dev veth1 root netem delay 30ms rate 10mbit
}
# 场景 2:非对称路径(一快一慢)
setup_asymmetric() {
# 路径 1:20ms RTT, 50Mbps, 0% loss (WiFi)
tc qdisc add dev veth0 root netem delay 10ms rate 50mbit
# 路径 2:100ms RTT, 5Mbps, 1% loss (4G)
tc qdisc add dev veth1 root netem delay 50ms rate 5mbit loss 1%
}
# 场景 3:动态变化(路径质量交替变化)
setup_dynamic() {
# 初始:路径 1 好
tc qdisc add dev veth0 root netem delay 10ms rate 50mbit
tc qdisc add dev veth1 root netem delay 100ms rate 5mbit
# 30 秒后:路径 1 变差,路径 2 变好
sleep 30
tc qdisc change dev veth0 root netem delay 200ms rate 2mbit loss 5%
tc qdisc change dev veth1 root netem delay 20ms rate 30mbit
}
# 场景 4:极端丢包
setup_lossy() {
# 路径 1:低延迟但高丢包
tc qdisc add dev veth0 root netem delay 10ms rate 50mbit loss 10%
# 路径 2:高延迟但稳定
tc qdisc add dev veth1 root netem delay 80ms rate 20mbit loss 0.1%
}
13.5.3 指标收集
// 评估指标结构
struct BenchmarkResults {
// 吞吐量
total_bytes: u64,
duration: Duration,
goodput_mbps: f64, // 有效吞吐(排除重传)
// 延迟
latency_p50: Duration, // 中位数延迟
latency_p95: Duration, // 95 分位
latency_p99: Duration, // 99 分位
jitter: Duration, // 延迟抖动
// 效率
retransmit_rate: f64, // 重传率
path_utilization: Vec<f64>, // 各路径利用率
// 公平性
jain_fairness_index: f64, // Jain 公平性指数
}
impl BenchmarkResults {
fn goodput(&self) -> f64 {
(self.total_bytes as f64 * 8.0) / self.duration.as_secs_f64() / 1_000_000.0
}
fn path_balance(&self) -> f64 {
// 路径利用率的标准差——越小越均衡
let mean: f64 = self.path_utilization.iter().sum::<f64>()
/ self.path_utilization.len() as f64;
let variance: f64 = self.path_utilization.iter()
.map(|x| (x - mean).powi(2))
.sum::<f64>() / self.path_utilization.len() as f64;
variance.sqrt()
}
}
13.5.4 结果呈现
┌─────────────────────────────────────────────────────────────────┐
│ 评估结果对比表(示例) │
├───────────────┬──────────┬──────────┬───────────┬──────────────┤
│ 调度器 │ 吞吐 Mbps│ P95 延迟 │ 重传率 % │ 路径均衡度 │
├───────────────┼──────────┼──────────┼───────────┼──────────────┤
│ MinRTT │ 45.2 │ 62ms │ 2.1% │ 0.35 │
│ RoundRobin │ 38.7 │ 105ms │ 4.5% │ 0.05 │
│ Redundant │ 23.1 │ 28ms │ 0.3% │ 0.02 │
│ WeightedRTT │ 48.9 │ 55ms │ 1.8% │ 0.22 │
│ (你的方案) │ ?? │ ?? │ ?? │ ?? │
├───────────────┴──────────┴──────────┴───────────┴──────────────┤
│ 场景:非对称路径(WiFi 50Mbps + 4G 5Mbps) │
│ 传输:100MB 文件下载 │
└─────────────────────────────────────────────────────────────────┘
13.6 犀牛鸟竞赛策略
13.6.1 时间线规划
┌──────────────────────────────────────────────────────────────────┐
│ 犀牛鸟 2026 竞赛时间线(建议) │
├──────────────────────────────────────────────────────────────────┤
│ │
│ 第 1-2 周:基础准备 │
│ ├── 读完本导读 ch01-ch06 │
│ ├── 搭建 TQUIC 开发环境 │
│ ├── 跑通所有现有测试 │
│ └── 理解 MultipathScheduler trait │
│ │
│ 第 3-4 周:深入理解 │
│ ├── 读完 ch07-ch12 │
│ ├── 精读 TQUIC 多路径代码 │
│ ├── 复现论文实验(MinRTT vs RoundRobin) │
│ └── 确定研究方向 │
│ │
│ 第 5-8 周:核心实现 │
│ ├── 实现自定义调度器 v1 │
│ ├── 搭建评估环境 │
│ ├── 初步实验 + 调参 │
│ └── 迭代改进到 v2、v3 │
│ │
│ 第 9-10 周:优化与完善 │
│ ├── 边界场景测试 │
│ ├── 性能优化 │
│ ├── 代码整理 + 文档 │
│ └── 准备 PR │
│ │
│ 第 11-12 周:提交与答辩 │
│ ├── 撰写技术报告 │
│ ├── 准备演示 │
│ ├── 提交 PR + 回应 review │
│ └── 答辩准备 │
│ │
└──────────────────────────────────────────────────────────────────┘
13.6.2 研究方向建议
方向 A:自适应权重调度
核心思路:不同场景下,RTT、带宽、丢包率对调度决策的重要性不同。设计一个能自动调整权重的调度器。
// 思路伪代码
struct AdaptiveWeightScheduler {
weights: [f64; 3], // [rtt_weight, bw_weight, loss_weight]
history: VecDeque<Decision>,
reward_signal: f64,
}
impl AdaptiveWeightScheduler {
fn adapt_weights(&mut self) {
// 根据最近的调度效果调整权重
// 如果选了某路径后吞吐提升 → 增加当时权重高的因素
// 如果选了某路径后延迟恶化 → 降低当时权重高的因素
}
}
优势:
- 不需要预知场景类型
- 能适应动态变化的网络
- 实现相对简单,效果可能显著
方向 B:预测性调度
核心思路:不只看当前 RTT,还预测未来 RTT 趋势。如果某路径 RTT 在上升,提前切换。
// 思路伪代码
struct PredictiveScheduler {
// 每条路径维护 RTT 趋势
rtt_history: HashMap<PathId, VecDeque<(Instant, Duration)>>,
}
impl PredictiveScheduler {
fn predict_rtt(&self, path_id: PathId, horizon: Duration) -> Duration {
// 用线性回归或指数平滑预测未来 RTT
// 如果趋势在恶化,预测值 > 当前值
// 如果趋势在改善,预测值 < 当前值
}
fn select_path(&self) -> Option<PathId> {
// 用预测的 RTT 而不是当前 RTT 做决策
}
}
优势:
- 能提前响应网络变化(而非被动响应)
- 减少”选了正在变差的路径”的情况
- 有学术创新点
方向 C:Explore-Exploit 调度
核心思路:借鉴多臂老虎机理论,平衡”利用已知最优路径”和”探索可能变好的路径”。
// 思路伪代码(UCB 策略)
struct UCBScheduler {
path_rewards: HashMap<PathId, f64>, // 平均收益
path_counts: HashMap<PathId, u64>, // 选择次数
total_count: u64,
}
impl UCBScheduler {
fn ucb_score(&self, path_id: PathId) -> f64 {
let avg_reward = self.path_rewards[&path_id];
let count = self.path_counts[&path_id] as f64;
let exploration_bonus = (2.0 * (self.total_count as f64).ln() / count).sqrt();
avg_reward + exploration_bonus // 选择次数少的路径获得更高探索奖励
}
}
优势:
- 有坚实的理论基础(regret bound)
- 能发现”被低估”的路径
- ch12 已有详细讲解,理论准备充分
13.6.3 评审关注点
根据开源竞赛的通常评审标准,评委通常关注:
┌─────────────────────────────────────────────────────────┐
│ 评审维度及权重(推测) │
├─────────────────────────────────────────────────────────┤
│ │
│ 30% 技术创新 │
│ ├── 方案是否有新意? │
│ ├── 不是简单搬运论文,有自己的改进 │
│ └── 理论分析是否清晰? │
│ │
│ 30% 实验效果 │
│ ├── 是否比现有方案更好?(至少在某些场景) │
│ ├── 实验方法是否科学? │
│ └── 结果是否可复现? │
│ │
│ 20% 代码质量 │
│ ├── 是否符合项目编码规范? │
│ ├── 是否有完善的测试? │
│ └── 是否易于维护和扩展? │
│ │
│ 20% 文档与展示 │
│ ├── 技术报告是否清晰? │
│ ├── 是否有好的可视化? │
│ └── 答辩是否能讲清思路? │
│ │
└─────────────────────────────────────────────────────────┘
13.6.4 常见陷阱
陷阱 1:过度复杂化
错误做法:
- 第一版就搞深度强化学习
- 引入大量超参数
- 使用需要 GPU 的模型
正确做法:
- 从简单有效的方案开始
- 逐步增加复杂度
- 确保每步改进都有实验支撑
陷阱 2:忽视基线对比
错误做法:
- 只展示自己的方案效果
- 没有和 MinRTT/RoundRobin 对比
- 测试场景单一
正确做法:
- 在多种场景下和所有现有调度器对比
- 分析”赢在哪里”和”输在哪里”
- 诚实报告不足之处
陷阱 3:不了解项目规范
错误做法:
- 提交大量格式不一致的代码
- 没有测试
- commit message 随意
正确做法:
- 仔细阅读 CONTRIBUTING.md
- 运行
cargo fmt和cargo clippy - 每个 commit 只做一件事,message 清晰
陷阱 4:闭门造车
错误做法:
- 不和 mentor 沟通就开始写代码
- 方向跑偏了才发现
- 最后一周才开始写报告
正确做法:
- 每周和 mentor 同步进展
- 早期确认方向可行性
- 边做边写文档
13.7 PR 提交指南
13.7.1 PR 结构
## 标题格式
multipath: add weighted RTT scheduler
## 描述模板
### What
简要说明这个 PR 做了什么。
### Why
为什么需要这个改进?现有方案有什么不足?
### How
技术方案简述。关键设计决策。
### Testing
- 单元测试:X 个新测试
- 集成测试:Y 个场景
- 性能测试:对比结果摘要
### Results
| 场景 | MinRTT | 本方案 | 提升 |
|------|--------|--------|------|
| 对称 | 45Mbps | 48Mbps | +6% |
| 非对称| 42Mbps | 51Mbps | +21% |
| 动态 | 35Mbps | 44Mbps | +25% |
### Related
- RFC/论文引用
- 相关 Issue 链接
13.7.2 代码组织
# 好的 commit 历史
git log --oneline
abc1234 multipath: add WeightedRttScheduler implementation
def5678 multipath: add unit tests for WeightedRttScheduler
ghi9012 multipath: add integration tests for weighted scheduling
jkl3456 multipath: add benchmark comparing schedulers
mno7890 docs: add weighted RTT scheduler documentation
pqr1234 config: expose weighted scheduler configuration options
每个 commit:
- 只做一件事
- 能独立编译通过
- 有清晰的 message
13.7.3 Review 响应
收到 review 意见时:
┌──────────────────────────────────────────────────────┐
│ Review 响应最佳实践 │
├──────────────────────────────────────────────────────┤
│ │
│ 1. 感谢 reviewer 的时间 │
│ "Thanks for the thorough review!" │
│ │
│ 2. 对每条意见逐一回应 │
│ - 同意并修改:"Good point, fixed in abc1234" │
│ - 不同意但有理由:解释原因,提供数据 │
│ - 需要讨论:"I see your point, could we..." │
│ │
│ 3. 不要 force push(除非 reviewer 要求 squash) │
│ │
│ 4. 修改后主动 ping reviewer │
│ "Updated per your feedback, PTAL" │
│ │
└──────────────────────────────────────────────────────┘
13.8 学习资源索引
13.8.1 必读材料
RFC 与标准:
- RFC 9000:QUIC 传输协议
- RFC 9001:QUIC 中使用 TLS
- RFC 9002:QUIC 丢包检测与拥塞控制
- draft-ietf-quic-multipath:多路径 QUIC(最新版)
论文:
- “Multipath QUIC: Design and Evaluation”(De Coninck & Bonaventure, 2017)
- “MPQUIC is not enough: Towards a Scheduler for Multipath Transport”(Shi et al.)
- “ECF: An MPTCP Scheduler for Managing Heterogeneous Paths”
- “BLEST: Blocking Estimation-based MPTCP Scheduler”
项目文档:
- TQUIC GitHub:https://github.com/Tencent/tquic
- TQUIC 官方文档
- 犀牛鸟竞赛指南
13.8.2 辅助工具
┌─────────────────────────────────────────────────────────┐
│ 开发工具链 │
├─────────────────────────────────────────────────────────┤
│ │
│ 编程: │
│ ├── Rust (rustup + cargo) │
│ ├── rust-analyzer (IDE 支持) │
│ └── cargo-watch (自动重编译) │
│ │
│ 网络测试: │
│ ├── tc netem (Linux 网络模拟) │
│ ├── mininet (网络拓扑模拟) │
│ ├── wireshark + quic 解析插件 │
│ └── qlog (QUIC 日志标准格式) │
│ │
│ 性能分析: │
│ ├── perf (Linux 性能分析) │
│ ├── flamegraph (火焰图) │
│ ├── criterion (Rust 基准测试) │
│ └── gnuplot / matplotlib (结果可视化) │
│ │
│ 协作: │
│ ├── git (版本管理) │
│ ├── GitHub PR workflow │
│ └── Markdown (文档) │
│ │
└─────────────────────────────────────────────────────────┘
13.8.3 社区资源
- IETF QUIC 工作组邮件列表
- QUIC 相关的学术会议:IMC、CoNEXT、SIGCOMM
- Rust 网络编程社区
- TQUIC 的 GitHub Issues 和 Discussions
13.9 常见问题 FAQ
Q:我没有 Rust 经验,能参加吗?
A:可以。建议安排 2 周集中学 Rust 基础(所有权、生命周期、trait),然后边读 TQUIC 代码边学。TQUIC 的代码质量很高,是学 Rust 的好材料。关键是不要在学 Rust 上花太多时间——够用就行,重点是算法设计。
Q:选哪个方向最容易出成果?
A:方向 A(自适应权重)实现难度最低,改进空间大;方向 C(Explore-Exploit)理论性最强,容易写出好论文;方向 B(预测性)居中。建议根据自己的背景选:
- 算法竞赛背景 → 方向 C
- 机器学习背景 → 方向 B
- 系统编程背景 → 方向 A + 性能优化
Q:评估实验需要什么硬件?
A:一台 Linux 机器(或 VM)就够。用 tc netem 模拟网络条件不需要真实的多网卡。最低配置:2 核 CPU、4GB RAM。推荐:4 核、8GB。云服务器也行,但注意 tc netem 需要 root 权限。
Q:如何判断我的方案”足够好”?
A:三个标准:
- 在至少一个场景下比 MinRTT 有 10% 以上的改进
- 在其他场景下不比 MinRTT 差超过 5%
- 实现复杂度可控(代码 < 500 行)
满足这三条就是一个很好的竞赛成果。
Q:可以参考其他实现吗?
A:当然可以,但要注意:
- 阅读其他项目代码获取灵感:完全 OK
- 引用论文中的算法:完全 OK(标注来源)
- 复制粘贴代码:绝对不行(版权 + 学术诚信)
- 基于论文重新实现:OK(这是最常见的做法)
13.10 竞赛心态
不要追求完美
好的时间分配: 坏的时间分配:
┌──────────┐ ┌──────────┐
│ 30% 调研 │ │ 60% 调研 │
├──────────┤ │ │
│ 40% 实现 │ ├──────────┤
│ │ │ 30% 实现 │
├──────────┤ ├──────────┤
│ 20% 评估 │ │ 10% 评估 │
├──────────┤ └──────────┘
│ 10% 文档 │
└──────────┘
核心原则:
- “Done is better than perfect”
- 一个完整的简单方案 > 一个未完成的复杂方案
- 有实验数据的普通想法 > 没有验证的天才想法
- 代码能跑 > 论文能发
渐进式开发
v0.1:能编译通过的空壳
↓
v0.2:硬编码的简单逻辑
↓
v0.3:可配置的参数
↓
v0.4:自适应机制
↓
v0.5:边界处理和鲁棒性
↓
v1.0:完善的测试和文档
每个版本都是可演示的、可测试的。如果时间不够,v0.3 也是一个完整的成果。
本章小结
本章给出了从”读完导读”到”提交竞赛成果”的完整路径:
- 渐进式里程碑:从 RTT 测量开始,逐步构建调度器
- 代码导读地图:知道在 TQUIC 的哪里找什么
- 实现模板:可以直接用的 trait 实现骨架
- 评估方法:科学的实验设计和指标体系
- 竞赛策略:时间规划、方向选择、避坑指南
- PR 指南:从代码到提交的全流程
记住:竞赛的本质是”在有限时间内做出有价值的贡献”。不需要解决所有问题,只需要在一个点上做得比现有方案更好,并且能清晰地证明这一点。
误区警示
| 错误认知 | 正确理解 |
|---|---|
| “必须发明全新算法才能赢” | 对已有算法的精巧组合和工程优化同样有价值 |
| “代码写完就完了” | 没有实验数据和文档的代码不是完整的贡献 |
| “一个人闷头做” | 和 mentor、社区交流能避免方向错误 |
| “越复杂越好” | 评委更看重”简洁有效”而非”华而不实” |
| “最后一周冲刺” | 前期稳步推进 + 充足的缓冲时间才是正道 |
| ← 第 12 章:技术挑战 | 目录 |