计网 09|TCP 流量控制与拥塞控制:rwnd 滑动窗口 + 慢开始/拥塞避免/快重传快恢复

第一次做 2022 年那道拥塞控制的题,我看到「拥塞窗口从 16KB 超时后,再涨回 16KB 至少要几个 RTT」,条件反射写了「翻倍嘛,4 个 RTT 就够」。

结果答案是 11 个 RTT。

错在哪?我只记得「慢开始每 RTT 翻倍」,却忘了翻倍只在 cwnd 小于门限时才成立,一旦超过门限就变成「每 RTT 只加 1」的线性爬升。这两个阶段混为一谈,算出来就差三倍。

这篇把流量控制和拥塞控制两条线彻底分开讲,重点是那张 cwnd 变化曲线,以及超时和 3 个重复 ACK 两种「掉下去」的方式到底差在哪。

〇、先花两分钟:流量控制 vs 拥塞控制

这两个词长得像,但管的是两件事,用两个类比一下就懂:

  • 流量控制(flow control) :你往我这边发数据,我处理不过来了,我就告诉你「慢点发」。这是点对点的------只涉及你和我这一对收发方,目的是让接收方来得及收。

  • 拥塞控制(congestion control) :整条网络都堵车了,大家都在狂发数据,路由器缓存爆满开始丢包。这时候不是「你和我」的问题,而是全局的------目的是别让过多数据一股脑灌进网络,把路压垮。

一句话记法:流量控制是「我方吃不消,让你慢点」;拥塞控制是「路上堵车了,大家都要慢」。

实际发送时,发送方的窗口要同时受两者约束,取较小值:

发送窗口上限 = min(接收窗口 rwnd, 拥塞窗口 cwnd)

rwnd 是接收方说了算(我还能收多少),cwnd 是网络说了算(路上还能扛多少)。下面分两节,先讲 rwnd 怎么管流量,再讲 cwnd 怎么管拥塞。

图示:cwnd 随 RTT 的变化曲线------先指数上升(慢开始)到门限 ssthresh,再线性上升(拥塞避免),超时后骤降为 1,如此循环;并标注 ssthresh 切换点

一、发送窗口 = min(rwnd, cwnd):两个窗口各管什么

TCP 是全双工,通信双方各自维护自己的发送窗口和接收窗口。谈窗口必须先说清「是谁的窗口」:

窗口 由谁决定 限制什么 单位
接收窗口 rwnd 接收方(写在报文段「窗口」字段里通告) 发送方别发太快,我收不过来 字节
拥塞窗口 cwnd 发送方(自己估算网络状况) 别往网络里灌太多,防拥塞 字节(常换算成报文段个数)

报文段首部的「窗口」字段,填的是本方的接收窗口 rwnd ,不是发送窗口。发送方收到后,拿它和自己的 cwnd 取小,得到实际能发的发送窗口。

⚠️ 易错:窗口字段是「我方还能收多少」,不是「我方还能发多少」;它是接收窗口,不是发送窗口。

二、流量控制:rwnd 怎么让发送方慢下来

机制就是第 5 章讲过的滑动窗口 (以字节为单位)。连接建立时,接收方通告一个 rwnd,发送窗口 ≤ rwnd。接收方每确认一批数据,就带着最新的 rwnd 回来,发送窗口随之滑动。

教材经典的三级递减例子:接收方通告 rwnd 从 400 → 300 → 100 → 0,发送方的发送窗口同步缩小,最后被「卡住」不允许再发。

零窗口死锁 :接收方发了 rwnd=0 后,自己腾出了空间,又发了一个「rwnd=400」的报文------但这个报文在路上丢了。于是:发送方一直等非零窗口通知,接收方一直等发送方的数据,双方死锁。

解决办法:持续计时器(persistence timer) 。发送方一收到零窗口,就启动这个计时器;超时后发一个零窗口探测报文段(只带 1 字节数据),对方回确认时顺便带上当前窗口值。如果还是 0 就重设计时器,非 0 就打破死锁。

⚠️ 易错 :窗口为 0 时不是彻底静默------零窗口探测段、纯确认段、带紧急数据的段仍然能发。

三、拥塞控制一般原理:cwnd 和 ssthresh 是什么

发送方维持一个 cwnd(拥塞窗口)。假定对方接收能力足够大(rwnd 很大),那么发送窗口就 ≈ cwnd,发送方靠调节 cwnd 来控制注入网络的速率。

还有个关键变量 慢开始门限 ssthresh,它决定切换点:

  • cwnd < ssthresh → 用慢开始(指数增长)

  • cwnd > ssthresh → 用拥塞避免(线性增长)

  • cwnd = ssthresh → 两者皆可

记住这条总纲,下面四种算法就只是「在哪些点怎么改 cwnd 和 ssthresh」。

