在TCP连接中假设发送方一开始便向网络发送多个报文段,直到达到接收方通告的窗口大小为止。当发送方和接收方处于同一个区域网段时,这种方式是可以的。但是如果发送方和接收方之间存在多个路由器和速率较慢的链路时,就有可能出现问题。
一些中间路由器必须缓存分组,并有可能耗尽存储器空间。
现在,TCP需要支持被称为“慢启动”的算法。该算法通过观察到新分组进入网络的速率应该与另一端返回确认的速率相同而进行工作。
慢启动发送方的TCP增加了另一个窗口:拥塞窗口(congestion window),记做cwnd。当与另一个网络的主机建立TCP连接时,拥塞窗口被初始化为1个报文段(即另一端通告的报文段大小)。每收到一个ACK,拥塞窗口就增加一个报文段(cwnd以字节为单位,但是慢启动算法以报文段大小为单位进行增加)。发送方取拥塞窗口与通告窗口中的最小值作为发送上限。拥塞窗口是发送方使用的流量控制,而通告窗口则是接收方使用的流量控制。
发送方开始时发送一个报文段,然后等待ACK。当收到该ACK时,拥塞窗口从1增加2,即可以发送两个报文段。当收到这两个报文段的ACK时,拥塞窗口就增加为4.这是一种指数增加关系。
拥塞避免算法(主要处理超时问题):
慢启动算法是在一个连接上发起数据流的方法,但有时我们会达到中间路由器的极限,此时分组将丢失。拥塞避免算法是一种处理丢失分组的方法。
该算法假定由于分组受到损坏引起的丢失是非常少的,因此分组丢失就意味着在源主机和目的主机之间的某处网络上发生了拥塞。有两种分组丢失的指示:发生超时和接收到重复的确认。
拥塞避免算法和慢启动算法是两个目的不同、独立的算法。但是当拥塞发生时,我们希望降低分组进入网络的速率于是可以调用慢启动来作到这一点。在实际中这两个算法通常在一起实现。
拥塞避免算法和慢启动算法需要对每个连接维持的两个变量:一个拥塞窗口cwnd和一个慢启动门限ssthresh。这样得到的算法的工作过程如下:
1)对一个给定的连接,初始化cwnd为一个报文段,ssthresh为65535个字节。
2)TCP输出例程的输出不能超过cwnd和接收方通告窗口的大小。拥塞避免是发送方使用的流量控制,而通告窗口则是接收方进行的流量控制。前者是发送方感受到的网络拥塞的估计,而后者则与接收方在该链接上的可用缓存大小有关。
3)当拥塞发生时(超时),ssthresh被设置成当前窗口大小的一半(cwnd和接收方通告窗口大小的最小值,但至少为两个报文段)。此外,如果是超时引起了拥塞,则cwnd被设置为1个报文段。然后会重新发送超时的报文。
4)当新的数据被对方确认时,就增加cwnd,但增加的方法依赖于我们是否正在进行慢启动或拥塞避免。如cwnd小于或等于ssthresh,则正在进行慢启动,否则正在进行拥塞避免。慢启动一直持续到我们回到拥塞发生时所处位置一半的时候才停止(因为我们记录了在步骤3中给我们制造麻烦的窗口大小的一半),然后转为执行拥塞避免。
慢启动算法初始设置cwnd为1个报文段,此后每增加一个确认就加1。这会使窗口按照指数方式增长:发送1个报文段,然后是2个,接着是4个。。。。。。
拥塞避免算法要求每次收到一个确认时将cwnd增加1/cwnd(cwnd记录的是字节而不是报文段个数)。与慢启动的指数增加比起来,这是一种加性增长。我们希望在一个往返时间内最多为cwnd增加一个报文段(不管在这个RTT中收到了多少个ACK),慢启动将根据这个往返时间中所收到的确认的个数增加cwnd。
快速重传和快速恢复(主要处理报文段失序问题):
我们知道在收到一个失序的报文段之时,TCP立即需要产生一个ACK(一个重复的ACK)。这个重复的ACK不应该被延迟。该重复的ACK的目的就在于让对方知道收到一个失序的报文段,并告诉对方自己希望收到的序号。
由于我们不知道一个重复的ACK是由一个丢失的报文段引起的,还是由于仅仅出现了几个报文段的重新排序,因此我们等待少量重复的ACK到来。假如这只是一些报文段的重新排序,则在重新排序的报文段被处理并产生一个新的ACK之前,只可能产生1~2个重复的ACK。如果一连串收到3个或3个以上的重复的ACK,就非常有可能是一个丢失的报文段了。于是我们就重传丢失的数据报文段,而无需等待超时定时器溢出。这就是快速重传算法。
在这种情况下没有执行慢启动的原因是由于收到重复的ACK不仅仅告诉我们一个分组丢失了。由于接收方只有在收到另一个报文段时才会产生重复的ACK,而该报文段已经离开了网络并进入了接收方的缓存。也就是说,在收发两端之间任然有流动的数据,而我们不想执行慢启动来突然减少数据流。
这个算法通常按如下过程进行实现:
1)当收到第3个重复的ACK时,将ssthresh设置为当前拥塞窗口大小的cwnd的一半。重传丢失的报文段。设置cwnd为ssthresh加上3倍的报文段的大小。
2)每次收到另一个回复的ACK时,cwnd增加一个报文段大小并发送一个分组(如果新的cwnd允许发送)
3)当下一个确认新数据的ACK到达时,设置cwnd为ssthresh(在第一步设置这个值)。这个ACK应该是在进行重传后的一个往返时间内对步骤1中的重传的确认。另外,这个ACK也应该是对丢失的分组和收到的第1个重复的ACK之间的所有中间报文段的确认。这一步采用的是拥塞避免,因此当分组丢失时我们将当前的速率减半。
参考资料:
1. 《TCP/IP详解卷——卷1协议》 机械工业出版社