观察到上一个实验中的慢启动的现象后,接下来我们就详细讲讲到底是怎么一回事。
1. 慢启动算法
对于发送方来说,TCP 维护了一个变量 cwnd (congestion window),这个变量称为拥塞窗口,它的大小就是 cwnd。它表示发送方一次想要发送多少字节的数据。
cwnd 是一个动态变化的值,它会根据网络的情况实时的调整自己,最后,cwnd 就会适应网络的情况,以保证发送 cwnd 字节的数据而网络不会拥塞。最终我们关注的问题就是 cwnd 是如何调整的问题。
在最开始,cwnd 有一个初始值,RFC 2581 规定,它的大小不超过 2MSS (MSS 应该取发送方或者接收方叫的较小者)。为了方便描述,我们不妨就假设初始值为一个 MSS 大小。另外,我们把 cwnd 的单位直接改为 MSS,比如说如果 cwnd = 2,就表示 cwnd 的大小为 2 倍 MSS.
慢启动算法是这样的:一开始发送数据的时候,如果把大量的数据注入到网络,就有可能引起网络拥塞,因为一开始并不知道网络的状况。因此,慢启动算法一开始先探测一下,即先发送大小为 cwnd = 1 的报文(一个报文)到网络中,当收到了对该报文的确认后,就把 cwnd 的值加 1,此时 cwnd 的值就变成 2 了。接下来,再发送大小为 cwnd = 2 的报文(两个报文)到网络中,发送方每收到一个确认(不能计算重传报文的 ack,另外我们把累积确认计算为多个确认),cwnd 的值就加 1. 我们用图 1 来表示这个过程。
图1 慢启动算法
了解了上述步骤后,你可能要问,cwnd 要增长到什么时候才会停?如果一直这样,迟早会拥塞啊,你说的没错!为了防止 cwnd 增长的过大,TCP 中还维护了另一个变量 ssthresh,它称之为慢启动门限,这是一个阈值,当 cwnd 超过这个值的时候,慢启动算法结束,进入拥塞避免算法!
ssthresh 变量在一开始也是有默认值的,比如 ssthresh = 16. 接下来,我们讨论 cwnd > ssthresh 后,TCP 的行为。
2. 拥塞避免算法
刚刚上面说了,当 cwnd > ssthresh 时,转而执行拥塞避免算法。这时候,TCP 发送 cwnd 个报文后,如果接收到了确认,cwnd 的值只是加 1,而不是加倍。这样,拥塞窗口 cwnd 就会按线性规律缓慢增长。
无论是在慢启动阶段,还是在拥塞避免阶段,只要发送方判断网络出现拥塞(依据就是没有按照收到确认),就要把 ssthresh 设置为出现拥塞时的 cwnd 值的一半(注意这只是一种策略,实际实现中不一定是这样的,RFC 中给出的公式是将已发出但是还未被确认的数据字节数来设置 ssthresh 的值)。
在更新了 ssthresh 后,同时将 cwnd 重新设置为 1,又开始执行慢启动算法。这样做的目的是要迅速减少主机发送到网络中的分组数,使得发生拥塞的中间设备有足够的时间把缓冲区中积压的分组处理完毕。
为了能将上述步骤形象化,可以参考图 2.
图2 慢启动和拥塞避免算法
注意到图 2 中的两个术语:“加法增大(Additive Increase)”和“乘法减小(Multiplicative Decrease)”。
加法增大是指执行拥塞避免算法后,使拥塞窗口 cwnd 缓慢增大,以防止网络过早出现拥塞。而乘法减小,是指不论在慢开始阶段还是在拥塞避免阶段,只要出现超时(即很可能出现了网络拥塞),就把 ssthresh 减半,即 ssthresh = cwnd/2,紧接着,cwnd = 1.
上面两种算法合起来常常称为 AIMD (Additive Increase, Multiplicative Decrease)。
3. 总结
- 掌握慢启动算法和拥塞避免算法
- 知道加法增大和乘法减小是什么