四、慢开始:每 RTT 翻倍(指数增长)

cwnd 从一个很小的值起步(老标准 1~2 个报文段,新 RFC 5681 为 2~4 个 SMSS)。每收到一个对新报文段的确认 ,cwnd 最多 +1 个 SMSS。

因为每经过一个 RTT,这一轮发出的报文段都会回来确认,相当于 cwnd 在每个 RTT 翻倍 。所以慢开始阶段是指数增长。

⚠️ 名字陷阱 :「慢开始」的「慢」只指起步小(先发 1 个试探),不是说增长慢------它其实是指数级暴涨,是所有阶段里涨得最快的。

五、拥塞避免:每 RTT +1(线性增长)

当 cwnd 超过 ssthresh,切到拥塞避免。这一阶段 cwnd 每个 RTT 只加 1 个报文段,线性缓慢爬升。

「避免」不是「完全不发生拥塞」,只是把增速从指数压成线性,让网络慢慢试探上限,别一下冲爆。

六、慢开始门限 ssthresh 的切换点

ssthresh 是「从翻倍改成 +1」的拐点。推演时每一步都要先比 cwnd 和 ssthresh:

  • 还没到门限:cwnd = cwnd × 2(慢开始)

  • 过了门限:cwnd = cwnd + 1(拥塞避免)

  • 正好等于门限:按惯例这一轮仍可算慢开始最后一次翻倍,下一轮起转拥避(具体看题设,两种处理分差很小,关键是别把「过门限后继续翻倍」)

七、超时怎么办:cwnd 骤降为 1,重来慢开始

一旦超时(说明网络很可能严重拥塞):

ssthresh = cwnd / 2,cwnd = 1

然后从 cwnd=1 重新开始慢开始。注意:超时是「最严重」的信号,cwnd 直接被打回 1,前面攒的窗口全废。

八、快重传:一连 3 个重复 ACK 立即重传

接收方收到失序报文段时,会立刻 发重复确认(不是等超时)。发送方只要一连收到 3 个重复确认 ,就推断只是丢了个别报文段(网络没那么糟),不等超时,立即重传那个缺失的段。这能把吞吐量提大约 20%。

九、快恢复:cwnd = ssthresh,不置 1(与超时的关键区别)

收到 3 个重复 ACK(不是超时)时,处理比超时温和:

ssthresh = cwnd / 2,cwnd = ssthresh(然后进入拥塞避免,线性增长)

⚠️ 最核心的易错点 :超时 → cwnd=1;3 个重复 ACK → cwnd=ssthresh(不置 1 )。二者门限都是 cwnd/2,但掉下去的「坑」深浅完全不同。考试就爱在这里挖坑,让你把 3-ACK 也写成 cwnd=1。

十、AIMD:把四种算法一条线串起来

TCP Reno 的拥塞控制本质是 AIMD(加法增大、乘法减小):

  • 加法增大(AI) :正常时每 RTT +1(拥塞避免阶段)

  • 乘法减小(MD) :一出问题就把 ssthresh 设为 cwnd/2(无论是超时还是 3-ACK)

四种算法的映射:

算法 触发 对 ssthresh 对 cwnd
慢开始 cwnd < ssthresh 不变 每 RTT ×2
拥塞避免 cwnd > ssthresh 不变 每 RTT +1
超时 超时 = cwnd/2 = 1
快重传/快恢复 收到 3 个重复 ACK = cwnd/2 = ssthresh

一句话:平时线性 +1,出问题门限减半;超时最狠掉到 1,3-ACK 只掉到门限。

十一、cwnd 数值推演算例(自编,单位:报文段)

初始 ssthresh = 16,cwnd = 1(慢开始)。约定 cwnd = ssthresh 时转拥塞避免。

RTT 阶段 cwnd ssthresh 说明
0 慢开始 1 16 初始
1 慢开始 2 16 ×2
2 慢开始 4 16 ×2
3 慢开始 8 16 ×2
4 慢开始→拥避 16 16 到门限,转拥避
5 拥避 17 16 +1
6 拥避 18 16 +1
7 拥避 19 16 +1
8 拥避 20 16 +1
9 拥避 21 16 +1
10 拥避 22 16 超时! ssthresh=11, cwnd=1
11 慢开始 1 11 新门限 11
12 慢开始 2 11 ×2
13 慢开始 4 11 ×2
14 慢开始 8 11 ×2
15 慢开始→拥避 11 11 到门限,转拥避
16 拥避 12 11 +1
17 拥避 13 11 +1

关键结论 :cwnd 在 [1, ssthresh] 指数增长,超过后线性增长;超时把它打回 1,3-ACK 只打到 ssthresh。

十二、17 年真题考在哪

数据口径 :以下年份题号来自 zhenti_all.json(2009--2025)计网部分(no 33--40),为单选题;2016、2023 年的综合大题含拥塞控制窗口变化,按公开统计口径列年份。

