前面提过的滑动窗口是TCP实现流量控制的一个主要手段,而这一次介绍的四个概念是TCP实现拥塞控制的主要方法。很多人会分不清流量控制和拥塞控制的区别,两者看起来都和保持信道的通畅的方法,但是对象确是不同的,如何区分这两个概念,其实只要记住一点就可以了。流量控制考虑的是端对端的通畅,而拥塞控制是考虑的全局的通畅。下面就具体介绍下拥塞控制的四大金刚吧。
在介绍具体的概念之前,首先我要提到的是TCP中对于拥塞控制的最高思想,主要有三条,那就是既要尽量快的利用信道的最大能力,不要浪费;又要谨慎的控制信道的负载,不要拥堵;同时如果发生了拥堵,必须能够自我调节,不要崩溃。根据这个最高思想,TCP设计了一系列拥塞避免的算法。
慢启动
在一个网络环境下,信道是共享的,也就是说所有发送的数据都会在这个信道上穿梭。这个道理和高速公路是一样的,你只是高速公路上车流中的一辆车。所以说,当你准备开始发送数据的时候,你不能仅仅做到不管三七二十一,就先按自己的节奏发出自己的数据。如果这个时候信道已经很拥挤,你发出的数据一定也会堵在路上,从而造成进一步的重传,这些重传更加加重了网络的负担,本来拥堵不堪的道路只会越来越差。
所以TCP的设计者们设计了一个保守的策略,这种策略带有浓浓的工科生特点,就是先谨慎的试试,再大胆的前进,实践检验效果。具体的说就是TCP的设计者们采用了一个新的窗口,称之为拥塞窗口,记为cwnd,这个cwnd初始设置为一个one segment的大小,在这里我们简单的理解为TCP报文一次允许发送的最大的大小以方便后面叙述。当发出这个一个报文之后,如果发送端能够收到对应的ACK。那么下一次就会发送2个segment的报文,接着如果还是收到这2个segment的ACK,那么说明这个信道还是可以承受当前大小的包的,那么接着进行试探,发送4个segment的包,收到4ACK,就发送8个segment,以此指数级增长的类推。
简单的说,慢启动就是一句话,大胆尝试,小心求证。而且在慢启动算法中,报文的增长速度是很快的,所以说这个算法的过程并不符合这个算法的名称,之所以称之为慢启动,是他小心的去试探网络情况的哲学上的"慢慢来"。
拥塞避免
上面的慢启动很明显可以在很短的时间内让信道的利用率达到一个比较理想的值,由于前面一直强调过,信道的资源总是有限的,所以到某一个阶段,这些快速增长的报文必定会充满信道使得信道变得开始拥挤。TCP的设计者们为了不要使得慢启动算法变成一个只有理论意义,而在很短的时间内又会造成网络崩溃从而导致本末倒置。所以说在慢启动的基础上,设计者们做了一些现实的改进与妥协。除了cwnd这个概念,设计者们还定义了一个叫做ssthresh的概念,slow start thresh, 中文一般翻译为慢启动阈值,比如65535个字节。如果cwnd的大小达到了这个阈值,那么就不采用指数增长这样的比较暴力的方法,而是采用每次收到一个ACK,cwnd就扩大1个单位的方法,这样比较保守,目的之一就是慢慢而可靠的试出来信道所能承受的最大的负载,符合前面所说的哲学的第一条。
但是信道资源总是有限的,而cwnd在前面描述的过程中无论是怎样的方式,他一直递增的,所以触碰到天花板是必然,按照前面的“三条不要”的最高思想,在遇到信道已经塞满的情况下,需要做的就是快速的减少对信道的负担并且能够快速的恢复。
TCP怎么知道信道拥堵了呢?反过来问,信道拥堵会给发送端带来什么呢?第一个明显的效应就是发送出去的消息收不到ACK,也就是前面说的超时了,如果连续几个消息都没有收到ACK而不停的发生重传,那么就可以判定为信道已经拥堵了。这个时候,拥塞避免算法会进入恢复阶段,其方法很简单:
- ssthresh设置为当前发生拥塞cwnd的一般,如果cwnd在30个字节的时候(当然不可能只有这么小)发生了拥堵,那么新的ssthresh就设置为15。
- TCP重新进入慢启动阶段,也就是将cwnd设置为1,指数增长知道达到新的ssthresh的15,然后再重新按照拥塞避免第一个阶段,线性增长。
这个过程可以用图表示:
快速重传
网络是一个很复杂的环境,如果有这么一种情况,网络发生了拥堵但是又那么的拥堵,这种情况的表现是什么呢?按照前面介绍过的滑动窗口,TCP不是one by one的发送数据包的,如果发送的数据包是1,2,3,1和3已经到,但是2没有到,由于拥堵在网络中丢失了,那么接收端会不断告诉发送端下一个需要的报文是2号报文,即使你后面的报文都到了,在2号报文没有收到的情况下,会一直发送对1号报文的ACK,表示需要的是2号报文。如果连续收到三个连续的ACK,就认为网络发生了拥堵。用语言描述有点绕,用图来表示就比较清晰。
这种情况说明了两种情况:网络确实发生了拥堵,但是又没有完全拥堵。因为如果完全拥堵了,那么发送端也不会受到三个ACK数据报文,所以这种情况没有必要从头再来,因为最高思想的第二点让我们最大的利用信道的能力。按照这个,设计者们又改进了上面的算法,提出了一个快速重传的方案,其思想如下:
- ssthresh设置为cwnd的一半
- cwnd设置为ssthresh的值
- 不需要重新进入慢启动阶段而是进入拥塞避免阶段
用图来表示这个过程如下:
快速恢复
快速重传算法已经尽力快的恢复对于网络的传送,但是设计们本着"面包里面抠面粉"的原则,在上面的快速重传算法中尝试想想有没有进步空间,在全面分析之后,提出了快速恢复的算法,其具体做法如下:
- 在收到3个重复的ACK之后,ssthresh设置为cwnd的一半,然后把cwnd设置为ssthresh加3个单位的大小,接着重传丢失的报文段,如果用前面的例子来举例就是重传2号报文。
- 如果这个时候再次收到重传的ACK,那么拥塞窗口增加1。
- 如果收到的是新的数据包的ACK,把cwnd设置为第一步的ssthresh的值。为什么这么做,因为如果收到的新的ACK,说明网络已经恢复了,可以进入拥塞避免的线性增长阶段了。
第一个例子里为什么加3呢,因为这个时候连续的收到3个ACK包,那么可以认为网络还有3个单位大小的余额,同时也可以这么想,说明有3个“老”的数据包已经从网络上离开了。
这就是拥塞控制的四个基本算法,当然在实际中还有很多更加联系实际的算法,用兴趣可以搜索Reno,NewReno之类的关键词,慢慢探索,可以得到很深入的研究。