深入理解 TCP 的流量控制与拥塞控制
深入理解 TCP 的流量控制与拥塞控制
在网络通信中,TCP(Transmission Control Protocol,传输控制协议) 是最常用、最基础的传输层协议之一。它负责在不可靠的网络上提供 可靠的数据传输。 为保证数据既不丢、不乱,又能尽量高效地传输,TCP 引入了许多机制,其中最重要的两个是:
- 流量控制(Flow Control)
- 拥塞控制(Congestion Control)
这两个机制名字相似,但解决的问题完全不同。
1. 为什么需要流量控制?
1.1 接收方处理能力有限
设想一个场景:发送方(Sender)是一台性能很强的服务器,而接收方(Receiver)是一台老旧的电脑。
如果发送方以极快的速度连续发包,接收方的缓存区(Buffer)来不及处理这些数据,缓冲区会被填满。 当新数据还在源源不断地到达,就会出现:
- 缓冲区溢出(Buffer Overflow)
- 数据被丢弃(数据还没来得及处理就被覆盖)
导致虽然 TCP 是“可靠传输协议”,但由于接收方过载,可靠性也会失效。
1.2 流量控制的目标
让发送方的发送速率不要超过接收方的接收速率。
流量控制是“点对点”的控制机制,控制的是“发送方—接收方”之间的流速匹配问题。
2. 流量控制的实现
2.1 实现机制:滑动窗口(Sliding Window)
TCP 使用 接收窗口(rwnd, receive window) 来实现流量控制。
2.1.1 工作原理
- 接收方在 TCP 报文首部中会告诉发送方: 「我当前还能接收的数据量是多少(即窗口大小 rwnd)」
- 发送方根据这个窗口大小,限制自己可以连续发送的数据量。
- 当接收方处理完一部分数据后,会更新 rwnd,让发送方继续发。
2.1.2 工作流程
(1) 发
发送方可以发送窗口内所有“未发送且可发送”的数据(比如这里的 6)。
(2) 等
当接收方收到数据后,会返回一个新的 ACK,告知:“我已经收到某个序号前的所有数据。”
(3) 滑
发送方收到新的 ACK 后,会:
- 把窗口下边界右移(因为旧数据已确认);
- 并将窗口上边界也右移(新的数据可以发了)。
当接收方缓冲区满时,可能会通告 win=0,这意味着:“暂时不要再发数据了。”
此时发送方会暂停发送,进入零窗口探测(Zero Window Probe)模式,定期探测接收方是否恢复接收能力。
3. 为什么需要拥塞控制?
3.1 网络链路资源有限
即使接收方能处理得很快,也不代表整个网络能承受那么多流量。
想象一条路上只有两车道,如果大家都疯狂往路上开车,就会出现:
- 网络中路由器缓存被填满(队列溢出)
- 数据包被丢弃(丢包)
- 重传增多,进一步加剧网络拥塞
最终结果是:整个网络都慢下来了。
3.2 拥塞控制的目标
防止过多的数据注入网络,从而避免网络发生拥塞。
流量控制是 “端到端” 的速率匹配问题;拥塞控制是 “整个网络” 的负载平衡问题。
3.3 拥塞控制的基本思想
TCP 通过检测网络状态(主要是“是否丢包”)来动态调整发送速率。 它引入了一个新的控制变量:拥塞窗口(cwnd, congestion window)
发送方实际能发送的数据量取决于:min(rwnd, cwnd)
- 不超过接收方的接收能力;
- 不超过网络的承载能力。
4. 拥塞控制的实现
TCP 拥塞控制主要经历了四个阶段:
| 阶段 | 名称 | 作用 |
|---|---|---|
| 1 | 慢启动(Slow Start) | 快速探测网络容量 |
| 2 | 拥塞避免(Congestion Avoidance) | 逐步增加速率,避免过载 |
| 3 | 快重传(Fast Retransmit) | 发现丢包时快速重传 |
| 4 | 快恢复(Fast Recovery) | 避免重启慢启动,平滑恢复速率 |
4.1 慢启动(Slow Start)
名字虽然叫“慢启动”,其实是“指数级增长”的。
- 初始化 cwnd = 1 MSS(最大报文段大小)
- 每收到一个 ACK,cwnd += 1 MSS → cwnd 成指数级增长(1, 2, 4, 8, 16…)
这样能迅速找到网络的“拥塞临界点”。
当 cwnd 达到某个阈值 ssthresh(慢启动阈值) 时,进入下一个阶段。
4.2 拥塞避免(Congestion Avoidance)
- 当 cwnd ≥ ssthresh 时,不再指数增长,而是线性增长(每个 RTT 增加 1 MSS)。
- 目的是逐步试探网络负载能力,防止再次拥塞。
4.3 快重传(Fast Retransmit)
在正常情况下,TCP 通过超时重传(RTO)判断丢包。 但如果等超时再重传,效率太低。
于是:
- 如果发送方收到连续 3 个相同的 ACK(即“冗余 ACK”), 它就认为某个分段丢失,立即重传该分段,而不必等超时。
4.4 快恢复(Fast Recovery)
配合快重传使用。
当检测到丢包后:
- ssthresh = cwnd / 2
- cwnd = ssthresh
- 然后进入拥塞避免阶段(线性增长)
这样避免每次都回到“慢启动”阶段,提高效率。
4.5 图解
- 慢开始(Slow Start)、
- 拥塞避免(Congestion Avoidance)、
- 超时重传后的重启
- 以及 快速重传 + 快速恢复 的阶段。
4.5.1 图像概述
横轴:传输轮次(Round Trip Times, RTT) 纵轴:拥塞窗口 cwnd(以报文段 MSS 为单位)
图展示了 TCP 发送方的 cwnd 随着传输过程的变化,反映了网络拥塞控制的核心思想:“循序渐进地探测网络能力,拥塞时迅速回退,防止网络崩溃。”
4.5.2 阶段 1:慢开始(Slow Start)
图中最左边,cwnd 从 1 开始(每次只发送一个段)。 随着每次成功收到 ACK,TCP 将 cwnd 加倍,即指数级增长:
| 传输轮次 | cwnd | 增长方式 |
|---|---|---|
| 1 | 1 | 初始 |
| 2 | 2 | 乘 2 |
| 3 | 4 | 乘 2 |
| 4 | 8 | 乘 2 |
| 5 | 16 | 乘 2 |
在这个阶段,TCP 试探性地增加发送速率,以快速占满网络带宽。
这种快速增长称为“慢开始”,其实并不慢,而是相对于瞬间全速发送而言更“温和”地增长。
4.5.3 阶段 2:拥塞避免(Congestion Avoidance)
当 cwnd 达到一个阈值(通常称为 ssthresh,Slow Start Threshold)后, TCP 认为网络已经接近饱和,不再使用指数增长,而是改用线性增长:每经过一个往返时间(RTT),cwnd = cwnd + 1
这对应图中大约从第 5 轮开始,cwnd 逐渐缓慢上升(16 → 18 → 19 → 20 → 22 → 23 → 24)。 此时网络处于拥塞避免阶段,TCP 小心地探测带宽上限。
4.5.4 阶段 3:发生超时(Timeout)
当 cwnd 增长到一定程度(约第 12 次传输)时,突然出现 拥塞事件:
- 可能是因为网络过载导致分组丢失;
- 或者接收方 ACK 超时未返回。
此时 TCP 会判定网络出现拥塞,采取剧烈收缩策略:
| 操作 | 含义 |
|---|---|
将 ssthresh 设为当前 cwnd 的一半 | 例如 cwnd=24 → ssthresh=12 |
将 cwnd 重置为 1 | 回到慢开始阶段 |
这对应图中第一次“断崖式下降”—— cwnd 从约 24 掉到 1。 此后又重新进入“慢开始”阶段。
4.5.5 阶段 4:第二次慢开始 + 拥塞避免
从第 13 轮开始,TCP 重新启动慢开始:
- cwnd 从 1 开始;
- 以指数方式增长到
ssthresh = 12; - 到达阈值后,又转为线性增长。
这正是图中第二次“上升曲线”:先陡升、再平缓。
4.5.6 阶段 5:快速重传 + 快速恢复
在第 21 轮附近,图中出现第二次“突然下降”,但这次并没有回到 1,而是掉到 8 左右,然后继续缓慢上升。
这说明:此次丢包不是超时引发,而是通过 快速重传(Fast Retransmit) 检测到的。
4.5.6.1 快速重传
当发送方连续收到 三个相同的 ACK(即三次重复确认) 时, TCP 判断某个报文段可能丢失(而不是所有数据都堵住),于是立即重传该段,而不必等超时。
4.5.6.2 快速恢复
进入快速重传后,TCP:
- 将
ssthresh设为当前 cwnd 的一半; - 将 cwnd 暂时降为
ssthresh + 3; - 随后进入拥塞避免阶段(线性增长)。
因此在图中:
- cwnd 从 16 → 8(下降一半);
- 然后缓慢上升(线性增加); 这正是快速恢复阶段的典型特征。
4.5.7 整体流程总结
| 阶段 | 特征 | 增长方式 | 触发条件 | 下一步 |
|---|---|---|---|---|
| 慢开始 | cwnd 指数增长 | cwnd ×2 | cwnd ≥ ssthresh | → 拥塞避免 |
| 拥塞避免 | cwnd 线性增长 | cwnd +1 | 分组丢失或超时 | → 拥塞处理 |
| 拥塞处理(超时) | cwnd 重置为 1 | 重新慢开始 | 超时 | → 慢开始 |
| 快速重传 + 快速恢复 | cwnd 减半 | 线性增长 | 三次重复 ACK | → 拥塞避免 |
4.5.8 从图中读出的网络意义
从整个 cwnd 曲线可以看到:
- TCP 的核心是“探测 + 回退”;
- 它不会一次性冲满带宽,而是试探性地增加;
- 一旦检测到网络拥塞,就立即收缩发送速率;
- 随后重新探测带宽上限;
- 整个过程不断循环,使得网络稳定在“临界负载附近”。
这就是 TCP 拥塞控制的精髓所在:“逐步增加速度,丢包立刻刹车”。
5. 流量控制 vs 拥塞控制
| 对比维度 | 流量控制 | 拥塞控制 |
|---|---|---|
| 控制范围 | 发送方与接收方之间 | 整个网络 |
| 控制目标 | 防止接收方被“淹没” | 防止网络被“塞爆” |
| 控制依据 | 接收方缓冲区大小(rwnd) | 网络状态(丢包、延迟、ACK) |
| 核心变量 | rwnd(接收窗口) | cwnd(拥塞窗口) |
| 调整逻辑 | 接收方告诉发送方“我还能接多少” | 发送方根据丢包/ACK动态调整 |
| 共同点 | 都是通过“控制发送速率”来实现可靠高效传输 |
- 流量控制:关注“接收方”,防止“接不过来”;
- 拥塞控制:关注“网络”,防止“传不过去”;
- 二者配合:保证 TCP 既稳又快。
当理解了这两种机制的本质,就能更好地理解 TCP 的行为—— 为什么下载刚开始很慢(慢启动), 为什么丢包后速度突然下降(拥塞控制), 以及为什么某些连接即使双方性能都很强,仍然跑不满(受网络或窗口限制)。
6. 缩写对照表
| 英文全称 | 英文缩写 | 中文全称 |
|---|---|---|
| Transmission Control Protocol | TCP | 传输控制协议 |
| Flow Control | — | 流量控制 |
| Congestion Control | — | 拥塞控制 |
| Receive Window | rwnd | 接收窗口 |
| Congestion Window | cwnd | 拥塞窗口 |
| Sliding Window | — | 滑动窗口 |
| Acknowledgment | ACK | 确认报文(确认应答) |
| Maximum Segment Size | MSS | 最大报文段长度 |
| Slow Start | — | 慢开始(慢启动) |
| Slow Start Threshold | ssthresh | 慢开始阈值 |
| Congestion Avoidance | — | 拥塞避免 |
| Fast Retransmit | — | 快速重传 |
| Fast Recovery | — | 快速恢复 |
| Round Trip Time | RTT | 往返时延 |
| Timeout Retransmission | RTO | 超时重传时间(重传超时) |
| Buffer Overflow | — | 缓冲区溢出 |
| Zero Window Probe | ZWP | 零窗口探测 |
| Receive Buffer | — | 接收缓冲区 |
| Sender / Receiver | — | 发送方 / 接收方 |
| Transmission Round | — | 传输轮次 |
| Duplicate Acknowledgment | Duplicate ACK | 重复确认(冗余 ACK) |
| Bandwidth | — | 带宽 |
| Network Congestion | — | 网络拥塞 |
| Data Segment | — | 数据报文段 |
| Reliable Transmission | — | 可靠传输 |
| Round Trip Times | RTTs | 多次往返时延(复数形式) |
| Timeout Event | — | 超时事件 |
| Congestion Event | — | 拥塞事件 |