年份(题号) 考法 答案
2009(39) 拥塞窗口 16KB 超时后,接下来 4 个 RTT 成功传输量 C
2010(39) MSS=1000,cwnd=4000,连发两最大段后收确认,rwnd=2000 时发送窗口 A
2014(38) 超时 cwnd=8KB,10 个 RTT 后发送窗口(rwnd 恒 10KB) A
2015(39) 初始阈值 32KB,MSS=1KB,rwnd=16KB,数据全确认后发送窗口 A
2017(39) MSS=1KB,RTT=5ms,rwnd=64KB,发送窗口达 32KB 至少需多久 A
2020(38) MSS=1KB,RTT=2ms,cwnd 从 8KB 增到 32KB 最长时间 D(48ms)
2022(38) MSS=1KB,cwnd=16KB 超时,再增回 16KB 至少几个 RTT C(11 RTT)
2023(47) 综合大题:TCP 连接 + 拥塞控制窗口变化 大题
2025(38) 图示 seq/ack/rcwnd,慢开始阶段收到确认后还能发几段 A

命题规律 :①流量/拥塞控制几乎年年考,小题 + 大题都有;②最爱考数值推演(给 RTT、MSS、初始 cwnd,问若干 RTT 后窗口或时间);③必挖的坑就是「超时 vs 3-ACK 处理不同」「ssthresh 切换点之后不再翻倍」。

十三、易错点清单

易错点 正确说法
慢开始增长慢 错,是指数增长(每 RTT 翻倍),所有阶段最快
过了 ssthresh 还在翻倍 错,超过门限后改每 RTT +1(线性)
3 个重复 ACK 时 cwnd 置 1 错,cwnd = ssthresh(不置 1);只有超时才置 1
超时与 3-ACK 的 ssthresh 不同 错,两者都是 ssthresh = cwnd/2
报文段「窗口」字段是本方发送窗口 错,是本方接收窗口 rwnd
窗口为 0 时彻底不能发 错,零窗口探测/确认/紧急数据仍可发
发送窗口只看 cwnd 错,取 min(rwnd, cwnd)
2020(38) 用慢开始算最短时间 错,问「最长」时间要假设 ssthresh ≤ 起点,全程拥避 +1

速查表(文末收藏版)

复制代码
发送窗口上限 = min(rwnd, cwnd)
rwnd:接收窗口,接收方通告,单位字节,限制「接收方来得及收」
cwnd:拥塞窗口,发送方自估,限制「网络扛得住」
​
慢开始   cwnd < ssthresh  →  每 RTT ×2(指数)
拥塞避免 cwnd > ssthresh  →  每 RTT +1(线性)
切换点   cwnd = ssthresh  →  转拥避
​
超时(严重)    ssthresh = cwnd/2,cwnd = 1        ← 打回原形
3 个重复 ACK    ssthresh = cwnd/2,cwnd = ssthresh  ← 只掉到门限(快恢复)
​
AIMD:加法增大(+1/RTT) + 乘法减小(ssthresh 减半)
零窗口死锁 → 持续计时器 + 1 字节零窗口探测
​
真题高频:2009(39) 2010(39) 2014(38) 2015(39) 2017(39) 2020(38) 2022(38) 2023(47) 2025(38)

本文依据 :谢希仁《计算机网络(第 8 版)》第 5 章 5.7--5.8;真题年份题号来自 zhenti_all.json(2009--2025 计网 no 33--40);考频口径为公开统计,★ 少不等于不考。

相关推荐
CQU_JIAKE2 小时前
10.29计算机网络
计算机网络
流浪0016 小时前
计算机网络篇13:应用层——HTTP 不记录用户状态,Cookie 在客户端存凭证、Session 在服务端存数据
计算机网络·cookie·session·http协议
希和20277 小时前
考研高数综合题|微分方程 + 平面面积 + 旋转体体积
考研·平面
Dwzun10 小时前
基于SpringBoot3+Vue3的油莎豆种植管理系统
java·前端·vue.js·spring boot·后端·python·计算机网络
听风说ml19 小时前
socket编程 【C语言】
c语言·udp·tcp·socket编程
lazy H1 天前
【计算机网络】第 13 篇:NAT 和端口映射是什么?为什么本地服务外网访问不了?
java·网络·网络协议·学习·计算机网络·408
希和20271 天前
考研高数|曲率圆选择题【拉格朗日版本解法】
考研
AI你一生一世2 天前
当“暂停“成为架构决策:从签证系统停摆看高可用设计的边界
架构·系统架构·流量控制·幂等性·高可用设计·优雅降级·状态一致性
我不会起名字3222 天前
TCP 三次握手为什么是三次:丢包、重传与初始序列号
网络·tcp·原理·握手