计算机网络(七)--TCP的可靠传输原理及实现(二)

来源:互联网 发布:win10 app设计软件 编辑:程序博客网 时间:2024/05/15 03:46

TCP的流量控制                所谓的流量控制(flow control)就是让发送方的发送速率不要太快,要让接收方来得及接收。

利用滑动窗口实现流量控制

设A向B发送数据。在连接建立时,B告诉A:“我的接收窗口rwnd=400”(这里rwnd表示receiver window)。

接收方的主机B进行了三次流量控制。将rwnd从400到300到100到0,rwnd=0即不允许发送方再发送数据了。这种使发送方暂停发送的状态将持续到主机B重新发出一个新的窗口值为止。B向A发送的三个报文段都将首部中的确认位ACK置为1,只有在ACK=1时确认号字段才有意义。

问题:B向A发送了零窗口的报文段后不久,B的接收缓存又有了一些存储空间。于是B向A发送rwnd=400的报文段,然而这个报文段在传送过程中丢失了。A一直等待收到B发送的非零窗口的通知,而B也一直等待A发送的数据。这种互相等待的死锁局面将一直延续下去。

解决方案:TCP为每一个ie连接设有一个持续计时器(persistence timer)。只要TCP连接的一方收到对方的零窗口通知,就启动持续计时器。若持续计时器设置的时间到期,就发送一个零窗口探测报文段(仅携带1字节的数据),而对方就在确认这个探测报文段时给出了现在的窗口值。如果窗口仍是零,那么收到这个报文段的一方就重新设置持续计时器。否则僵局就打破了。


必须考虑传输效率

应用进程把数据传送到TCP的发送缓存后,剩下的发送任务就由TCP来控制了。可以用不同的机制来控制TCP报文段的发送时机。

第一种机制,TCP维持一个变量,它等于最大报文段长度MSS。只要缓存中存放的数据达到MSS字节时,就组装成一个TCP报文段发送出去。

第二种机制,由发送方的应用进程指明要求发送报文段,即TCP支持的推送(push)操作。

第三种机制,发送方的一个计时器期限到了,这时就把当前已有的缓存数据装入报文段(但长度不能超过MSS)发送出去。


如何控制TCP发送报文段的时机仍然是一个较为复杂的问题。

例子:一个交互式用户使用一条TELNET连接(运输层为TCP协议)。假设用户只发1个字符。加上20字节的首部后,得到21字节长的TCP报文段。再加上20字节的IP首部,形成41字节长的IP数据报。在接收方TCP立即发送确认,构成的数据报是40字节长(假设没有数据发送)。若用户要求远地主机回送这一字符,则又要发回41字节长的IP数据报和40字节长的确认IP数据报。这样,用户仅发1字节时线路上就需要传送总长度为162字节共4个报文段。这种传送方法的效率不高,因此应适当推迟发回确认报文,并尽量使用捎带确认的方法。

TCP的实现中广泛使用Nagle算法。算法:若发送应用进程把要发送的数据逐个字节地送到TCP的发送缓存,则发送方就把第一个数据字节先发送出去,把后面到达的数据字节都缓存起来。当发送方收到对第一个数据字符的确认后,再把发送缓存中的所有数据组装成一个报文段发送出去,同时继续对随后到达的数据进行缓存。只有在收到对前一个报文段的确认后才继续发送下一个报文段。当数据到达较快而网络速率较慢时,用这样的方法可明显地减少所用的网络带宽。Nagle算法还规定,当到达的数据已达到发送窗口大小的一半或已达到报文段的最大长度时,就立即发送一个报文段。这样做,就可以有效地提高网络的吞吐量。


糊涂窗口综合症(silly window syndrome),有时也会使TCP的性能变坏。

设想:TCP接收方的缓存已满,而交互式的应用进程一次只从接收缓存中读取1个字节(这样就使接收缓存空间仅腾出1个字节),然后向发送方发送确认,并把窗口设置为1个字节(但发送的数据报是40字节长)。接着,发送方又发来1个字节的数据(请注意,发送方发送的IP数据报是41字节长)。接收方发回确认,仍然将窗口设置为1个字节。这样进行下去,使网络的效率很低。

解决方案:让接收方等待一段时间,使得或者接收缓存已有足够空间容纳一个最长的报文段,或者等到接收方缓存已有一半空闲的空间。只要出现这两种情况之一,接收方就发出确认报文,并向发送方通知当前的窗口大小。此外,发送方也不要发送太小的报文段,而是把数据积累成足够大的报文段,或达到接收方缓存的空间的一半大小。

