Skip to content

第一百一十七章:拥塞

问道期

涉及内核源码:

林小源走在一条宽阔的水道上,水道两侧是高耸的石壁。水面上漂浮着无数光点——每一个都是一个数据包,争先恐后地向前涌去。

"慢点!慢点!"一个尖锐的声音从前方传来。

林小源看见一个瘦削的青年站在水道中央,双臂张开,像是要拦住所有的光点。他穿着一件灰色的袍子,上面绣着一条指数增长的曲线,从底部急速攀升。

"你是谁?"

"慢启动。"青年说,声音有些急促,"我在控制流量——你看看这水道,再不减速就要溢了。"

林小源向前看去——水道越来越窄,前方有一个巨大的闸门,光点在闸门前挤成一团,有些被挤碎了,化为虚无。

"那些碎掉的是……"

"丢包。"慢启动的脸色难看,"太多数据包同时涌向网络,路由器的缓冲区溢出,数据包就被丢弃了。这就是拥塞。"

林小源看着那些消散的光点,感到一阵心痛:"那怎么办?"

"所以我在。"慢启动深吸一口气,"我不让发送方一开始就全速发送——从一个 MSS 开始,每收到一个 ACK 就翻倍。指数增长,但起步很慢。像探路一样——先伸出一只脚,确认安全了再迈下一步。"

水道渐渐变宽,闸门前的拥堵缓解了。林小源看到一个沉稳的中年人从旁边走来,他的步伐均匀而缓慢,每一步都踩在固定的节拍上。

"拥塞避免。"慢启动介绍道。

"你在指数增长阶段表现不错。"拥塞避免对慢启动说,"但到了 ssthresh 就该换我了——线性增长,每 RTT 加一个 MSS。"

林小源看着两人交接——慢启动退后,拥塞避免走上前,步伐立刻变得平稳。"为什么不一直用指数增长?快多了。"

拥塞避免回头看了他一眼:"太快会出事。cwnd 越大,网络承受的压力越大。指数增长只能用来探测——到了阈值就该收敛,用线性增长小心翼翼地逼近极限。"

"那如果还是拥塞了呢?"

"那就要快重传和快恢复。"拥塞避免指向远处。

林小源看到另一个区域——一个敏捷的少年正在快速移动,手中握着三面小旗。每当他收到三个重复的 ACK,就立刻重传丢失的包,不等超时。

"三个重复 ACK 说明后面的包到了,中间那个丢了。"少年喊道,"不等超时,立刻重传!然后把 cwnd 减半,进入拥塞避免——这就是快恢复。"

林小源在水道的尽头看到了一块巨大的石碑,上面刻着三种名字:Reno、CUBIC、BBR。

"这是拥塞控制的三大门派。"一个苍老的声音说。

林小源转身,看到一个白发老者坐在石碑旁,手中握着一根拐杖,拐杖上刻着一条三次函数曲线。

"我是 CUBIC。"老者说,"Reno 是老前辈,用线性增长。我用三次函数——恢复更快,更适合高带宽网络。"

"BBR 呢?"林小源问。

老者指向远方——那里有一个奇怪的身影,他不像其他人那样盯着丢包,而是不停地测量着什么。"BBR 不看丢包,它看带宽和延迟。传统拥塞控制认为丢包就是拥塞——但无线网络丢包不一定是拥塞,可能是干扰。BBR 测量实际可用带宽和最小 RTT,更准确地判断网络状态。"

"那 Reno 呢?"

"经典,但简单。"老者站起身,"快重传加快恢复,遇到超时就回到慢启动。在高带宽长延迟的网络中表现不好——恢复太慢。但它是基础,后来的算法都是在它之上改进的。"

林小源看着石碑上的三个名字,忽然明白了——拥塞控制不是一种算法,而是一个不断演化的领域。每种算法都在寻找同一个平衡点:既不浪费带宽,又不造成拥塞。

c
/*
 * 拥塞控制的核心算法:
 *
 * 1. 慢启动 (Slow Start)
 *    cwnd 从 1 MSS 开始
 *    每收到一个 ACK,cwnd++
 *    cwnd 指数增长
 *
 * 2. 拥塞避免 (Congestion Avoidance)
 *    cwnd 达到 ssthresh 后
 *    cwnd 线性增长
 *
 * 3. 快重传 (Fast Retransmit)
 *    收到 3 个重复 ACK
 *    立即重传丢失的包
 *
 * 4. 快恢复 (Fast Recovery)
 *    ssthresh = cwnd / 2
 *    cwnd = ssthresh
 *    进入拥塞避免
 *
 * 拥塞检测:
 *   超时: cwnd = 1, 慢启动
 *   重复 ACK: 快重传 + 快恢复
 */

printf("=== TCP 拥塞控制 — 避免网络过载 ===\n\n");

printf("拥塞控制的核心算法:\n\n");

printf("1. 慢启动:\n");
printf("   cwnd = 1 MSS\n");
printf("   每个 ACK: cwnd++\n");
printf("   cwnd: 1 → 2 → 4 → 8 → 16...\n\n");

printf("2. 拥塞避免:\n");
printf("   cwnd >= ssthresh\n");
printf("   每个 RTT: cwnd++\n");
printf("   cwnd 线性增长\n\n");

printf("3. 快重传:\n");
printf("   收到 3 个重复 ACK\n");
printf("   立即重传\n\n");

printf("4. 快恢复:\n");
printf("   ssthresh = cwnd / 2\n");
printf("   cwnd = ssthresh\n");
printf("   进入拥塞避免\n\n");

printf("--- 拥塞窗口变化 ---\n");
printf("cwnd\n");
printf("  │    /\\      拥塞\n");
printf("  │   /  \\\n");
printf("  │  /    \\   快恢复\n");
printf("  │ /      \\  /\n");
printf("  │/ 慢启动 \\/\n");
printf("  └──────────── time\n\n");

printf("--- 拥塞算法 ---\n");
printf("Reno:\n");
printf("  经典算法\n");
printf("  快重传 + 快恢复\n\n");
printf("CUBIC:\n");
printf("  默认算法\n");
printf("  三次函数增长\n\n");
printf("BBR:\n");
printf("  Google 提出\n");
printf("  基于带宽和延迟\n");

道藏笔记

内核启示

TCP 拥塞控制避免网络过载。

拥塞控制算法:

  • 慢启动 — cwnd 指数增长
  • 拥塞避免 — cwnd 线性增长
  • 快重传 — 收到 3 个重复 ACK 立即重传
  • 快恢复 — cwnd 减半后进入拥塞避免

拥塞算法:

  • Reno — 经典算法
  • CUBIC — 默认算法,三次函数增长
  • BBR — 基于带宽和延迟

拥塞控制是"探测"——让 TCP 适应网络条件。


破关试炼

拥塞控制之试

拥塞控制一章讨论慢启动、拥塞窗口和退让,主要服务于哪种传输协议?

答对后才能继续滑动和进入下一章。

以修仙之名,悟内核之道