TCP/IP详解--拥塞控制 慢启动 快恢复 拥塞避免
1. 慢开始和拥塞避免
拥塞窗口:
发送方维持一个拥塞窗口 cwnd ( congestion window )的状态变量。拥塞窗口的大小取决于网络的拥塞程度,并且动态地在变化。发送方让自己的发送窗口等于拥塞。
慢开始算法:
在刚刚开始发送报文段时,先把拥塞窗口 cwnd 设置为1个最大报文段MSS的数值,而后每收到一个对新的报文段的确认,就把拥塞窗口增加1个MSS的数值,这样拥塞窗口cwnd的值就随着传输轮次(一个轮次即发送完一个cwnd的MSS)呈指数级增长,事实上,慢启动的速度一点也不慢,只是它的起点比较低一点而已。用这样的方法逐步增大发送方的拥塞窗口 cwnd ,可以使分组注入到网络的速率更加合理。
一个传输轮次所经历的时间其实就是往返时间RTT,不过“传输轮次”更加强调:把拥塞窗口cwnd所允许发送的报文段都连续发送出去,并收到了对已发送的最后一个字节的确认。
慢开始门限ssthresh:
为了防止拥塞窗口cwnd增长过大引起网络拥塞,还需要设置一个慢开始门限ssthresh状态变量(如何设置ssthresh)。慢开始门限ssthresh的用法如下:
当 cwnd < ssthresh 时,使用上述的慢开始算法。
当 cwnd > ssthresh 时,停止使用慢开始算法而改用拥塞避免算法。
当 cwnd = ssthresh 时,既可使用慢开始算法,也可使用拥塞控制避免算法。
拥塞避免算法:
当cwnd >= ssthresh时,就会进入“拥塞避免算法”,让拥塞窗口cwnd缓慢地增大,每收到1个ACK拥塞窗口cwnd = cwnd + 1/cwnd,即每经过一个传输轮次就把发送方的拥塞窗口cwnd加1。这样拥塞窗口cwnd按线性规律缓慢增长,比慢开始算法的拥塞窗口增长速率缓慢得多。
网络拥塞:
无论在慢开始阶段还是在拥塞避免阶段,只要发送方判断网络出现拥塞(超时计时器RTO时限已到但还没有收到确认),就要把慢开始门限ssthresh=cwnd/2(>=2),然后把拥塞窗口cwnd=1,执行慢开始算法。这样做的目的就是要迅速减少主机发送到网络中的分组数,使得发生拥塞的路由器有足够时间把队列中积压的分组处理完毕。
如下图,用具体数值说明了上述拥塞控制的过程。现在发送窗口的大小和拥塞窗口一样大。
1). 当TCP连接进行初始化时,把拥塞窗口cwnd置为1MSS,慢开始门限的初始值设置为16个MSS,即 ssthresh = 16 。
2). 在执行慢开始算法时,拥塞窗口 cwnd 的初始值为1。以后发送方每收到一个对新报文段的确认ACK,就把拥塞窗口cwnd加1,则每个传输轮次拥塞窗口cwnd翻倍,因此在慢开始阶段拥塞窗口cwnd 随着传输轮次按指数规律增长。
3). 当拥塞窗口cwnd增长到慢开始门限值ssthresh时(即当cwnd=16时),就改为执行拥塞控制算法,每个传输轮次拥塞窗口cwnd加1,因此在拥塞控制阶段拥塞窗口cwnd 随着传输轮次按线性规律增长。
4). 假定拥塞窗口的数值增长到24时,超时计时器RTO时限已到但还没有收到确认,则认为网络出现超时(这很可能就是网络发生拥塞了)。更新后的ssthresh值变为ssthresh/2=12,拥塞窗口cwnd=1,然后回到第2)步继续运行。
强调:“拥塞避免”并非指完全能够避免了拥塞。利用以上的措施要完全避免网络拥塞还是不可能的。“拥塞避免”是说在拥塞避免阶段将拥塞窗口控制为按线性规律增长,使网络比较不容易出现拥塞。
2. 快重传和快恢复
快重传算法:
快重传算法首先要求接收方每收到一个失序的报文段就立即发出重复确认(为的是使发送方及早知道有报文段没有到达对方),然后若是发送方接收到3个重复确认ACK,则启动快重传算法。
接收方收到了M1和M2后都分别发出了确认。现在假定接收方没有收到M3但接着收到了M4、M5、M6。显然,接收方不能确认M4、M5、M6,因为M4、M5、M6是收到的失序报文段。按照快重传算法的规定,接收方应及时发送对M2的重复确认,这样,发送方共收到了接收方的四个对M2的确认,其中后三个都是重复确认。
快重传算法还规定,发送方只要一连收到三个重复确认就应当立即重传对方尚未收到的报文段M3,而不必继续等待M3设置的重传计时器RTO到期。
快速恢复算法:
TCP Reno算法定义在RFC5681。快速重传和快速恢复算法一般同时使用。快速恢复算法认为,你还有3个Duplicated Acks说明网络也不那么糟糕,所以没有必要像RTO超时那么强烈,并不需要重新回到慢启动进行,这样可能降低效率。所以协议栈会做如下工作:
1). 拥塞窗口减半cwnd=cwnd/2
2). 慢开始门限ssthresh=cwnd(减半后的值)
然后启动快速恢复算法:
1). 设置cwnd = ssthresh+ack个数(一般情况下会是3个Dup Acks),然后重传指定的数据包;
2). 如果再次收到Dup Acks,则cwnd=cwnd+1, 并且在条件允许的情况下发送下一个报文段(非重传报文段)
3). 如果收到新的ACK, 则快速恢复算法结束,设置cwnd = ssthresh, 进入拥塞避免阶段
上面这个算法是有问题,那就是——它依赖于3个重复的Acks。因为,3个重复的Acks并不代表只丢了一个数据包,很有可能是丢了好多包。但这个算法只会重传一个,而剩下的那些包只能等到RTO超时,于是,进入了恶梦模式——超时一个阻塞窗口减半,多个超时会超成TCP的传输速度呈级数下降,而且也不会触发快速恢复算法了。
TCP New Reno算法:
当发送者这边收到了3个Duplicated Acks,进入快速恢复模式,开始重传重复acks指示的那个包。如果只有这一个包丢了,那么,重传这个包后回来的ack会把整个已经被发送方传输出去的数据的ack返回来。如果没有的话,说明有多个包丢了。我们叫这个ACK为Partial ACK。
一旦发送方这边发现了Partial ACK出现,那么发送方就可以推理出来有多个包被丢了,于是继续重传滑动窗口里未被ack的第一个包。直到再也收不到了Partial Ack,才真正结束快速恢复这个过程。
接收方根据自己的接收能力设定了接收窗口rwnd,并把这个窗口值写入TCP首部中的窗口字段,传送给发送方。因此,接收窗口又称为通知窗口。因此,从接收方对发送方的流量控制的角度考虑,发送方的发送窗口一定不能超过对方给出的接收窗口rwnd 。
发送方窗口的上限值 = min [ rwnd, cwnd ]
当rwnd < cwnd 时,是接收方的接收能力限制发送方窗口的最大值。
当cwnd < rwnd 时,则是网络的拥塞限制发送方窗口的最大值。