上述两种方法可配合使用。使得在发送方不发送很小的报文段的同时,接收方也不要在缓存刚刚有了一点小的空间就急忙把这个很小的窗口大小信息通知给发送方。


TCP的拥塞控制

在计算机网络中的链路容量(即带宽)、交换结点中的缓存和处理机制等,都是网络的资源。在某段时间,若对网络中某一资源的需求超过了该资源所能提供的可用部分,网络的性能就要变坏。这种情况就叫做拥塞(congestion)。

问题:当某个结点缓存的容量太小时,到达该结点的分组因无存储空间暂存而不得不被丢弃。现设想将该结点缓存的容量扩展到非常大,会提高网络的性能吗?

解答:若到达该结点的分组均可在结点的缓存中排队,由于输出链路的容量和处理机的速度并未提高,因此在这队列中的绝大多数分组的排队等待时间将会大大增加,结果上层软件只好把它们进行重传(因为早就超时了)。

问题的实质是往往是整个系统的各部分不匹配。只有所有的部分平衡了,问题才会得到解决。

所谓拥塞控制就是防止过多的数据注入到网络中,这样可以使网络中的路由器或链路不致过载。拥塞控制所要做的都有一个前提:网络能够承受现有的网络负荷。

拥塞控制是一个全局性的过程,涉及到所有的主机、所有的路由器,以及与降低网络传输性能有关的所有因素。但TCP连接的端点只要迟迟不能收到对方的确认,就猜想在当前网络中德某处很可能发生了拥塞,但这时却无法知道拥塞到底发生在网络的何处,也无法知道发生拥塞的具体原因(访问某个服务器的通信量过大?还是在某个地区出现了自然灾害)。

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

进行拥塞控制需要付出代价。首先需要获得网络内部流量分布的信息。在实施拥塞控制时,还需要在结点之间交换信息和各种命令,以便选择控制的策略和实施控制。

 


横坐标是提供的负载(offered load),代表单位时间内输入给网络的分组数目。也称为输入负载或网络负载。

纵坐标是吞吐量(throughput),代表单位时间内从网络输出的分组数目。

当提供的负载达到某一数值时,网络的吞吐量随提供的负载的增大而下降,这时网络就进入了拥塞状态。

当提供的负载继续增大到某一数值时,网络的吞吐量就下降到零,网络已无法工作。这就是死锁(deadlock)。

拥塞控制是很难设计的,因为它是一个动态的(而不是静态的)问题。当前网络正朝着高速化的方向发展,这很容易出现缓存不够大而造成 分组的丢失。但分组的丢失是网络发生拥塞的征兆而不是原因。在许多情况下,甚至正是拥塞控制机制本身成为引起网络性能恶化甚至发生死锁的原因。


从控制理论的角度来看拥塞控制问题,可以分为两种:开环控制闭环控制

开环控制方法:在设计网络时事先将有关发生拥塞的因素考虑周到,力求网络在工作时不产生拥塞。但一旦整个系统运行三、起来,就不再中途进行改正。

闭环控制是基于反馈环路的概念。属于闭环控制的有以下几种措施:

1)监测网络系统以便检测到拥塞在何时、何处发生。

2)把拥塞发生的信息传送到可采取行动的地方。

3)调整网络系统的运行以解决出现的问题。

用于监测网络的拥塞的一些主要指标:由于缺少缓存空间而被丢弃的分组的百分数、平均队列长度、超时重传的分组数、平均分组时延、分组时延的标准差等等。上述这些指标的上升都标志着拥塞的增长。


一般在监测到拥塞发生时,要将拥塞发生的信息传送到产生分组的源站。通知拥塞发生的分组同样会使网络更加拥塞。

另一种方法是在路由器转发的分组中保留一个比特或字段,用该比特或字段的值表示网络没有拥塞或产生了拥塞。也可以由一些主机或路由器周期性地发出探测分组,以询问拥塞是否发生。

此外,过于频繁地采取行动以缓和网络的拥塞,会使系统产生不稳定的振荡。但过于迟缓地采取行动又不具有任何实用价值。因此,要采用某种折中的方法。但选择正确的时间常数是相当困难的。


1999年公布的因特网建议标准定义了进行拥塞控制的四种算法:慢开始(slow-start)、拥塞避免(congestion)、快重传(fast retransmit)、快恢复(fast recovery)。

后来RFC又对这些算法进行了一些改进。为了集中精力讨论拥塞控制,假定:

1)数据是单方向传送,而另一个方向只传送确认。

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

                                                                                                                                                                                                                                      

慢开始拥塞避免

