Appearance
TCP 拥塞控制典型计算题
2026 大纲 五(三)5 TCP 拥塞控制。本篇是《TCP 拥塞控制》的计算配套篇,只处理"给定初始参数与事件序列,逐轮推出 cwnd / ssthresh / 发送窗口"这一类问题;四种算法的机制与推导一概不重讲、只引用。
一、八成的错来自口径,不是算错
上一篇把四种算法讲完了,规则本身并不难。可这类题的失分率却很高——因为规则的边界处有四个含糊点,题面往往不说明,而不同的取法会让整张表整体错开。
所以做题的第一件事不是算,是把四个口径钉死。
口径①:「第
口径②:cwnd = ssthresh 时算哪个阶段。 教材原话是"既可使用慢开始算法,也可使用拥塞避免算法"(标准留下的两可)。本站统一按拥塞避免,即从这一轮起 +1。题面另有说明以题面为准。
口径③:慢开始翻倍会被 ssthresh 封顶。 依据是教材图 5-25 的点③——超时后 ssthresh 被调成 12、cwnd 从 1 重新慢开始,教材写的是"当拥塞窗口 cwnd = ssthresh = 12 时,改为执行拥塞避免算法"。若允许无脑翻倍,
口径④:ssthresh 除 2 向下取整。 cwnd 为奇数时取
两条最容易踩空的边界
其一:事件处理一律用"事件发生时的旧 cwnd"算 ssthresh,顺序不能反。 先算 ssthresh,再设 cwnd。反过来先设 cwnd 再算 ssthresh,结果差一倍。
其二:rwnd 从不参与拥塞控制的状态更新。
还有两处小提醒。cwnd 的单位多数题目用 MSS,也可能用字节——用字节时"翻倍""+1"都要换算成"加一个 MSS",审题时先确认。ssthresh 只在事件发生时改变,平时保持不变;所以只问"某一轮的 ssthresh"时不必逐轮算 cwnd,找到最近一次事件发生在哪一轮、当时 cwnd 是多少即可。
二、两张表 + 四步流程
规则转自《TCP 拥塞控制》,本篇不另立口径。
逐轮规则(正常传输时):
| 本轮阶段 | 判定条件 | 下一轮 cwnd |
|---|---|---|
| 慢开始 | cwnd < ssthresh | |
| 拥塞避免 | cwnd ≥ ssthresh |
事件分支:
| 事件 | TCP Reno | TCP Tahoe |
|---|---|---|
| 超时 | 与 Reno 完全相同 | |
| 3 个重复确认 | 当超时处理: |
做题四步:
第一步,列出初始参数(cwnd 初值、ssthresh 初值、有无 rwnd),并确认题面的事件措辞属于口径 ① 的哪一种。
第二步,画表格,列固定为——轮次 / cwnd / ssthresh / 阶段 / 事件 / 发送窗口。有 rwnd 才写最后一列。
第三步,逐行推进,每行的动作顺序是:① 先按 cwnd 与 ssthresh 的大小关系判定本轮阶段;② 填本轮的 cwnd、ssthresh、发送窗口;③ 看本轮末有没有事件——有事件就先算新 ssthresh 再设新 cwnd;无事件就按本轮阶段的规则算下一轮 cwnd(慢开始翻倍且封顶到 ssthresh,拥塞避免 +1)。
第四步,用下面四条自查法扫一遍。
三、四条自查法
- 慢开始段是不是纯翻倍。 应该是
。中间出现不是翻倍的值,只有一种合法解释——被 ssthresh 封顶,且那个值必须恰好等于当前 ssthresh;不等就是算错了。 - 事件后的 ssthresh 一定小于事件发生时的 cwnd(因为它是那个 cwnd 的一半再向下取整)。算出比它还大的必错。
- 拥塞避免段必须每轮恰好 +1。 出现跳跃或翻倍说明阶段判定错了——大概率是"cwnd = ssthresh 那一轮"判成了慢开始。
- 快恢复后 cwnd 绝不为 1。 Reno 的 3 个重复确认后
。cwnd 降到 1 只有两种可能:要么这是超时事件,要么题目用的是 Tahoe。
再补一条口径自查:把最终答案整体前移或后移一轮,看看是不是更符合题面措辞——若题面是"当 cwnd = X 时发生超时"而你按"第 X 轮末"处理了,结果会整体错开一轮。
Reno 的完整逐轮推演与同题换 Tahoe 的对照(第一次做这类题、或想看清两类事件怎么落在表里时展开)
某 TCP 连接使用 Reno 算法,初始
MSS, MSS。第 6 轮末发送方收到 3 个重复确认,第 13 轮末发生超时。求第 0~18 轮每一轮的 cwnd 与 ssthresh。
| 轮次 | cwnd | ssthresh | 本轮阶段 | 本轮末事件 | 下一轮 cwnd 的由来 |
|---|---|---|---|---|---|
| 0 | 1 | 8 | 慢开始 | — | |
| 1 | 2 | 8 | 慢开始 | — | |
| 2 | 4 | 8 | 慢开始 | — | |
| 3 | 8 | 8 | 拥塞避免(cwnd = ssthresh,按口径②) | — | |
| 4 | 9 | 8 | 拥塞避免 | — | |
| 5 | 10 | 8 | 拥塞避免 | — | |
| 6 | 11 | 8 | 拥塞避免 | 3 个重复确认 | ① |
| 7 | 5 | 5 | 拥塞避免(cwnd = ssthresh) | — | |
| 8 | 6 | 5 | 拥塞避免 | — | 7 |
| 9 | 7 | 5 | 拥塞避免 | — | 8 |
| 10 | 8 | 5 | 拥塞避免 | — | 9 |
| 11 | 9 | 5 | 拥塞避免 | — | 10 |
| 12 | 10 | 5 | 拥塞避免 | — | 11 |
| 13 | 11 | 5 | 拥塞避免 | 超时 | ① |
| 14 | 1 | 5 | 慢开始 | — | |
| 15 | 2 | 5 | 慢开始 | — | |
| 16 | 4 | 5 | 慢开始 | — | |
| 17 | 5 | 5 | 拥塞避免 | — | |
| 18 | 6 | 5 | 拥塞避免 | — | 7 |
cwnd 序列:1 2 4 8 9 10 11 | 5 6 7 8 9 10 11 | 1 2 4 5 6
三处关键步骤的"为什么":
- 第 3 轮为什么直接进拥塞避免。 慢开始从 4 翻倍到 8,恰好等于 ssthresh,按口径② 执行拥塞避免,所以第 4 轮 +1 而不是翻倍。这一步定错后面每一行都会错——"翻倍到刚好等于 ssthresh 的那一轮"算慢开始还是拥塞避免,直接决定下一轮是 16 还是 9。
- 第 6 轮为什么用 11 除 2 而不是用 5。 ssthresh 要用事件发生时的 cwnd(第 6 轮那个 11)算:
,然后才把 cwnd 设成 5。反过来先设 cwnd 再算 ssthresh,会得到 ,整条后续全错。 - 第 16 轮为什么是 5 不是 8。 慢开始翻倍
已越过 ssthresh = 5,按口径③ 封顶到 5。全表只有这一格用到"封顶"规则,也是最容易写成 8 的地方。
同一题其余条件不变、改用 TCP Tahoe: 唯一改动是 3 个重复确认当超时处理(
| 轮次 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Reno cwnd | 11(事件) | 5 | 6 | 7 | 8 | 9 | 10 | 11(超时) | 1 | 2 | 4 | 5 | 6 |
| Tahoe cwnd | 11(事件) | 1 | 2 | 4 | 5 | 6 | 7 | 8(超时) | 1 | 2 | 4 | 5 | 6 |
- Reno:
1 2 4 8 9 10 11 5 6 7 8 9 10 11 1 2 4 5 6 - Tahoe:
1 2 4 8 9 10 11 1 2 4 5 6 7 8 1 2 4 5 6
三个值得注意的差别:① 第 7 轮就分岔,Reno 回到 5、Tahoe 回到 1;② Tahoe 的第 9→10 轮出现封顶(
第 ③ 点最容易漏:事件本身的处理规则相同,但代入的 cwnd 不同,结果自然不同。 不要以为"超时处理一样"就可以直接抄 Reno 的数。第 14 轮之后 Tahoe 的 ssthresh 是 4,慢开始
到第 16 轮就等于 ssthresh 了,数值上与 Reno 恰好重合,但阶段判定的理由不同。
题目给了 rwnd 时:表多一列,但 cwnd 的演变一点不变。 设 Reno、
| 轮次 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
|---|---|---|---|---|---|---|---|
| cwnd | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 发送窗口 = min(cwnd, 12) | 12 ← 受限 | 12 | 12 | 12 | 12 | 12 | 12 ← 受限 |
第 4~10 轮的发送窗口全部被 rwnd 卡在 12(共 7 轮)。而第 10 轮末超时时,新
这一格最容易踩空:发送窗口只有 12,但拥塞控制的状态变量是 cwnd,事件处理一律用 cwnd。 用 12 去除会得到 6,整条后续全错。rwnd 从不参与拥塞控制的状态更新。
问"前
以 1 2 4 8 16 32 33 34 35 36(第 5 轮
与逐个累加的结果一致 ✓。两段闭式对不上,就说明阶段分界点判错了——最容易出问题的还是"cwnd = ssthresh 那一轮"归错了阶段。
给出 cwnd 逐轮取值,反推事件类型与各次 ssthresh(想掌握反推题的通用套路时展开)
某 TCP Reno 连接的 cwnd 逐轮取值(单位 MSS):
1, 2, 4, 8, 16, 17, 18, 19, 20, 10, 11, 12, 13, 1, 2, 4, 6, 7。求初始 ssthresh、各次事件的轮次与类型、以及每次事件后的新 ssthresh。
第 1 步:按"相邻两轮怎么变"分类。 判据只有五条:
| 相邻变化 | 判定 |
|---|---|
| 翻倍( | 慢开始 |
| 加 1( | 拥塞避免 |
| 降到 1 | 超时 |
| 降到大于 1 的值 | 3 个重复确认(快恢复) |
| 既不翻倍也不 +1 的上升 | 慢开始被 ssthresh 封顶,此时 |
第 2 步:逐段判。
| 轮次段 | cwnd 变化 | 判定 |
|---|---|---|
| 0→4 | 1,2,4,8,16 翻倍 | 慢开始 |
| 4→5 | 16→17 加 1 | 转入拥塞避免 ⟹ 初始 ssthresh = 16 |
| 5→8 | 17,18,19,20 加 1 | 拥塞避免 |
| 8→9 | 20 → 10,降到大于 1 | 第 8 轮末:3 个重复确认;新 |
| 9→12 | 10,11,12,13 加 1 | 拥塞避免(cwnd = ssthresh 起步即 +1) |
| 12→13 | 13 → 1 | 第 12 轮末:超时;新 |
| 13→15 | 1,2,4 翻倍 | 慢开始 |
| 15→16 | 4 → 6,既非翻倍(应为 8)也非 +1 | 慢开始被封顶,故 |
| 16→17 | 6→7 加 1 | 拥塞避免 |
第 3 步:交叉验证。 这一序列里有两处可以互证:第 8 轮末算出
答案:初始 ssthresh = 16;第 8 轮末收到 3 个重复确认(ssthresh 变 10);第 12 轮末超时(ssthresh 变 6)。
反推题的通用套路就是这两步:先用"降到 1 还是降到大于 1"分出事件类型,再用"封顶值"和"快恢复后的 cwnd"回头验证 ssthresh。 只要有一处对不上,就说明前面某一段的阶段判错了。
本节小结
- 八成的错来自口径不统一,不是算错。 四个口径必须先钉死:「第
轮末发生事件」的影响从第 轮体现(题面写"当 cwnd = X 时"则直接在该点处理,两种措辞整体错开一轮);cwnd = ssthresh 时按拥塞避免;慢开始翻倍封顶到 ssthresh;ssthresh 除 2 向下取整。 - 两条最容易踩空的边界:事件处理一律用事件发生时的旧 cwnd 算 ssthresh,顺序反了差一倍;rwnd 只参与
取小,从不参与 cwnd 的状态更新,慢开始封顶用的也是 ssthresh。 - 表填完必扫四条自查:慢开始段除封顶格外纯翻倍且封顶值恰等于当前 ssthresh/事件后的 ssthresh 一定小于事件时的 cwnd/拥塞避免段每轮恰好 +1/快恢复后 cwnd 绝不为 1(是 1 就只能是超时或 Tahoe)。
考点速记
本篇处理的正是真题里被考过的形式中最难的那一类:cwnd 与 rwnd 两条线同时跟踪。 三道题恰好把三种"该看哪条线"的情形各考了一遍。
① 只有 cwnd 一条线:慢开始要几轮(cn-2017-39)。MSS = 1 KB、RTT = 5 ms、乙的接收缓存 64 KB,问从连接建立成功到发送窗口达到 32 KB 最少需要多久。
先判 rwnd 会不会成为瓶颈:接收缓存 64 KB > 目标 32 KB,全程不受限,所以只跟 cwnd 一条线。慢开始
选项 B(30 ms)是多算了一轮(把"连接建立"那 1 个 RTT 也算进去了,可题面明说"从连接建立成功"起算);C(160 ms)与 D(165 ms)则是按拥塞避免算的——每轮 +1,从 1 涨到 32 要 31 轮。这道题的机关在"最少"两个字:最少就走慢开始。
② 两条线都要跟,rwnd 最后封顶(cn-2014-38)。甲在
先执行事件动作:
| RTT | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
|---|---|---|---|---|---|---|---|---|---|---|
| 本轮 cwnd | 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 阶段 | 慢开始 | 慢开始 | 拥塞避免 | 拥塞避免 | 拥塞避免 | 拥塞避免 | 拥塞避免 | 拥塞避免 | 拥塞避免 | 拥塞避免 |
10 个 RTT 之后 cwnd 涨到 12 KB,但 rwnd 一直是 10 KB → 发送窗口
这道题两处都能栽:一是第 3 轮 cwnd 恰好等于 ssthresh = 4,按口径② 要转拥塞避免(判成慢开始会得到 8 而不是 4,后面全错);二是最后一步忘了取 min,直接答 12——而 12 恰好不在选项里,反倒提示你漏了一步。选项里的 14、15 则是阶段判错后的产物。
③ 一个 ACK 之后还能发几段:要扣掉已发未确认(cn-2025-38)。甲在
这道题最容易错的不是拥塞控制,是对 ack_seq 的理解。 三步:
第一步,cwnd 涨多少。
慢开始不是"翻倍",是"每 ACK 加 1 个 MSS"。 只有当一个 RTT 内发出的段被同时确认时,效果上才等于翻倍。本题只收到 1 个 ACK,所以只加 1 个 MSS。
第二步,发送窗口。
第三步,扣掉已发未确认。 甲在 seq=2001(占 2001~3000)和 seq=3001(占 3001~4000)。收到的确认是 ack_seq=3001——它表示"期待 3001",也就是 3001 那一段还没被确认。所以已发未确认 = 1000 B。
答 A。把 ack_seq=3001 读成"seq=3001 那段已确认"的人会算出 3 段甚至更多——ack_seq = N 永远表示"期待 N",意味着
本篇练习区里还会出现四道题:cn-2009-39、cn-2020-38、cn-2022-38 只跟 cwnd 一条线,讲在 TCP 拥塞控制;cn-2015-39 的 rwnd 会被一路榨干到 1 KB,讲在 TCP 流量控制。
本篇的四个口径与两个折叠推演在真题里不单独成题,但它们是上面三道题不出错的前提——尤其是口径②(cwnd = ssthresh 归拥塞避免),cn-2014-38 的第 3 轮正卡在这一格上。
易错:cwnd 恰好等于 ssthresh 那一轮按拥塞避免算(口径②),判成慢开始会让后面每一行都错。
易错:先用事件发生时的旧 cwnd 算 ssthresh,再设新 cwnd。 顺序反了差一倍。
易错:最后一步别忘了取
。 题面给了接收缓存或 rwnd 就一定要跟这条线。
易错:rwnd 只参与取 min,从不参与 cwnd 的状态更新。 算 ssthresh 一律用 cwnd。
易错:慢开始的规则是"每收到一个 ACK 加 1 个 MSS",收到几个就加几个;"翻倍"只是一个 RTT 内全部被确认时的效果。
易错:
ack_seq = N表示"期待 N",N 及以后的还没被确认——算已发未确认时全靠这一条。
易错:慢开始翻倍会被 ssthresh 封顶(口径③),不会冲过去再退回来。
易错:问"最少时间"走慢开始、问"最长时间"走拥塞避免。
教材出处
- 本篇的全部动作规则、口径依据与教材页码,见 TCP 拥塞控制 · 教材出处。本篇不重复列出,只标注三处直接支撑本篇口径的原文:
- 谢希仁《计算机网络》(第 8 版)p243:"当 cwnd < ssthresh 时,使用上述的慢开始算法。当 cwnd > ssthresh 时,停止使用慢开始算法而改用拥塞避免算法。当 cwnd = ssthresh 时,既可使用慢开始算法,也可使用拥塞避免算法。" ——这是口径②的来源(标准留两可,本站统一取拥塞避免)。
- 同书 p243:图 5-25 点③的叙述 "按照慢开始算法,发送方每收到一个对新报文段的确认 ACK,就把拥塞窗口值加 1。当拥塞窗口 cwnd = ssthresh = 12 时(图中的点③,这是 ssthresh 第 1 次调整后的数值),改为执行拥塞避免算法" ——超时后 ssthresh 被调为 12,cwnd 从 1 重新慢开始,若允许无脑翻倍则序列为 1,2,4,8,16,永远取不到 12 这个值;教材明确写 cwnd 到达 12,即慢开始被 ssthresh 封顶。这是口径③的直接依据。
- 同书 p244:快恢复的三步 "发送方第 2 次调整门限值,使 ssthresh = cwnd / 2 = 8,同时设置拥塞窗口 cwnd = ssthresh = 8,并开始执行拥塞避免算法" ——先算 ssthresh 再设 cwnd 的顺序,以及"Reno 快恢复后 cwnd 等于新 ssthresh"这条自查依据。