流量控制和拥塞控制并不尽相同,但它们之间仍有关联。流量控制是基于双方发送和接收速率之间来考虑的,而拥塞控制则是基于网络中链路带宽和中间设备能力来考虑的。
流量控制可能对拥塞控制有所帮助
流量控制是采用滑动窗口协议来实现的,TCP都以数据段为单位进行传输,接收端通过TCP头来识别所接收的数据属于哪个段。只要数据段没有完全接收,接收端就不会认为已接收该数据段。
通信双方都分别有一个发送窗口和接收窗口,窗口大小又具体分为物理窗口大小和可用窗口大小。所谓物理窗口就是实际的窗口缓存大小,而可用窗口则是随着使用情况和变动,这一个是要通报给另一端的。
下图就是一个滑动窗口示例:
假设发送端收到接收端发来的确认数据段,确认号为301,窗口大小为500,表示可以连续发送5个数据段(起始为301),然后就需要停下来等待对方确认,否则则会超出发送窗口大小。
假定一段时间后收到了确认号为501的确认数据段,则从本机发送窗口删除301、401两个数据段,则其体现为窗口向前滑动200个字节。从图中虚线移到了图中实线的位置。
但假定这个确认号为501的确认数据段同时返回的窗口大小为400,则这次只能发送未被发送的801数据段(因为501及后的数据段未被确认)。
假定一段时间后又收到了确认号为801的确认数据段,同时返回的窗口大小为500,则如前者一样窗口前移,因为上一步已经发送了801数据段,所以此时发送窗口中仍有801数据段等待确认。因此,这一次则发送901~1201这个4个数据段。
以上就是滑动窗口的实现机理。
如果数据传输过程中,整个数据段丢失,则发送端永远都等不到这些数据段的确认段,这时就要使用超时重传机制。
但仍然存在一个问题,如果假定某个时间对方发送来一个窗口大小为0的数据段,那么必然不能再发送任何数据,直到收到一个窗口大小不为0的数据段,假定该数据段丢失,那么对于对端而言,它一直在等待新数据的到来,而对于接收方而言,它也一直在等待一个新的窗口大小不为0的数据段,这就会导致死锁。
因此,TCP引入了一个持续计时器(Persistence Timer),在收到窗口大小为0的数据段时,不断的向对端发送探测数据段(一般仅携带1字节的无效数据),这时,就可以不停的获得确认数据段了。当确认数据段窗口大小仍然为0,则会重启该计时器,重复这一过程,直到有可用的窗口大小为止。
虽然这部分并不属于流量控制的范畴,但因为它和流量控制的特性息息相关,因此放在这部分稍作提及。
对于大型的数据传输,几十字节的报头加起来并不算什么问题,几乎不影响传输效率。但在一些交互式应用中,每次只传输一个或几个字节,这样的报头就显得非常昂贵了,而且由于确认数据段的存在,就显得更加的昂贵。
因为如此,所以确认数据段一般会捎带着数据一起发送,这就涉及到什么时候才组装成一个数据段发送的问题,这里有两个算法:Nagle算法和Clark算法。
如果数据每次以1字节的方式进入发送端,每收到1字节就发送1字节显然非常低效。
因此该算法提出发送端先发送第一个字节,等待直到这第一个字节被确认为止,等待过程中缓存在发送窗口的数据组装在一个数据段发送,随后重复这个过程,直到所有数据发送完毕。这样可以有效的降低因每次携带数据量过小造成的效率降低。
同时,它还规定如果数据已经达到发送窗口的一半或者已经到达数据段的最大长度时,也发送。
如果数据每次以1字节的方式被应用层取出,就会出现确认段每次只确认1个字节的数据,而且窗口满后大小字段维持在1个字节,显然这也会影响效率。
因此该算法提出,禁止接收端发送窗口大小为1的数据段,即等待到窗口有足够空间可以容纳到一个最大数据段的长度,或者缓存空间已有一半为空时才发送确认数据段。这时,窗口大小肯定不为1了,这样发送端就可以一次性发送更多的数据,从而提高传输效率。
TCP中最复杂的功能可能就是拥塞控制了,因为传输途中经过的网络数量、类型、性能都非常不确定,但这又严重的影响TCP连接拥塞控制的复杂性和难度。
TCP拥塞控制主要依靠双方协商降低数据发送速率来实现的。
途中可能出现的情况非常的复杂,因此一般把整个网络有效处理负荷能力成为吞吐量(Throughout),而自发送端的符合称为输入负荷(Input Load),在理想的情况下它们之间有如下图线性关系。
可以看出,吞吐量随着输入负荷同步提高,呈现45°上升,这是健康的。但是吞吐量是有上限的,到了这时,输入负荷在如何提升,吞吐量也不会提高了。这时就是出现了拥塞现象。
但上图只是一种理想状态,事实上,在拥塞时,吞吐量是会下降的。最终还会使网络的吞吐量下降为0,呈现死锁状态,如下图所示:
在了解了吞吐量和输入负荷之间的关系之后,就会发现要设计一套有效的拥塞控制方案并不容易,因为拥塞相关的参数总是在变的。
为了防止网络出现拥塞现象,出现了一系列的拥塞控制方案,最初出现的是在1988年提出的由慢启动和拥塞避免组成的控制方案,后期又加入了快速重传和快速恢复方法。下面一一介绍。
这是初期预防方案,基本思想就是TCP连接正式传输数据时每次发送的数据大小是逐渐增大的,也就是先从小字节试探性的发送,在收到确认数据段后再慢慢增加发送的数据量,直到到达慢启动阈值(SSTHRESH)。
在该方案中,除了要根据接收端的窗口大小调整外,还要维护一个拥塞窗口(CWND, Congestion Window),它是为了避免拥塞而设置的,最终允许发送的字节数是发送窗口和拥塞窗口的最小值。
假设拥塞窗口总是小于接收端的接收窗口,其具体步骤如下:
下图是一个示例:
不过可以预见的是,CWND不可能无限制的增长,于是就有了慢启动阈值(SSTHRESH, Slow Start Threshold),其初始值为65535字节(64KB),当这个过程发生数据丢失时,SSTHRESH置为当前CWND的一半,然后CWND重置为1,继续这个过程。但如果CWND再次触及到SSTHRESH时(这时为原CWND的一半),就要停止使用慢启动方案,采用拥塞避免来解决。
其基本思想就是CWND在第二次到达SSTHRESH时,让CWND每经过一个RTT时间仅值加1,而不是原来的翻倍,从指数增长变为线性增长。
显然,这种方案CWND的增长速度会明显慢于慢启动方案,当再次出现数据丢失时,又会把SSTHRESH减为当前CWND的一半,同时把CWND重置为1,又返回慢启动过程,两者往复循环,以此类推。
下图就是这两种拥塞控制方案的示例,它假设最初已经发生过一次数据丢失,SSTHRESH已减为32KB:
可以看见,慢启动和拥塞避免组成了一套降低拥塞的方案,先通过慢启动快速接近网络拥塞点,然后使用拥塞避免法逐步接近网络拥塞点,既可以降低网络负荷也可以更有效的利用网络。
快速重传的基本思想是,当接收端收到一个不按序的数据段时,迅速发送一个重复确认数据段,而不用等到有数据需要发送时顺带发送。在收到3个重复的确认数据段后,即认为对应确认号的数据段已经丢失,而无需等待重传定时器就重传看起来已经丢失的数据。
其流程如下图所示:
在快速重传机制启动后,快速恢复同时启动,快速恢复的基本思想是:收到3个重复的确认数据段时,就把当前CWND值设为SSTHRESH的一半,以减轻网络负荷,然后执行拥塞避免算法。
可以看到这种方法和慢启动不同的是,它通过双方交互来实现,它通过接收端的快速响应来尽快的调整CWND和SSTHRESH值,而不用等待超时。