16.1 Introduction
16.1 Introduction(拥塞控制的基本思想)
TCP 的拥塞控制主要用于 大规模数据传输(bulk transfer)场景,其目标是防止网络因为发送流量过大而被压垮。基本策略是:当 TCP 认为网络可能出现拥塞时降低发送速率,而当网络恢复时再逐渐提高速率。
TCP 最初通过 流量控制(Flow Control) 来避免接收方被发送方压垮。发送方根据接收方在 ACK 中通告的 Window Size(接收窗口) 调整发送速度。但流量控制只能解决 接收端处理能力不足的问题,并不能解决 网络中间设备(如路由器)发生拥塞的问题。
当网络中的发送流量 超过链路长期能够处理的能力 时,路由器缓存会不断积累数据,最终缓存耗尽并开始 丢包(packet drop)。这种情况称为 网络拥塞(congestion)。如果不进行控制,网络性能可能急剧下降,极端情况下会出现 拥塞崩溃(congestion collapse)。
因此 TCP 引入了一系列 拥塞控制算法,通过动态调节发送速率,使网络既能充分利用带宽,又不会进入严重拥塞状态。不同 TCP 实现(不同操作系统或算法变种)在具体行为上可能有所不同。
16.1.1 TCP 如何检测拥塞(Detection of Congestion in TCP)
在传统互联网设计中,路由器不会主动告诉 TCP 当前是否拥塞,因此 TCP 必须通过间接方式推断拥塞是否发生。
经典 TCP 的基本假设是:
丢包 = 网络发生拥塞的信号。
TCP 主要通过两种机制检测丢包:
- RTO 超时重传(Retransmission Timeout) —— 重传定时器超时
- Fast Retransmit(快速重传) —— 收到多个重复 ACK
当 TCP 检测到丢包时,不仅需要 重传数据,还需要 降低发送速率。如果在网络已经拥塞的情况下大量 TCP 连接继续发送或重传更多数据,只会让拥塞进一步恶化,这种行为常被比喻为 “往火里倒汽油”。
后来研究者提出了一些更先进的拥塞检测方式,例如:
- 基于时延的检测(Delay-based):通过 RTT 的变化推测网络拥塞
- 显式拥塞通知(ECN, Explicit Congestion Notification):由路由器主动标记拥塞而不是丢包
另外需要注意:
- 在 有线网络 中,丢包通常主要由 拥塞 引起
- 在 无线网络 中,丢包还可能由 链路误码(transmission error) 引起
因此区分“拥塞丢包”和“链路误码丢包”一直是网络研究中的重要问题。
16.1.2 TCP 如何降低发送速率(Slowing Down a TCP Sender)
为了根据网络情况控制发送速率,TCP 在发送端引入了一个新的变量:
拥塞窗口(Congestion Window,cwnd)。
TCP 实际允许的发送窗口为:
1
W = min(cwnd, awnd)
其中:
- cwnd:拥塞窗口(网络可承受能力的估计值)
- awnd:接收端通告窗口(receiver advertised window)
- W:发送端实际可用窗口
发送方在任意时刻 未被确认的数据量 不能超过 W。
换句话说:
发送速率同时受到两个因素限制:
- 接收端处理能力(awnd)
- 网络拥塞情况(cwnd)
实际发送窗口取两者的最小值。
Flight Size
Flight Size 指:
已经发送但还没有收到 ACK 的数据量
其关系为:
1
flight_size ≤ W
最优窗口:带宽时延积(BDP)
理想情况下,TCP 发送窗口应该接近 带宽时延积(Bandwidth-Delay Product, BDP):
1
BDP = Bandwidth × RTT
BDP 表示:
在网络路径上“同时在途”的最大数据量。
如果发送窗口:
- 小于 BDP → 网络带宽利用率不足
- 远大于 BDP → 容易产生队列积压、延迟增加以及拥塞
但在互联网环境中,RTT、路由路径、网络负载都会随时间变化,因此 TCP 只能通过算法 动态估计并调整 cwnd。
反向路径拥塞(ACK Path)
拥塞不仅可能发生在 数据发送路径,也可能发生在 ACK 返回路径。
在 RFC 5690 中提出了一种机制:
发送方可以建议接收方使用一定的 ACK ratio,例如:
- 每接收 N 个数据包再发送一个 ACK
这样可以减少 ACK 数量,从而缓解反向路径的拥塞。
16.2 The Classic Algorithms
当一个新的 TCP 连接刚建立时,发送方通常并不知道初始的 cwnd 应该设多大,因为它无法直接获知网络可用的带宽(除非使用之前缓存的性能信息,如第 14 章提到的 destination metrics)。发送方可以通过一次数据包交换获取接收方的 awnd,但对于网络容量(cwnd),TCP 唯一可行的方法是 逐步尝试以更快速率发送数据,直到出现丢包或其他拥塞信号。直接以最大速率发送会影响同一路径上其他 TCP 连接的性能,因此 TCP 在启动阶段通常使用 慢启动(Slow Start) 算法避免一开始发送过快,而在稳定阶段则使用 拥塞避免(Congestion Avoidance) 算法。TCP 的发送行为由 ACK 驱动(ACK Clock):收到 ACK 表示网络中有数据已成功移除,发送方可以发送新的数据包,这体现了 数据包守恒(Packet Conservation) 原则,即网络中同时存在的数据包数量应该保持稳定,从而形成 自时钟机制(Self-Clocking),保证发送节奏与网络容量匹配。这两种算法基于这一原则,由 Jacobson 在经典论文中首次提出,并在后续进行了改进。每条 TCP 连接都独立执行这两个算法,但同一时间只运行其中之一,会根据网络状况在两者之间切换。
16.2.1. Slow Start
慢启动算法在 TCP 新建连接时或检测到 重传超时(RTO)后的丢包时启动,也可能在发送端长时间空闲后再次发送时触发。其主要目的在于帮助 TCP 逐步确定拥塞窗口(cwnd)的初始值,在使用 拥塞避免算法前探测 可用带宽,并建立 ACK 时钟。慢启动可以避免在 网络条件未知时一次性发送大量数据造成拥塞,从而保障网络稳定。
TCP 在慢启动阶段会发送一定数量的报文段作为 初始窗口(Initial Window, IW)。根据 RFC5681,IW 的大小取决于 发送最大报文段(SMSS)的值:当 SMSS > 2190 字节时 IW 不超过 2 个报文段;当 1095 < SMSS ≤ 2190 字节时 IW 不超过 3 个报文段;其他情况下 IW 不超过 4 个报文段。为了简化理解,通常假设 IW = 1 SMSS。SMSS通常为 接收方 MSS 与路径 MTU 减去头部的较小值。
在慢启动过程中,cwnd 初始为 1 SMSS。每当 TCP 收到一个 “good ACK”(即确认号比之前更大的 ACK)时,cwnd 按收到的字节数增加(适用 Appropriate Byte Counting, ABC)。这种机制支持 指数增长:例如,第一个 ACK 使 cwnd 从 1 增加到 2,可发送两个报文段;随后收到的 ACK 又使 cwnd 增加到 4,再发送四个报文段,依此类推。在理想情况下,每个 RTT 都有 ACK 返回时,经过 k 个 RTT 后,窗口大小 W = 2^k。这种增长虽然快速,但仍比立即发送接收方广告窗口大小的数据要慢。
当 cwnd 增长到接近网络容量时,如果继续指数增长可能引发拥塞。此时,TCP 会将 cwnd 减半,并从 慢启动阶段切换到拥塞避免阶段。切换的判断依据是 cwnd 与慢启动阈值(ssthresh)的关系。通过这种方式,TCP 可以在初始阶段 快速增长,同时在接近网络极限时 减缓发送速度,保持稳定性。
延迟 ACK(Delayed ACK)会影响慢启动的增长速度。当 ACK 每两个报文段才返回一次时,cwnd 仍然呈指数增长,但速度较慢。为优化慢启动性能,Linux 支持 Quick ACK 模式,在慢启动阶段尽量不延迟 ACK,从而加快窗口增长。
总的来说,慢启动通过 指数增长 cwnd,配合 ACK 反馈机制,使 TCP 在 未知网络环境中安全探测可用带宽。在达到 慢启动阈值或发生丢包时,TCP 自动切换到 拥塞避免阶段,保证网络传输既 高效又 稳定。