TCP 拥塞控制主要有四个重要阶段:
慢启动;
拥塞避免;
快速重传;
快速恢复。
慢启动
慢启动Slow Start,是指 TCP 传输的开始阶段是从一个相对低的速度(“慢”一词的由来)开始的。事实上,在这个阶段,拥塞窗口会以翻倍的方式增长,所以从增长过程来看,叫“快启动”也未尝不可。具体来说,在这个阶段,每次 TCP 收到一个确认了数据的 ACK,拥塞窗口就增加一个 MSS。比如下面这样:
那么,这个过程什么时候终止呢?是下面两件事中有一件发生时:
遇到了拥塞;
拥塞窗口增长到慢启动阈值。
慢启动阈值
慢启动阈值(也有人称之为慢启动门限),英文简称 ssthresh。过了这个阈值,拥塞窗口的增长速度立刻就放缓了,变成了每过一个 RTT,拥塞窗口就只增长一个 MSS(此前是每个确认数据的 ACK,增长一个 MSS)。
比如上图的例子中,假设 ICW 是 4 个 MSS,ssthresh 是 32 个 MSS。在慢启动阶段,经过一个 RTT 后,CW 扩大为 8 个 MSS,然后是 16 个 MSS,32 个 MSS,以指数级上升。那么到了这个阈值后,TCP 就进入了拥塞避免阶段,每过一个 RTT,拥塞窗口只增加一个 MSS,于是在图上看,就又变成了一条平直的斜率比较低的直线了。
拥塞窗口
Congestion Window,缩写是 CWND,或者 CW。拥塞窗口是不是操作系统全局统一的配置呢?其实这是比较常见的误解。拥塞窗口是每个连接分开维护的,比如同一个主机有两个 TCP 连接在传输数据的话,那么这两个连接就各自维护自己的拥塞窗口,比如一个很大而一个很小,都没有关系。
拥塞避免
传输过了慢启动阈值(ssthresh)之后,就进入了拥塞避免阶段。这个阶段的特征是“和性增长乘性降低”,英文是 Addictive increase/mutiplicative decrease,缩写为 AIMD。
因为 AIMD 的关系,每一个 RTT 里,拥塞窗口只增长一个 MSS,所以这个阶段的拥塞窗口的增长是线性的。直到探测到拥塞,然后拥塞窗口就要往下降。这个下降是直接减半的,所以叫乘性降低。
窗口和 MSS 的关系
窗口值一般比 MSS 大很多,相当于就是 MSS 的某个倍数,比如 2 倍、10 倍、50 倍等等。
MSS 是有确定上限的,我在前面课程里都多次提到过,MSS 一般为 1460,当然根据实际情况,也经常会有更低的值。比如,开启 TCP timestamp 等 Option 的话,肯定要相应地从 1460 字节里扣去这部分字节数,这样的话 MSS 就会低于 1460,比如可能是 1440 字节。
确切来说,窗口的单位是字节数,所以也经常不是 MSS 的整数倍,这也都是正常的。
快速重传
超时重传
TCP 每发送一个报文,就启动一个超时计时器。如果在限定时间内没收到这个报文的确认,那么发送方就会认为,这个报文已经在网络上丢失了,于是需要重传这个报文,这种形式叫做超时重传。
一般来说,TCP 的最小超时重传时间为 200ms。这样的超时重传的机制虽然解决了丢包的问题,但也带来了一个新的问题:如果每次丢包都要等 200ms 或者更长时间,那应用不是就不能及时处理了吗?特别是对于有些时间敏感型的应用来说,影响更为严重。
快速重传
所以,TCP 会用另外一种方式来解决超时重传带来的时间空耗的问题,就是用快速重传。在这个机制里,一旦发送方收到 3 次重复确认(加上第一次确认就一共是 4 次),就不用等超时计时器了,直接重传这个报文。
快速恢复
这是 TCP Reno 算法引入的一个阶段,它是跟随快速重传一起工作的。跟之前的“慢启动 -> 拥塞避免 -> 慢启动 -> 拥塞避免”这种做法不同的是,在遇到拥塞点之后,通过快速重传,就不再进入慢启动,而是从这个减半的拥塞窗口开始,保持跟拥塞避免一样的线性增长,直到遇到下一个拥塞点。你可以参考下面的图片来理解,橙色线就是快速恢复阶段:
那么,是不是有了快速重传和快速恢复,传输过程中都不用反复进入慢启动了呢?其实并不是这样。如果遇到超时,也一样要回到慢启动阶段,重新开始。
TCP 拥塞控制机制
丢包对于发送端来说就是“拥塞”,然后它就根据拥塞控制机制,主动进入拥塞避免阶段,以确保传输速度,不至于大面积丢包。事实上就自动降低了速率,达到了我们想要的“限速”的效果。
慢启动:每收到一个 ACK,拥塞窗口(CW)增加一个 MSS。
拥塞避免:策略是“和性增长乘性降低”,每一个 RTT,CW 增加一个 MSS。
快速重传:接收到 3 次或者以上的重复确认后,直接重传这个丢失的报文。
快速恢复:结合快速重传,在遇到拥塞点后,跳过慢启动阶段,进入线性增长。