TCP的拥塞控制

       拥塞控制与流量控制的关系密切,他们之间也存在着一些差别。所谓拥塞控制就是防止过多的数据注入网络中,这样可以使网络中的路由器或链路不致过载。拥塞控制所要作的都有一个前提,就是网络能够承受现有的网络负荷。拥塞控制是一个全局性的过程,涉及到所有的主机、所有的路由器,以及与降低网络传输性能有关的所有因素。但TCP连接的端点只要迟迟不能收到对方的确认信息,就猜想在当前网络中的某处可能发生了拥塞,但这时却无法知道拥塞到底发生在网络的何处,也无法知道发生拥塞的具体原因。

       流量控制往往是指点对点通信量的控制,是个端到端的问题(接收端控制发送端)。流量控制所要作的是抑制发送端发送数据的速率,以便使接收端来得及接受

       可以用一个例子来说明这种区别。设某个光纤网络的链路传输速率为1000Gbit/s。有一台巨型计算机向一台个人电脑以1Gbit/s的速率传送文件。显然,网络本身的带宽是足够大的,因而不存在产生拥塞的问题。但流量控制却是必须的,因为巨型计算机必须经常停下来,以便使个人电脑来得及接收。

       但如果有另一个网络,其链路传输速率为1Mbit/s,而有1000台大型计算机连接在这个网络上,假定其中的500台计算机分别向其余的500台计算机以100kbit/s的速率发送文件。那么现在的问题已不是接收端的大型计算机是否来得及接收,而是整个网络的输入负载是否超过网络所能承受的。

TCP的拥塞控制方法

      TCP进行拥塞控制的方法有四种,即慢开始(slow-start)、拥塞避免(congestion-avoidance)、快重传(fast retransmit)和快恢复(fast recover)。下面我们就介绍这些算法。为了简便,我们假定:

1) 数据是单方向传送的,对方只传送确认报文。

2) 接收方总是有足够大的缓存空间,因而发送窗口的大小由网络的拥塞程度来决定。

1. 慢开始和拥塞避免

      下面讨论的拥塞控制也叫做基于窗口的拥塞控制。为此,发送方维持一个叫做拥塞窗口cwnd(Congestion window)的状态变量。拥塞窗口的大小取决于网络的拥塞程度,并且动态地在变化。发送方让自己的发送窗口等于拥塞窗口。

       发送方控制拥塞窗口的原则是:只要网络没有出现拥塞,拥塞窗口就可以再增大一些,以便把更多的分组发送出去,这样就可以提高网络的利用率。但只要网络出现拥塞或有可能出现拥塞,就必须把拥塞窗口减小一些,以减少注入到网络中的分组数,以便缓解网络出现的拥塞。

      现在通信线路的传输质量一般都很好,因传输出差错而丢弃分组的概率是很小的。因此,判断网络拥塞的依据就是出现了超时。

      下面我们讨论拥塞窗口cwnd的大小是怎样变化的。我们从“慢开始算法”讲起。

       慢开始算法的思路是这样的:当主机开始发送数据时,由于并不清楚网络的负荷情况,所以如果立即把大量数据字节注入到网络,那么就有可能引起网络发生拥塞。经验证表明,较好的办法是先探测一下,即由小到大逐渐增大发送窗口,也就是说,由小到大逐渐增大拥塞窗口的数值。

       旧的规定是这样的:在刚开始发送报文段时,先把初始拥塞窗口cwnd设置为1至2个发送方的最大报文段SMSS(Sender Maximum Segment Size)的数值,但新的RFC 5681把初始拥塞窗口cwnd设置为不超过2至4个SMSS的数值,具体规定如下:

        若SMSS > 2190 字节 ,则设置初始拥塞窗口 cwnd = 2 * SMSS字节,且不得超过2个报文段。

        若SMSS > 1095 且SMSS < 2190字节,  则设置初始拥塞窗口cwnd = 3 * SMSS字节,且不得超过 3 个报文段。

        若 SMSS <= 1095字节,  则设置初始拥塞窗口cwnd = 4* SMSS 字节,且不得超过4个报文段。

        可见这个规定就是限制初始拥塞窗口的字节数。