发送方维持一个叫做拥塞窗口 cwnd(congestion window)的状态变量。 拥塞窗口的大小取决于网络的拥塞程度,并且动态地在变化。发送方让自己的发送窗口等于拥塞窗口

发送方控制拥塞窗口的原则:只要网络没有出现拥塞,拥塞窗口就再增大一些,以便把更多地分组发送出去。但只要网络出现拥塞,拥塞窗口就减小一些,以减少注入到网络中的分组数。

问题:发送方是如何知道网络发生了拥塞呢?

解答:当网络发生拥塞时,路由器就要丢弃分组。因此只要发送方没有按时收到应当到达的确认报文,就可以猜想网络可能出现了拥塞。现在通信线路的传输质量一般都很好,因传输出差错而丢弃分组的概率是很 小的(远小于1%)。

慢开始算法的思路:因为不清楚网络的负荷情况,所以先探测一下,即由小到大逐渐增大拥塞窗口数值(发送窗口等于拥塞窗口)。通常在刚刚开始发送报文段时,先把拥塞窗口 cwnd 设置为一个最大报文段MSS的数值。而在每收到一个对新的报文段的确认(注意是每一个确认)后,把拥塞窗口增加至多一个MSS的数的数值。

为了方便起见,用报文段的个数作为窗口大小的单位(实际上TCP是用字节作为窗口的单位),这样可以用使用较小的数字说明拥塞控制的原理。

使用慢开始算法后,每经过一个传输轮次(transmission round),拥塞窗口 cwnd 就加倍。

问题:一个传输轮次所经历的时间好像就是往返时间RTT?

解答:“传输轮次”更加强调把拥塞窗口cwnd所允许发送的报文段都连续发送出去,并收到了对已发送的最后一个字节的确认。即cwnd的大小是4,则这时的往返时间是发送方连续发送4个报文段, 并收到这4个报文段的确认,总共经历的时间。


为了防止拥塞窗口增长过大引起网络拥塞,需要设置一个慢开始门限ssthresh状态变量。
当cwnd > ssthresh 时,停止使用慢开始算法而改用拥塞避免算法。
拥塞避免算法的思路:让拥塞窗口cwnd缓慢地增大,即每经过一个传输轮次就把发送方的拥塞窗口加一。
无论在慢开始阶段或拥塞避免阶段只要发送方判断网络出现拥塞(其根据是否按时收到确认),就把慢开始门限ssthresh设置为出现拥塞时发送方窗口值的一半(但不鞥小于2)。然后把拥塞窗口cwnd重新设置为1,执行慢开始算法。目的是迅速减少主机发送到网络中的分组数,使得发送拥塞的路由器有足够时间把队列中积压的分组处理完毕。


快重传快恢复算法
提出该算法是基于如下考虑:
如果发送方设置的超时计时器时限已到但还没有收到确认,那么很可能是网络出现拥塞,致使报文段在网络中的某处被丢弃。这种情况下,TCP马上把拥塞窗口cwnd减小到1,并执行慢开始算法。
快重传算法要求接收方每收到一个失序的报文段后就立即发出重复确认(为的是使发送方及早知道有报文段没有到达对方)而不要等待自己发送数据时才进行捎带确认。


快恢复算法,与快重传配合使用,其过程有以下要点:
1)当发送方连续收到三个重复确认时,就执行“乘法减小”算法,把慢开始门限ssthresh减半。
2)由于发送方现在认为网络很可能没有发生拥塞(如果网络发生了严重的拥塞,就不会一连有好几个报文段连续到达接收方,也就不会导致接收方连续发送重复确认),因此与慢开始不同之处是现在不执行慢开始算法(即拥塞窗口cwnd不设置为1),而是把cwnd值设置为慢开始门限ssthresh减半后的数值,然后开始执行拥塞避免算法(“加法增大”),使拥塞窗口缓慢地线性增大。


扩展:有的快重传实现是把开始时的拥塞窗口cwnd值再增大一些(增大三个报文段的长度),即等于ssthresh+3*MSS。

理由:既然发送方收到了三个重复的确认,就表明有三个分组已经离开了网络。这三个分组不再消耗网络的资源而是停留在接收方的缓存中(接收方发送出三个重复的确认就证明了这个事实)。现在网络中并不是堆积了分组而是减少了三个分组。因此可以适当把拥塞窗口扩大些。


注意:在这节的开始我们假定了接收方总是有足够大的缓存空间,因而发送窗口的大小由网络的拥塞程度来决定。实际上接收方的缓存空间总是有限的。

接收方根据自己的接收能力设定了接收窗口rwnd,并把这个窗口值写入TCP首部中的窗口字段,传送给发送方。

