📚 第六章 TCP与UDP
📄 一句话概括

慢启动不慢,它每轮翻倍:「慢」指的是起点小。 本质是「加性增、乘性减」:慢慢加,一丢就砍半。

📝 本节和前面几节的关系

6.4.8 的流控制是「接收方管」,这一节是「发送方自己管自己」。 5.8.4 的 ECN 是这个机制的「温和版」,这里能看到它想替代什么。

一、为什么要自己管自己

流控制只考虑「接收方收不收得下」,完全不考虑「网络受不受得了」: 如果所有发送方都按接收窗口猛发,中间链路就会堵死。 而路由器堵到极限时的处理方式很粗暴:丢包。 所以 TCP 必须自己「试探」网络的容量:先小量发,慢慢加,一发现丢包就往回退。 这套试探机制就是拥塞控制,它维护的那个值叫 cwnd。 注意 cwnd 是「发送方自己猜的」:网络并不会告诉它「我能承受多少」, 它只能靠「丢没丢包」来推断。 这种「靠反馈推断」的控制,天生就有滞后和震荡。

二、三个阶段

阶段 cwnd 怎么变 什么时候
慢启动 每轮翻倍 刚开始,或超时之后
拥塞避免 每轮加一 超过 ssthresh 之后
快速恢复 减半后加一 三次重复确认之后

第一行「翻倍」是指数增长,从 1 涨到 16 只要四轮。 所以「慢启动」这个名字容易误解——它慢在「起点是 1」,而不是「增长慢」。 真正的意思是「谨慎地开始」,而不是「缓慢地增长」。 第二个阶段是「线性增长」:每轮加一个 MSS,慢慢试探上限。 这两个阶段的分界点叫 ssthresh,它是个「我猜大概到这儿就该堵了」的估计值。

三、加性增、乘性减

第三行的「减半」是整个机制的关键: 涨的时候一点一点涨(加一),退的时候直接砍一半(乘 0.5)。 这个不对称叫 AIMD。 为什么不对称是对的: 如果退的时候也只减一点,那网络堵了还慢慢退,会更堵;如果涨的时候也翻倍,那会很快又撞墙。 「慢涨快退」在数学上能证明「多个连接最终会收敛到公平分配」—— 这就是为什么「同时开很多下载,速度会趋于平均」。 注意「公平」是对「往返时间相同」的连接说的:往返短的连接涨得快,所以会抢到更多带宽。 这是 TCP 的一个已知偏差,也是「为什么同一份文件,近的服务器下得更快」的一个原因。

四、动手 Demo:慢启动到底慢在哪

从 cwnd 等于 1 开始,跑到第 6 轮发生丢包,看它怎么退。 注意第 6 轮之后的那个「1」。

cwnd, ssthresh = 1, 16
for rnd in range(1, 11):
    phase = '慢启动' if cwnd < ssthresh else '拥塞避免'
    print(rnd, cwnd, phase, ssthresh)
    if rnd == 6:
        ssthresh, cwnd = max(2, cwnd // 2), 1
        continue
    cwnd = cwnd * 2 if cwnd < ssthresh else cwnd + 1

实跑输出:

--------------------------------------------------------------------
慢启动到底慢在哪
--------------------------------------------------------------------
**「慢启动」这个名字有点误导**:**它增长得很快,每轮翻倍,而「慢」指的是「起点小」。**

  轮次     cwnd     处于哪个阶段         ssthresh
  ------------------------------------------------
  1        1        慢启动(翻倍)       16
  2        2        慢启动(翻倍)       16
  3        4        慢启动(翻倍)       16
  4        8        慢启动(翻倍)       16
  5        16       拥塞避免(加一)     16
  6        17       拥塞避免(加一)     16
  7        1        慢启动(翻倍)       8
  8        2        慢启动(翻倍)       8
  9        4        慢启动(翻倍)       8
  10       8        拥塞避免(加一)     8

**前四轮是指数增长**:**1、2、4、8**,**每一轮把「在路上的数据量」翻一倍。**
**所以「慢启动」从 1 涨到 16 只要四个往返**——**真正慢的是「每次丢包都要从 1 重来」。**

**第六轮打完就丢包了**:
**ssthresh 被砍成当前窗口的一半(17 的一半取整是 8),cwnd 回到 1,从第七行重新慢启动。**
**所以「拥塞控制」的本质是「用一个乘法增长、再用一个除法退避」**——
**这就是「加性增、乘性减」(AIMD)**,**也是「多个连接抢带宽时最终会比较公平」的数学原因。**

前四轮是指数增长:1、2、4、8,每一轮把「在路上的数据量」翻一倍。 所以从 1 涨到 16 只要四个往返——真正慢的是「每次丢包都要从 1 重来」。 第六轮打完就丢包了:ssthresh 被砍成当前窗口的一半(17 取整是 8),cwnd 回到 1。 于是第七轮又从 1 开始。 这也解释了「网络一抖,下载速度就掉一半」:因为丢包触发了「砍半」,而重新涨回去要很多轮。 所以「稳定的高速」比「峰值高」更难做到——它需要「几乎不丢包」的链路。

30 秒实验

  1. 算一下:cwnd 从 1 到 16 要几个往返?
  2. 想一下:为什么退的时候砍半、涨的时候加一?
  3. 想一下:为什么往返短的连接会抢到更多带宽?

常见误区

  1. 以为「慢启动增长慢」。 它每轮翻倍,慢的是「起点小」。
  2. 以为「拥塞控制是接收方管的」。 是发送方自己猜的,靠丢包推断。
  3. 以为「AIMD 对所有人绝对公平」。 往返短的占便宜,公平性有偏差。
自测三问(点开对答案) 1. 慢启动和拥塞避免的区别? —— **慢启动每轮翻倍,拥塞避免每轮加一**,分界点是 ssthresh。 2. AIMD 是什么意思? —— **加性增、乘性减**:涨的时候线性加,丢包时砍半。 3. 为什么这个机制能收敛到公平? —— **慢涨快退**的组合让多个连接最终稳定在接近平均的份额上。

下一节讲几个「提高利用率」的小技巧。

相关笔记