4. 经典拥塞控制算法:Tahoe 与 Reno

TCP Tahoe 与 Reno 是 TCP 拥塞控制领域最具奠基意义的两个算法。Tahoe 首次完整定义了慢启动、拥塞避免、快速重传三大核心机制;Reno 则在 Tahoe 基础上增加了快速恢复阶段,显著提升了单包丢失场景下的吞吐量。理解这两个算法是掌握后续所有拥塞控制变种(NewReno、BIC、CUBIC 等)的前提。

4.1 核心状态机与关键变量

TCP 拥塞控制状态机由四个阶段组成,每个阶段由拥塞窗口(cwnd)和慢启动阈值(ssthresh)共同驱动。下表总结了各阶段的核心行为:

阶段 触发条件 cwnd 更新规则 ssthresh 更新规则
慢启动 连接建立或超时重传后 每收到一个 ACK,cwnd += 1 MSS(指数增长) 初始 ssthresh = 拥塞窗口上限(如 65535 字节)
拥塞避免 cwnd >= ssthresh 每收到一个 ACK,cwnd += MSS / cwnd(线性增长) 保持不变
快速重传 收到 3 个重复 ACK(DupACK) 不直接修改 cwnd,触发重传 ssthresh = cwnd / 2(Tahoe 与 Reno 相同)
快速恢复 仅 Reno:快速重传后进入 cwnd = ssthresh + 3 MSS(Reno 特有) 已更新为 cwnd / 2
关键变量说明:cwnd(Congestion Window)以 MSS(最大报文段长度)为单位,控制发送端一次可发送的数据量;ssthresh(Slow Start Threshold)是慢启动与拥塞避免的分界线。两个变量共同决定 TCP 发送速率。

4.2 慢启动与拥塞避免流程

慢启动阶段的目标是快速探测网络可用带宽,拥塞避免阶段则转为保守的线性增长以避免拥塞。下图展示了从连接建立到拥塞避免的完整状态转换:

TCP Tahoe/Reno 慢启动 → 拥塞避免状态转换 慢启动 cwnd 指数增长 拥塞避免 cwnd 线性增长 超时重传 cwnd = 1 MSS 快速重传 3 DupACK 触发 连接建立 cwnd >= ssthresh 3 DupACK Tahoe: cwnd = 1 MSS Reno: 快速恢复后进入 超时 虚线箭头:Tahoe 与 Reno 在丢包恢复后的不同路径
慢启动指数增长细节:每收到一个 ACK,cwnd 增加 1 MSS。假设初始 cwnd = 1 MSS,经过 1 个 RTT 后收到 1 个 ACK,cwnd 变为 2;再经过 1 个 RTT 收到 2 个 ACK,cwnd 变为 4;以此类推,每 RTT 窗口翻倍。这种指数增长在连接初期能快速填满网络管道。

4.3 快速重传与快速恢复

快速重传机制解决了传统超时重传等待时间过长的问题。当发送端连续收到 3 个重复 ACK 时,立即重传丢失的报文段,无需等待 RTO 超时。Tahoe 与 Reno 在此处分叉:

  • Tahoe 行为:进入快速重传后,设置 ssthresh = cwnd / 2,然后将 cwnd 重置为 1 MSS,重新进入慢启动阶段。这种激进的重置策略在单包丢失时过于保守。
  • Reno 行为:进入快速重传后,设置 ssthresh = cwnd / 2,但 cwnd 不重置为 1,而是设置为 ssthresh + 3 MSS,进入快速恢复阶段。在快速恢复期间,每收到一个重复 ACK,cwnd 临时增加 1 MSS(用于发送新数据);当收到新 ACK 时,cwnd 降为 ssthresh,退出快速恢复进入拥塞避免。

下表对比了 Tahoe 与 Reno 在丢包恢复时的关键差异:

特性 Tahoe Reno
3 DupACK 后 cwnd 重置为 1 MSS 设置为 ssthresh + 3 MSS
恢复阶段 无快速恢复,直接进入慢启动 有快速恢复阶段
单包丢失吞吐量 较低(需重新慢启动) 较高(保持管道半满)
多包丢失处理 可能触发多次超时 可能退化为超时(Reno 限制)
Linux 内核实现 仅历史参考 tcp_reno_cong_avoid()

4.4 快速恢复详细流程

Reno 快速恢复是理解 TCP 拥塞控制状态机复杂性的关键。以下伪代码展示了 Linux 内核中快速恢复的核心逻辑:

/* Linux 内核 TCP Reno 快速恢复伪代码 */
void tcp_reno_fast_recovery(struct sock *sk, struct sk_buff *skb) {
    struct tcp_sock *tp = tcp_sk(sk);

    /* 1. 进入快速恢复:设置 ssthresh = cwnd / 2 */
    tp->snd_ssthresh = max(tp->snd_cwnd >> 1U, 2U);

    /* 2. 设置 cwnd = ssthresh + 3 MSS(已收到的 3 个 DupACK 允许发送新数据) */
    tp->snd_cwnd = tp->snd_ssthresh + 3;

    /* 3. 重传丢失的报文段(由快速重传触发) */
    tcp_retransmit_skb(sk, skb);

    /* 4. 进入快速恢复循环 */
    tp->recovery = 1;

    /* 5. 每收到一个重复 ACK,临时增加 cwnd */
    while (收到重复 ACK) {
        tp->snd_cwnd++;
        /* 允许发送新数据 */
    }

    /* 6. 收到新 ACK(确认了丢失报文段的重传) */
    if (收到新 ACK) {
        /* 退出快速恢复 */
        tp->snd_cwnd = tp->snd_ssthresh;
        tp->recovery = 0;
        /* 进入拥塞避免 */
    }
}
Reno 快速恢复的局限性:当发生多包丢失(如一个窗口内丢失多个报文段)时,Reno 的快速恢复只能恢复第一个丢失的报文段。后续丢失的报文段需要等待超时重传,导致吞吐量急剧下降。这一缺陷直接催生了 NewReno 和 SACK 选项的诞生。

4.5 状态机完整转换图

下图整合了 Tahoe 与 Reno 的完整状态机,突出显示两者在丢包恢复路径上的分叉:

Tahoe vs Reno 拥塞控制状态机对比 慢启动 cwnd 指数增长 拥塞避免 cwnd 线性增长 快速重传 3 DupACK 快速恢复 仅 Reno 超时重传 RTO 超时 连接建立 初始 cwnd = 1 cwnd >= ssthresh 3 DupACK Reno 进入 Tahoe: cwnd = 1 收到新 ACK 超时 实线:Reno 路径 虚线:Tahoe 路径 Tahoe 无快速恢复状态

4.6 内核实现关键点

在 Linux 内核网络协议栈中,Tahoe 与 Reno 的实现集中在 net/ipv4/tcp_cong.c 和 net/ipv4/tcp_input.c 中。以下是几个关键实现细节:

  • 拥塞避免函数:tcp_reno_cong_avoid() 实现了 AIMD(加法增乘法减)逻辑,每收到一个 ACK 增加 cwnd 的 1/cwnd。
  • 快速重传触发:在 tcp_ack() 中检测重复 ACK 计数,当达到 TCP_FASTRETRANS_THRESH(默认为 3)时调用 tcp_fastretrans_alert()。
  • 快速恢复入口:tcp_enter_recovery() 设置 tcp->recovery 标志,并调用拥塞控制算法的 cong_control 回调。
  • ssthresh 更新:通过 tcp_reno_ssthresh() 实现,返回 max(cwnd / 2, 2)。
调优建议:在 Linux 系统中,可以通过 sysctl net.ipv4.tcp_congestion_control 查看当前拥塞控制算法。对于 Reno 算法,关键可调参数是 net.ipv4.tcp_retries1 和 net.ipv4.tcp_retries2,它们影响快速重传和超时重传的阈值。在低延迟网络中,适当降低这些值可以更快地检测丢包。

4.7 Tahoe 与 Reno 的适用场景

尽管 Tahoe 和 Reno 已被更先进的算法(如 CUBIC、BBR)取代,但理解它们对于以下场景仍然至关重要:

场景 推荐算法 原因
低丢包率、低延迟网络 Reno 快速恢复能有效利用带宽,算法简单开销小
高丢包率无线网络 不推荐 Reno 多包丢失导致频繁超时,吞吐量骤降
历史兼容性测试 Tahoe 用于验证旧版 TCP 实现的行为
教学与协议分析 两者皆可 作为拥塞控制基础理论的参考实现

Tahoe 与 Reno 奠定了 TCP 拥塞控制的 AIMD 原则,后续所有算法都是在这一框架上的优化与扩展。掌握这两个算法的状态机转换和窗口更新规则,是深入理解 Linux 内核网络协议栈拥塞控制子系统的基石。