慢开始规定,在每收到一个新的报文段的确认后,可以把拥塞窗口增加最多一个SMSS的数值,就是

                                        拥塞窗口cwnd 每次的增加量 = min(N, SMSS)

       其中N是原来未被确认的,但现在被刚收到的确认报文段所确认的字节数。不难看出,当N<SMSS时,拥塞窗口每次的增加量要小于SMSS。

       下面用例子说明慢开始算法的原理,注意,虽然实际上TCP是用字节数作为窗口大小的单位,但为叙述起见,我们用报文段的个数作为窗口大小的单位,这样可以使较小的数字来阐明拥塞控制的原理。

       在一开始发送方先设置cwnd = 1,发送第一个报文段M1,接收方收到后确认M1,发送方收到对M1的确认后,把cwnd从1增大到2,于是发送方接着发送M2和M3两个报文段,接收方收到后发回对M2和M3的确认。发送方每收到一个对新报文段的确认(重传的不算在内)就使发送方的拥塞窗口加1,因此发送方在收到两个确认后,cwnd就从2增大到4,并可发送M4~M7共4个报文段(下图)。因此使用慢开始算法后,每经过一个传输轮次,拥塞窗口cwnd就加倍

TCP的拥塞控制

       一个传输轮次所经历的时间其实就是往返时间RTT(RTT并非恒定的数值),使用“传输轮次”是更加强调:把拥塞窗口所允许发生的报文段都连续发送出去,并收到了对已发送的最后一个字节的确认。例如,拥塞窗口cwnd的大小是4个报文段,那么这时的往返时间RTT就是发送方连续发送4个报文段,并收到这4个报文段的确认,总共经历的时间。

       在TCP的实际运行中,发送方只要收到一个对新报文段的确认,其拥塞窗口cwnd就立即加1,并可以立即发送新的报文段,而不需要等这个轮次中所有的确认都收到后(如上图)再发送新的报文段。

       为了防止拥塞窗口cwnd增长过大引起网络拥塞,还需要设置一个慢开始门限ssthresh状态变量。慢开始门限ssthresh用法如下:

                   当cwnd < ssthresh时,使用上述的慢开始算法

                   当cwnd > ssthresh时,  停止使用慢开始算法而改用拥塞避免算法

                   当cwnd = ssthresh时 , 即可以使用慢开始算法,也可以使用拥塞避免算法。

       拥塞避免算法的思路是让拥塞避免窗口cwnd缓慢的增大,即每经过一个往返时间RTT就把发送方的拥塞窗口cwnd加1。(注意,现在将的是原理,把窗口的单位改为报文段的个数,实际上应当是“拥塞窗口仅增加一个MSS的大小,单位是字节”。在具体实现拥塞避免算法的方法时可以这样来完成:只要收到一个新的确认,就使用拥塞窗口cwnd增加(MSS * MSS /cwnd)个字节,例如,假定cwnd等于10个MSS的长度,而MSS是1460字节。发送方可一连发送14600字节(即10个报文段)。假定接收方每收到一个报文段就发回一个确认。于是发送方每到一个新的确认,就把拥塞窗口稍微增大一些,即增大0.1 MSS = 146字节。经过一个往返时间RTT(或一个传输轮次)后,发送方共收到10个新的确认,拥塞窗口就增大了1460字节,正好是一个MSS的大小。)而不是像慢开始阶段那样加倍增长。因此在拥塞避免阶段就有“加法增大”AI的特点。这表明在拥塞避免阶段,拥塞窗口cwnd按线性规律缓慢增长,比慢开始算法的拥塞窗口增长速率缓慢得多。