由拥塞控制和接收方对发送方的流量控制得出:发送方窗口的上限值 = Min [ rwnd , cwnd ]


随机早期检测RED

假定一个路由器对某些分组的处理时间特别长,那么这就可能使这些分组中的数据部分(即TCP报文段)经过很长时间才能到达终点,结果引起发送方对这些报文段的重传。

重传会使TCP连接的发送端认为在网络中发生了拥塞。于是在TCP的发送端就采取了拥塞控制措施,但实际上网络并没有发生拥塞。


网络层的策略对TCP拥塞控制影响最大的就是路由器的分组丢弃策略。

路由器的队列通常都是按照“先进先出”的规则处理分组的。当队列已满时,以后再到达的所有分组将都被丢弃。这叫做尾部丢弃策略

尾部丢弃会导致一连串分组的丢失,这就使发送方出现超时重传,使TCP进入拥塞控制的慢开始状态,结果使TCP连接的发送方突然把数据的发送速率降低到很小的数值。更为严重的是,在网络中通常有很多的TCP连接(它们有不同的源点和终点),这些连接中的报文段通常是复用在网络层的IP数据报中传送。若发生尾部丢失,就可能同时影响很多条TCP连接,结果使这些TCP连接在同一时间突然都进入到慢开始状态。在TCP的术语中称为全局同步(global syncronization)。

为避免发生网络中的全局同步现象,可以在路由器采用随机早期检测RED。

要点如下:

使路由器的队列维持两个参数,即队列长度最小门限THmin和最大门限THmax。当每一个分组到达时,RED就先计算平均队列长度Lav

1)若平均队列长度小于最小门限,则把新到达的分组放入队列进行排队。

2)若平均队列长度超过最大门限,则把新到达的分组丢弃。

3)若平均队列长度在最小门限和最大门限之间,则按照某一概率p将新到达的分组丢弃。


RED不是等到已经发生网络拥塞后才把所有在队列尾部的分组全部丢弃,而是在检测到网络拥塞的早期征兆时(即路由器的平均长度超过一定的门限值时),就先以概率p随机丢弃个别的分组,让拥塞控制只在个别的TCP连接上进行,因而避免发生全局性的拥塞控制。    

最小门限必须足够大,以保证连接路由器的输出链路有较高的利用率。经验证明,使最大门限等于最小门限值的两倍时合适的。

概率p的数值取决于当前的平均队列长度和所设定的两个门限值。三条原则来确定:

1)当平均队列长度小于最小门限时,分组丢弃概率p = 0;

2)当平均队列长度超过最大门限时,分组丢弃概率p = 1;

3)当平均队列长度在两者之间时,概率在0到1之间。例如可以按照线性规律变化。

问题:为什么要使用“平均队列长度”呢?                                                                                                                                                                                                                     

解答:计算机数据具有突发性的特点,因此路由器中的队列长度经常出现很快的变化。如果仅因为瞬时队列长度超过了门限值就将其丢弃就会产生不必要的拥塞控制。

RED采用了和计算平均往返时间RTT类似的加权平均的方法来计算平均队列长度LAV,并根据这个平均队列长度LAV求出分组丢弃概率p。

平均队列长度LAV = ( 1 - a ) * ( 旧的LAV) + a * (当前的队列长度样本)     

公式中的a是在0到1之间的数。若a足够小,则平均队列长度LAV取决于队列长度的长期变化趋势,而不受持续时间短的数据突发的影响。

具体的做法:

       先计算出分组丢弃概率pp =ptemp/( 1- count *ptemp )

       count代表新到达的分组有多少个已经进入了队列(没有被丢弃);ptemp是过渡的分组丢弃概率。

       ptemp =pmax * ( LAV - THmin ) / ( THmax - THmin ) 

具体举例:设pmax = 0.02,而变量count的初始值为0,再假定平均队列长度正好在两个门限之间,计算出过渡的分组丢弃概率ptemp = 0.01。由于在开始时变量count = 0,因此算出p = ptemp = 0.01。也就是说,现在到达的分组进入路由器的队列的概率是0.99。但随着分组的不断进入队列,变量count的值不断增大,由此算出的分组丢弃概率也逐渐增大。   

注意:路由器在某一时刻的瞬时队列长度完全可能远远超过平均队列长度。如果按照上述公式算出的丢弃概率很小,但路由器的队列已经没有空间可接纳新到达的分组,那么这时RED的操作和“尾部丢弃”方式是一样的。RED只是在可能的条件下尽量使“尾部丢弃” 不要发生。                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           

0 0