TCP的的流量控制
1.利用滑动窗口实现流量控制
一般来说我们希望发送方的发送速率越高,这样数据的传输率可能就变得高一点,但是如果发送方数据传输的过快,接受方可能来不及接收,这就会造成数据的丢失。,所谓流量控制就是让发送方的发送速率不要太快,要让接收方来得及接受。
利用TCP的滑动窗口可以很方便的对流量进行控制
现在假设A向B发送消息,在连接建立时B告诉A我的接收窗口是400,因此发送方的发送窗口不能超过接收方的接受窗口的数值,注意TCP的滑动窗口是字节为单位并不是报文段,现在假设每个报文段大小为100字节。
并且数据段报文序号初始值为1。
我们因该注意到上图接受主机进行了3次流量控制,第一次把窗口减小到rwnd=300,第二次又减小到rwnd=100,第三次减小到0,即不予许再发送了。这种发送暂停的等待状态会持续到主机A收到B新的确认报文,三个确认报文都设置了ACK=1,切记当ACK=1时确认才有意义。
现在我们考虑另一种情况,现假设B给A发送0窗口报文后,现在又有了新的缓存空间。所以给A发送窗口值=400的报文段,可此报文段由于网络问题丢失了,此时B在等待A发数据,A在等待B发窗口值改变的报文。如果没有其他措施这种死锁局面就会一直延续下去。
为了解决这种问题TCP为每个连接都设置了一个持续计时器,只要对方设置了0窗口,就启动持续计时器开始计时,若果超时就会发送一个零窗口探测报文(只携带一个字节),对方收到探测报文就会给出现在的窗口值,若窗口依然是0,就继续等待,若不是则打破僵局。
考虑传输效率
前面已经讲过,应用进程把数据传送到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)RFC813],有时也会使TCP的性能变坏。设想一种情况:TCP接收方的缓存已满,而交互式的应用进程一次只从接收缓存中读取1个字节(这样就使接收缓存空间仅腾出1个字节),然后向发送方发送确认,并把窗口设置为1个字节(但发送的数据报是40字节长)。接着,发送方又发来1个字节的数据(请注意,发送方发送的IP数据报是41字节长)。接收方发回确认,仍然将窗口设置为1个字节。这样进行下去,使网络的效率很低。
要解决这个问题,可以让接收方等待一段时间,使得或者接收缓存已有足够空间容纳一个最长的报文段,或者等到接收缓存已有一半空闲的空间。只要出现这两种情况之一,接收方就发出确认报文,并向发送方通知当前的窗口大小。此外,发送方也不要发送太小的报文段,而是把数据积累成足够大的报文段,或达到接收方缓存的空间的一半大小。
上述两种方法可配合使用。使得在发送方不发送很小的报文段的同时,接收方也不要在缓存刚刚有了一点小的空间就急忙把这个很小的窗口大小信息通知给发送方。
TCP的拥塞控制
计算机里的链路容量,交换节点的缓存,处理机等这些都是有限的网络资源,若某段时间对资源的需求大于本身资源的负载,就会使网络性能变坏,这时就出现了拥塞。我们可以发现以下条件拥塞=对资源的需求>可用资源
若网络中有许多资源同时呈现供应不足,网络的性能就要明显变坏,整个网络的吞吐量将随输入负荷的增大而下降。有人可能会说:“只要任意增加一些资源,例如,把结点缓存的存储空间扩大,或把链路更换为更高速率的链路,或把结点处理机的运算速度提高,就可以解决网络拥塞的问题。”其实不然。这是因为网络拥塞是一个非常复杂的问题。简单地采用上述做法,在许多情况下,不但不能解决拥塞问题,而且还可能使网络的性能更坏。
网络拥塞往往是由许多因素引起的。例如,当某个结点缓存的容量太小时,到达该结点的分组因无存储空间暂存而不得不被丢弃。现在设想将该结点缓存的容量扩展到非常大。于是凡到达该结点的分组均可在结点的缓存队列中排队,不受任何限制。由于输出链路的容量和处理机的速度并未提高,因此在这队列中的绝大多数分组的排队等待时间将会大大增加,结果上层软件只好把它们进行重传(因为早就超时了)。由此可见,简单地扩大缓存的存储空间同样会造成网络资源的严重浪费,因而解决不了网络拥塞的问题。
又如,处理机处理的速率太慢可能引起网络的拥塞。简单地将处理机的速率提高,可能会使上述情况缓解一些,但往往又会将瓶颈转移到其他地方。问题的实质往往是整个系统的各个部分不匹配。只有所有的部分都平衡了,问题才会得到解决。拥塞常常趋于恶化。如果一个路由器没有足够的缓存空间,它就会丢弃一些新到的分组。但当分组被丢弃时,发送这一分组的源点就会重传这一分组,甚至可能还要重传多次这样会引起更多的分组流入网络和被网络中的路由器丢弃。可见拥塞引起的重传并不会缓解网络的拥塞,反而会加剧网络的拥塞。
拥塞控制与流量控制的关系密切,它们之间也存在着一些差别。所谓拥塞控制就是防止过多的数据注入到网络中,这样可以使网络中的路由器或链路不致过载。拥塞控制所要做的都有一个前提,就是网络能够承受现有的网络负荷。拥塞控制是一个全局性的过程,涉及到所有的主机、所有的路由器,以及与降低网络传输性能有关的所有因素。但TCP连接的端点只要迟迟不能收到对方的确认信息,就猜想在当前网络中的某处很可能发生了拥塞,但这时却无法知道拥塞到底发生在网络的何处,也无法知道发生拥塞的具体原因(是访问某个服务器的通信量过大?还是在某个地区出现了自然灾害)。
相反,流量控制往往指点对点通信量的控制,是个端到端的问题(接收端控制发送端)。流量控制所要做的就是抑制发送端发送数据的速率,以便使接收端来得及接收。可以用个简单例子说明这种区别。设某个光纤网络的链路传输速率为1000Gb/s。有一个巨型计算机向一个PC机以1Gb/s的速率传送文件。显然,网络本身的带宽是足够大的,因而不存在产生拥塞的问题。但流量控制却是必需的,因为巨型计算机必须经常停下来,以便使PC机来得及接收。
但如果有另一个网络,其链路传输速率为1Mb/s,而有1000台大型计算机连接在这个网络上。假定其中的500台计算机分别向其余的500台计算机以100kb/s的速率发送文件。那么现在的问题已不是接收端的大型计算机是否来得及接收,而是整个网络的输入负载是否超过网络所能承受的。拥塞控制和流量控制之所以常常被弄混,是因为某些拥塞控制算法是向发送端发送控制报文,并告诉发送端,网络已出现麻烦,必须放慢发送速率。这点又和流量控制是很相似的。
进行拥塞控制需要付出代价。这首先需要获得网络内部流量分布的信息。在实施拥塞控制时,还需要在结点之间交换信息和各种命令,以便选择控制的策略和实施控制。这样就产生了额外开销。拥塞控制有时需要将一些资源(如缓存、带宽等)分配给个别用户(或些类别的用户)单独使用,这样就使得网络资源不能更好地实现共享。十分明显,在设计拥塞控制策略时,必须全面衡量得失。
在图中的横坐标是提供的负载( offered load),代表单位时间内输入给网络的分组数目。因此提供的负载也称为输入负载或网络负载。纵坐标是吞吐量( throughput),代表单位时间从网络输出的分组数目。具有理想拥塞控制的网络,在吞吐量饱和之前,网络吞吐量应等于提供的负载,故吞吐量曲线是45°的斜线。但当提供的负载超过某一限度时,由于网络资源受限,吞吐量不再增长而保持为水平线,即吞吐量达到饱和。这就表明提供的负载中有一部分损失掉了(例如,输入到网络的某些分组被某个结点丢弃了)。虽然如此,在这种理想的拥塞控制作用下,网络的吞吐量仍然维持在其所能达到的最大值
但在实际的情况下无拥塞控制计划,随着网络吞吐量增长速率减小,网络会轻度拥塞,继续负载则会死锁。
拥塞控制方法
慢开始和拥塞避免
慢开始并不是指窗口值增长速率慢,而是指TCP最开始将窗口值设置为1,然后逐渐增长,这样的方式使得并不是一开始就将过量数据注入网络,先试探一下你,网络是否拥堵,然后在进行逐步增大。
为了防止拥塞窗口cwnd增长过大引起网络拥塞,还需要设置一个慢开始门限 ssthresh状态变量(如何设置 ssthresh,后面还要讲)。慢开始门限 ssthresh的用法如下:
当cwnd< ssthresh时,使用上述的慢开始算法。
当cwnd> ssthresh时,停止使用慢开始算法而改用拥塞避免算法。
当cwnd= ssthresh时,既可使用慢开始算法,也可使用拥塞避免算法。
拥塞避免算法的思路是让拥塞窗口cwnd缓慢地增大,即每经过一个往返时间RTT就把发送方的拥塞窗口cwnd加1,而不是加倍。这样,拥塞窗口cwnd按线性规律缓慢增长,比慢开始算法的拥塞窗口增长速率缓慢得多。无论在慢开始阶段还是在拥塞避免阶段,只要发送方判断网络出现拥塞(其根据就是没有按时收到确认),就要把慢开始门限 ssthresh设置为出现拥塞时的发送方窗口值的一半(但不能小于2)。然后把拥塞窗口cwnd重新设置为1,执行慢开始算法。这样做的目的就是要迅速减少主机发送到网络中的分组数,使得发生拥塞的路由器有足够时间把队列中积
压的分组处理完毕。
下图用具体数值说明了上述拥塞控制的过程。现在发送窗口的大小和拥塞窗口一样大。
在执行慢开始算法时,拥塞窗口cwnd的初始值为1。以后发送方每收到一个对新报文段的确认ACK,就把拥塞窗口值加1,然后开始下一轮的传输(请注意,图的横坐标是传输轮次)。因此拥塞窗口cwd随着传输轮次按指数规律增长。当拥塞窗口cwd增长到慢开始门限值 ssthresh时(即当cwnd=16时),就改为执行拥塞避免算法,拥塞窗口按线性规律增长。
快重传和快恢复
上面讲的慢开始和拥塞避免算法是1988年提出的TCP拥塞控制算法。1990年又增加了两个新的拥塞控制算法。这就是快重传和快恢复
提出这两个算法是基于如下的考虑:
如果发送方设置的超时计时器时限已到但还没有收到确认,那么很可能是网络出现了拥塞,致使报文段在网络中的某处被丢弃。在这种情况下,TCP马上把拥塞窗口cwnd减小到1,并执行慢开始算法,同时把慢开始门限值 ssthresh减半,如前图所示。这是不使用快重传的情况。
那快重传是怎么样的呢,快重传中如某次发送方发送数据丢失,接收方由于后续确认只能确认收到的最低序号确认则会对没收到的序号-1,进行确认并希望收到缺失的序号,如果,发送方连续三次收到了B对某数据的缺失确认则立即发送此数据报文,而不用等待此数据的重传计时器。
因此使用传重传能使网络吞吐量提高20%。
与快重传配合使用的还有快恢复算法,其过程有以下两个要点:
(1)当发送方连续收到三个重复确认时,就执行“乘法减小”算法,把慢开始门限ssthresh减半。这是为了预防网络发生拥塞。请注意,接下去不执行慢开始算法。
(2)由于发送方现在认为网络很可能没有发生拥塞(如果网络发生了严重的拥塞,就不会一连有好几个报文段连续到达接收方,也就不会导致接收方连续发送重复确认),因此与慢开始不同之处是现在不执行慢开始算法(即拥塞窗口cwd现在不设置为1),而是把cwnd值设置为慢开始门限ssthresh减半后的数值,然后开始执行拥塞避免算法(“加法增大”),使拥塞窗口缓慢地线性增大,下图给出了快重传和快恢复的示意图,并标明了“ TCPReno版本”,这是目前使用得很广泛的版本。图中还画出了已经废弃不用的虚线部分( TCP Tahoe版本)。请注意它们的区别就是:新的 TCP Reno版本在快重传之后采用快恢复算法而不是采用慢开始算法
请注意,也有的快重传实现是把开始时的拥塞窗口cwnd值再增大一些(增大3个报文段的长度),即等于 ssthresh+3×MSS。这样做的理由是:既然发送方收到三个重复的确认,就表明有三个分组已经离开了网络。这三个分组不再消耗网络的资源而是停留在接收方
的缓存中(接收方发送出三个重复的确认就证明了这个事实)。可见现在网络中并不是堆积了三个分组,而是减少了三个分组,此时可适当加大拥塞窗口。
在采用快恢复算法时,慢开始算法只是在TCP连接建立时和网络岀现超时时才使用釆用这样的拥塞控制方法使得TCP的性能有明显的改进
在里开始我们就假定了接收方总是有足够大的缓存空间,因而发送窗口的大小由网络的拥塞程度来决定。但实际上接收方的缓存空间总是有限的。接收方根据自己的接收能力设定了接收窗口rwnd,并把这个窗口值写入TCP首部中的窗口字段,传送给发送方。因此,接收窗口又称为通知窗口( advertised window)。因此,从接收方对发送方的流量控制的角度考虑,发送方的发送窗口一定不能超过对方给出的接收窗口值rwnd。