TCP的拥塞控制

       看上图说明了在拥塞控制的过程中,TCP的拥塞窗口cwnd是怎样变化的,图中的数字(1)到(5)是特别要注意的几个点,现假定TCP的发送窗口等于拥塞窗口。                                                                                               

       当TCP连接进行初始化时,把拥塞窗口cwnd置为1。为了便于理解,上图中的窗口单位不使用字节而是用报文段的个数。在本例中,慢开始门限的初始值置为16个报文段,即ssthresh = 16。在执行慢开始算法时,发送方每收到一个对新报文段的确认ACK,就把拥塞窗口值加1,然后开始下一轮的传输(图中的横坐标是传输轮次,不是时间)。因此拥塞窗口cwnd随着传输轮次按指数规律增长,当拥塞窗口cwnd增长到慢开始门限值ssthresh时(图中点(1),此时拥塞窗口cwnd=16),就改为执行拥塞避免算法,拥塞窗口按线性规律增长,但注意,“拥塞避免”并非完全能够避免拥塞,“拥塞避免”是说把拥塞窗口控制为线性规律增长,使网络比较不容易出现拥塞。

    当拥塞窗口cwnd=24时,网络出现了超时(图中点(2)),发送方判断为网络拥塞,于是调整门限值ssthresh=cwnd/2=12,同时设置拥塞窗口cwnd=1,进入慢开始阶段。

       按照慢开始算法,发送方每收到一个对新报文段的确认ACK,就把拥塞窗口值加1。当拥塞窗口cwnd=ssthresh = 12时(图中点(3),则是新的ssthresh值),改为执行拥塞避免算法,拥塞窗口按线性规律增大。

       当拥塞窗口cwnd=16时(图中点(4)),出现了一个新的情况,就是发送方一连收到3个对同一个报文段的重复确认(图中记为3-ACK)。这是因为:有时,个别报文段会在网络中丢失,但实际上网络并未发生拥塞。如果发送方迟迟收不到确认,就会产生超时,就会误认为网络发生了拥塞。这就导致发送方错误地启动慢开始,把拥塞窗口cwnd又设置为1,因而降低了传输效率。

       采用快重传算法可以让发送方尽早知道发生了个别报文段的丢失。快重传算法首先要求接收方不要等待自己发送数据时才进行捎带确认,而是要立即发送确认,即使收到了失序的报文段也要立即发出对已收到的报文段的重复确认。如下图。接收方收到了M1和M2后都分别及时发出了确认,现假定接收方没有收到M3的但收到了M4,本来接收方可以什么都不做。但按照快重传算法,接收方必须立即发送对M2的重复确认,以便让发送方及早知道接收方没有收到报文段M3。发送方接着发送M5和M6,接收方收到后也仍要再次分别发出对M2的重复确认,这样,发送方共收到了接收方的4个对M2的确认,其中后3个都是重复确认。快重传算法规定,发送方只要一收到3个重复确认,就知道接受方确实没有收到报文段M3,因而应当立即进行重传(即“快重传”),这样就不会出现超时,发送方也不就会认为出现了网络拥塞。

TCP的拥塞控制

       因此在上上图中的点(4),发送方知道现在只是丢失了个别的报文段,于是不启动慢开始,而是执行快恢复算法。这时,发送方调整门限值ssthresh = cwnd/2 = 8,同时设置拥塞窗口cwnd=ssthresh = 8(上上图中点(5)),并开始执行拥塞避免算法。

      请注意,也有的快恢复实现是把快恢复开始时的拥塞窗口cwnd值再增大一些(增大3个报文段的长度),即等于新的ssthresh+3* MSS。这样做的理由是:既然发送方收到了3个重复的确认,就表明有3个分组已经离开了网络。这三个分组不再消耗网络的资源而是停留在接收方的缓存中(接收方发送出3个重复的确认就证明了这个事实)。可见现在网络中并不是堆积了分组而是减少了3个分组。因此可以适当把拥塞窗口扩大些。

       从上上图可以看出,在拥塞避免阶段,拥塞窗口时按照线性规律增大的,这常称为加法增大AI(Additive Increase)。而一旦出现超时或3个重复的确认,就要把门限值设置为当前拥塞窗口值的一半,并大大减小拥塞窗口的数值。这称为“乘法减小”MD(Multiplicative Decrease)。二者合在一起就是所谓的AIMD算法。

       根据以上所述,TCP的拥塞控制可以归纳为下图的流程图。

TCP的拥塞控制

      在这里,我们刚开始就假定了接收方总是有足够大的缓存空间,因而发送窗口的大小由网络的拥塞程度来决定。但实际上接收方的缓存空间总是有限的。接收方根据自己的接收能力设定了接收方窗口rwnd,并把这个窗口值写入TCP首部中的窗口字段,传送给发送方。因此,接收方窗口又称为通知窗口。因此,从接收方对发送方的流量控制的角度考虑,发送方的发送窗口一定不能超过对方给出的接收方窗口值rwnd。

       如果把这里所讨论的拥塞控制和接收方对发送方的流量控制一起考虑,那么很显然,发送方的窗口的上限值应当取为 接收方窗口rwnd和拥塞窗口cwnd这两个变量中较小的一个,也就是说:

                                    发送方窗口的上限值 = Min[ rwnd, cwnd  ]

上式指出:

      当rwnd < cwnd 时,是接收方的接收能力限制发送方窗口的最大值。

      反之,当cwnd < rwnd 时,则是网络的拥塞程度限制发送方窗口的最大值。

      也就是说,rwnd和cwnd中数值较小的一个,控制了发送方发送数据的速率。