3.1. Slow Start 和 Congestion Avoidance
TCP sender 必须使用 slow start 和 congestion avoidance 算法来控制注入网络的 outstanding data 数量.
状态变量
为了实现这些算法, TCP 每连接状态中添加两个变量:
- Congestion Window (cwnd): sender 在收到 acknowledgment (ACK) 之前可向网络传输的数据量的 sender-side 限制
- Receiver's Advertised Window (rwnd): outstanding data 数量的 receiver-side 限制
cwnd 和 rwnd 中的较小值控制数据传输.
另一个状态变量 slow start threshold (ssthresh) 用于确定使用 slow start 还是 congestion avoidance 算法控制数据传输, 如下文所述.
Slow Start 的目的
在状况未知的网络中开始传输时, TCP 需要缓慢探测网络以确定可用容量, 避免用不适当的大量突发数据造成网络拥塞. slow start 算法用于此目的:
- 在传输开始时
- 在修复由 retransmission timer 检测到的丢失之后
此外, slow start 还用于启动 TCP sender 在 slow start, congestion avoidance 和 loss recovery 算法中向网络释放数据所用的 "ACK clock".
Initial Window (IW)
IW, 即 cwnd 的初始值, 必须使用以下准则作为上界进行设置:
If SMSS > 2190 bytes:
IW = 2 * SMSS bytes and MUST NOT be more than 2 segments
If (SMSS > 1095 bytes) and (SMSS <= 2190 bytes):
IW = 3 * SMSS bytes and MUST NOT be more than 3 segments
If SMSS <= 1095 bytes:
IW = 4 * SMSS bytes and MUST NOT be more than 4 segments
如 [RFC3390] 所规定, SYN/ACK 以及 SYN/ACK 的 acknowledgment 不得增加 congestion window 的大小. 此外, 如果 SYN 或 SYN/ACK 丢失, sender 在正确传输 SYN 之后使用的 initial window 必须是一个最多由 SMSS 字节组成的 segment.
[RFC3390] 给出了 IW 设置的详细理由和讨论.
Path MTU Discovery 考量
当实现超过一个 segment 的 initial congestion window 并同时使用 Path MTU Discovery [RFC1191] 时, 如果发现所用 MSS 过大, congestion window cwnd 应该被降低, 以防止较小 segment 的大规模突发. 具体而言, cwnd 应该按旧 segment size 与新 segment size 的比例降低.
初始 ssthresh 值
ssthresh 的初始值应该任意设置为较高值 (例如最大可能 advertised window 的大小), 但 ssthresh 必须响应拥塞而降低.
将 ssthresh 设置得尽可能高, 可让网络状况而不是某个任意 host 限制来决定发送速率. 在 end systems 对网络路径有可靠了解的情况下, 更谨慎地设置初始 ssthresh 值可能有意义 (例如使 end host 不在路径上造成拥塞).
算法选择
- 当
cwnd < ssthresh时使用 slow start 算法 - 当
cwnd > ssthresh时使用 congestion avoidance 算法 - 当
cwnd和ssthresh相等时, sender 可以使用 slow start 或 congestion avoidance
Slow Start 算法
在 slow start 期间, TCP 对每个收到且 cumulatively acknowledges 新数据的 ACK, 至多将 cwnd 增加 SMSS 字节. slow start 在以下情况下结束:
- cwnd 超过 ssthresh (或者如上文所述, 可选地在达到 ssthresh 时), 或
- 观察到拥塞时
推荐的 cwnd 增长
传统上, TCP 实现在收到覆盖新数据的 ACK 时会精确地将 cwnd 增加 SMSS 字节, 但我们 RECOMMEND TCP 实现按以下方式增加 cwnd:
cwnd += min (N, SMSS) (equation 2)
其中 N 是 incoming ACK 中确认的此前未确认字节数.
此调整是 Appropriate Byte Counting [RFC3465] 的一部分, 并提供对行为不当 receiver 的鲁棒性; 这些 receiver 可能试图使用称为 "ACK Division" [SCWA99] 的机制诱导 sender 人为膨胀 cwnd. ACK Division 指 receiver 为单个 TCP data segment 发送多个 ACK, 每个 ACK 仅确认其中一部分数据. 如果 TCP 为每个这类 ACK 将 cwnd 增加 SMSS, 就会不适当地膨胀注入网络的数据量.
Congestion Avoidance 算法
在 congestion avoidance 期间, cwnd 每个 round-trip time (RTT) 大约增加 1 个 full-sized segment. congestion avoidance 持续到检测到拥塞为止.
增加 cwnd 的准则
在 congestion avoidance 期间增加 cwnd 的基本准则为:
- 可以将 cwnd 增加 SMSS 字节
- 应该按 equation (2) 每 RTT 增加一次 cwnd
- 不得将 cwnd 增加超过 SMSS 字节
注意, [RFC3465] 允许在实验基础上, 在 slow start 期间对 incoming acknowledgments 将 cwnd 增加超过 SMSS 字节; 然而, 此类行为不作为标准的一部分被允许.
推荐实现
在 congestion avoidance 期间增加 cwnd 的推荐方法是统计新数据 ACK 已确认的字节数. (此实现的一个缺点是需要维护额外状态变量.) 当已确认字节数达到 cwnd 时, cwnd 最多可增加 SMSS 字节.
注意, 在 congestion avoidance 期间, cwnd 每 RTT 不得增加超过 SMSS 字节. 该方法既允许 TCP 在面对 delayed ACKs 时每 RTT 将 cwnd 增加一个 segment, 又提供对 ACK Division 攻击的鲁棒性.
替代公式
TCP 在 congestion avoidance 期间可以用于更新 cwnd 的另一个常见公式见 equation (3):
cwnd += SMSS*SMSS/cwnd (equation 3)
此调整在每个确认新数据的 incoming ACK 上执行. Equation (3) 对每 RTT 将 cwnd 增加 1 个 full-sized segment 的底层原则给出了可接受近似. (注意, 对于 receiver 每隔一个 packet 才确认一次的连接, (3) 比允许的行为更不激进, 大约每第二个 RTT 才增加 cwnd.)
Equation (3) 的实现说明
Note 1: 由于 TCP 实现通常使用整数算术, 当 congestion window 大于 SMSS*SMSS 时, equation (3) 中给出的公式可能无法增加 cwnd. 如果上述公式产生 0, 结果应该向上舍入为 1 字节.
Note 2: 较旧实现会在 equation (3) 右侧添加额外加性常数. 这是不正确的, 并且实际上可能导致性能降低 [RFC2525].
Note 3: 一些实现以字节为单位维护 cwnd, 另一些则以 full-sized segments 为单位维护. 后者会发现 equation (3) 难以使用, 可能更倾向于使用上一段讨论的计数方法.
对丢失的响应
当 TCP sender 使用 retransmission timer 检测到 segment loss, 且给定 segment 尚未通过 retransmission timer 重发时, ssthresh 的值必须设置为不超过 equation (4) 给出的值:
ssthresh = max (FlightSize / 2, 2*SMSS) (equation 4)
其中, 如上所述, FlightSize 是网络中的 outstanding data 数量.
另一方面, 当 TCP sender 使用 retransmission timer 检测到 segment loss, 且给定 segment 已至少一次通过 retransmission timer 重传时, ssthresh 的值保持不变.
关于 FlightSize 的实现说明
一个容易犯的错误是简单使用 cwnd 而不是 FlightSize; 在某些实现中, cwnd 可能偶然增加到远超 rwnd.
Timeout 后设置 cwnd
此外, 发生 timeout 时 (如 [RFC2988] 所规定), cwnd 必须设置为不超过 loss window, LW, 其等于 1 个 full-sized segment (无论 IW 值如何).
因此, 在重传 dropped segment 后, TCP sender 使用 slow start 算法将 window 从 1 个 full-sized segment 增加到新的 ssthresh 值, 此时 congestion avoidance 再次接管.
虚假重传
如 [FF96] 和 [RFC3782] 所示, timeout 后基于 slow-start 的 loss recovery 可能导致触发 duplicate acknowledgments 的 spurious retransmissions. TCP 实现对这些 duplicate ACKs 到达的反应差异很大. 本文档不规定如何处理这类 acknowledgments, 但指出这是一个可能受益于更多关注,实验和规范化的领域.