TCP拥塞控制机制

 研究TCP的拥塞机制,不只仅是想了解TCP如何的精巧,更多的是领悟其设计思想,即在通常状况下,咱们该怎样处理问题。
 
一.拥塞的发生与其不可避免    
拥塞发生的主要缘由:在于网络可以提供的资源不足以知足用户的需求,这些资源包括缓存空间、链路带宽容量和中间节点的处理能力。因为互联网的设计机制致使其缺少“接纳控制”能力,所以在网络资源不足时不能限制用户数量,而只能靠下降服务质量来继续为用户服务,也就是“尽力而为”的服务。
拥塞实际上是一个动态问题,咱们没有办法用一个静态方案去解决,从这个意义上来讲,拥塞是不可避免的
  • 静态解决问题办法1:
  • 例如:增长缓存空间到必定程度时,只会加剧拥塞,而不是减轻拥塞,这是由于当数据包通过长时间排队完成转发时,它们极可能早已超时,从而引发源端超时重发,而这些数据包还会继续传输到下一路由器,从而浪费网络资源,加剧网络拥塞。事实上,缓存空间不足致使的丢包更多的是拥塞的“症状”而非缘由。另外,增长链路带宽及提升处理能力也不能解决拥塞问题。
  • 静态解决问题办法2:
  •  例如:咱们有四台主机ABCD链接路由器R,全部链路带宽都是1Gbps,若是A和B同时向C以1Gbps的速率发送数据,则路由器R的输入速率为2Gbps,而输出速率只能为1Gbps,从而产生拥塞。避免拥塞的方法只能是控制AB的速率,例如,都是0.5Gbps,可是,这只是一种状况,假若D也向R发送数据,且速率为1Gbps,那么,咱们先前的修正又是不成立的,
 
二.流量控制    
  • 早期的TCP协议只有基于窗口的流控制(flow control)机制,咱们简单介绍一下,并分析其不足。   在TCP中,为了实现可靠性,发送方发出一个数据段以后要等待接受方相应的确认信息,而不是直接发送下一个分组。
  • 具体的技术是采用滑动窗口,以便通讯双方可以充分利用带宽。滑动窗口容许发送方在收到接收方的确认以前发送多个数据段。窗口大小决定了在收到目的地确认以前,一次能够传送的数据段的最大数目。窗口大小越大,主机一次能够传输的数据段就越多。当主机传输窗口大小数目的数据段后,就必须等收到确认,才能够再传下面的数据段。例如,若视窗的大小为 1,则传完数据段后,都必须通过确认,才能够再传下一个数据段;当窗口大小等于3时,发送方能够一次传输3个数据段,等待对方确认后,再传输下面三个数据段。  
  •  窗口的大小在通讯双方链接期间是可变的,通讯双方能够经过协商动态地修改窗口大小。在TCP的每一个确认中,除了指出但愿收到的下一个数据段的序列号以外,还包括一个窗口通告,通告中指出了接收方还能再收多少数据段(咱们能够把通告当作接收缓冲区大小)。若是通告值增大,窗口大小也相应增大;通告值减少,窗口大小也相应减少。可是咱们能够发现,接收端并无特别合适的方法来判断当前网络是否拥塞,由于它只是被动得接收,不像发送端,当发出一个数据段后,会等待对方得确认信息,若是超时,就能够认为网络已经拥塞了。
  • 因此,改变窗口大小的惟一根据,就是接收端缓冲区的大小了。   
  • 流量控制做为接受方管理发送方发送数据的方式,用来防止接受方可用的数据缓存空间的溢出。
  • 流控制是一种局部控制机制,其参与者仅仅是发送方和接收方,它只考虑了接收端的接收能力,而没有考虑到网络的传输能力;
  • 而拥塞控制则注重于总体,其考虑的是整个网络的传输能力,是一种全局控制机制。正由于流控制的这种局限性,从而致使了拥塞崩溃现象的发生。  
三.重传
一、一旦收到确认,发送方关闭重发定时器而且将数据片的备份从重发队列中删除。发送方若是在规定的时间内没有收到数据确认,就重传该数据。
二、当TCP超时并重传时,它不必定要重传一样的报文段,相反,TCP容许进行从新分组而发送一个较大的报文段,这将有助于提升性能(固然,这个较大的报文段不可以超过接收方声明的MSS)。在协议中这是容许的,由于TCP是使用字节序号而不是报文段序号来进行识别它所要发送的数据和进行确认。
三、重发定时器
(1)  每一次一个包含数据的包被发送(包括重发),若是该定时器没有运行则启动它,使得它在RTO秒以后超时(按照当前的RTO值)。
(2)  当全部的发出数据都被确认以后,关闭该重发定时器。
(3)  当接收到一个ACK确认一个新的数据,从新启动该重发定时器,使得它在RTO秒以后超时(按照当前的RTO值)
 
四.TCP拥塞控制机制    
TCP的拥塞控制由4个核心算法组成:“慢启动”(Slow Start)、“拥塞避免”(Congestion voidance)、“快速重传 ”(Fast Retransmit)、“快速恢复”(Fast Recovery)。
具体的流程图能够参见:http://www.eventhelix.com/RealtimeMantra/Networking/,这里我会把本身的理解尽量详细的列出来。为了方便起见,把发送端叫作client,接收端为server,每一个segment长度为512字节,阻塞窗口长度为cwnd(简化起见,下面以segment为单位),sequence number为seq_num,acknowledges number为ack_num。一般状况下,TCP每接收到两个segment,发送一个ack。
 
-- 慢启动    
早期开发的TCP应用在启动一个链接时会向网络中发送大量的数据包,这样很容易致使路由器缓存空间耗尽,网络发生拥塞,使得TCP链接的吞吐量急剧降低。因为TCP源端一开始并不知道网络资源当前的利用情况,所以新创建的TCP链接不能一开始就发送大量数据,而只能逐步增长每次发送的数据量,以免上述现象的发生,这里有一个“学习”的过程。   假设client要发送5120字节到server,
慢启动过程以下:   
1.初始状态,cwnd=1,seq_num=1;client发送第一个segment;   
2.server接收到512字节(一个segment),回应ack_num=513;   
3.client接收ack(513),cwnd=1+1=2;如今能够一次发送2个数据段而没必要等待ack   
4.server接收到2个segment,回应ack_num=513+512*2=1537   
5.client接收ack(1537),cwnd=2+1;一次发送3个数据段   
6.server接收到3个segment,回应2个ack,分别为ack_num=1537+1024=2561和ack_num=2561+512=3073   
7.client接收ack(2561)和ack(3073),cwnd=3+2=5;一次能够发送5个数据段,可是只用4个就知足要求了   
8.server接收到4个segment,回应2个ack,分别为4097,5121   9.已经发送5120字节,任务完成!
总结一下: 当创建新的TCP链接时,拥塞窗口(congestion window,cwnd)初始化为一个数据包大小。源端按cwnd大小发送数据,每收到一个ACK确认,cwnd就增长一个数据包发送量。  
-- 拥塞避免    能够想象,若是按上述慢启动的逻辑继续下去而不加任何控制的话,必然会发生拥塞,引入一个慢启动阈值ssthresh的概念,当cwnd<ssthresh的时候,tcp处于慢启动状态,不然,进入拥塞避免阶段。一般,ssthresh初始化为 64 Kbytes。   当cwnd = 64947 + 512 = 65459,进入拥塞避免阶段,假设此时seq_num = _101024:   
1.client一次发送cwnd,可是先考虑头两个segment  
 2.server回应ack_num = 102048   
3.client接收到ack(102048),cwnd = 65459 + [(512 * 512) /65459] = 65459 + 4 = 65463,也就是说,每接到一个ack,cwnd只增长4个字节。   
4.client发送一个segment,并开启ack timer,等待server对这个segment的ack,若是超时,则认为网络已经处于拥塞状态,则重设慢启动阀值ssthresh=当前cwnd/2=65463/2=32731,而且,马上把cwnd设为1,很极端的处理!
 5.此时,cwnd<ssthresh,因此,恢复到慢启动状态。
总结一下: 若是当前cwnd达到慢启动阀值,则试探性的发送一个segment,若是server超时未响应,TCP认为网络能力降低,必须下降慢启动阀值,同时,为了不形势恶化,干脆采起极端措施,把发送窗口降为1,我的感受应该有更好的方法。
 
-- 快速重传和快速恢复    
前面讲过标准的重传,client会等待RTO时间再重传,但有时候,没必要等这么久也能够判断须要重传,
快速重传
例如:client一次发送8个segment,seq_num起始值为100000,可是因为网络缘由,100512丢失,其余的正常,则server会响应4个ack(100512)(为何呢,tcp会把接收到的其余segment缓存起来,ack_num必须是连续的),这时候,client接收到四个重复的ack,它彻底有理由判断100512丢失,进而重传,而没必要傻等RTO时间了。  
快速恢复
咱们一般认为client接收到3个重复的ack后,就会开始快速重传,可是,若是还有更多的重复ack呢,如何处理?这就是快速恢复要作的,事实上,咱们能够把快速恢复看做是快速重传的后续处理,它不是一种单独存在的形态。   
如下是具体的流程:  
假设此时cwnd=70000,client发送4096字节到server,也就是8个segment,起始seq_num = _100000:   
1.client发送seq_num = _100000   
2.seq_num =100512的segment丢失   
3.client发送seq_num = _101024   
4.server接收到两个segment,它意识到100512丢失,先把收到的这两个segment缓存起来   
5.server回应一个ack(100512),表示它还期待这个segment  
 6.client发送seq_num = _101536   
7.server接收到一个segment,它判断不是100512,依旧把收到的这个segment缓存起来,并回应ack(100512)   。   。   。  
 8.如下同六、7,直到client收到3个ack(100512),进入快速重发阶段:   
9.重设慢启动阀值ssthresh=当前cwnd/2=70000/2=35000   
10.client发送seq_num = 100512      如下,进入快速恢复阶段:   
11.重设cwnd = ssthresh + 3 segments =35000 + 3*512 = 36536,之因此要加3,是由于咱们已经接收到3个ack(100512)了,根据前面说的,每接收到一个ack,cwnd加1   
12.client接收到第四个、第五个ack(100512),cwnd=36536+2*512=37560  
 13.server接收到100512,响应ack_num = _104096   14.此时,cwnd>ssthresh,进入拥塞避免阶段。
 
【思考】:为何一般clieng每接收到一个ack,会把cwnd增长一个segment呢?  这是基于“管道”模型(pipe model)的“数据包守恒”的原则(conservation of packets principle),即同一时刻在网络中传输的数据包数量是恒定的,只有当“旧”数据包离开网络后,才能发送“新”数据包进入网络。若是发送方收到一个ACK,则认为已经有一个数据包离开了网络,因而将拥塞窗口加1。若是“数据包守恒”原则可以获得严格遵照,那么网络中将不多会发生拥塞;本质上,拥塞控制的目的就是找到违反该原则的地方并进行修正。
 
五.联想    想一想看,能不能把TCP解决拥塞的方法应用到交通拥塞呢?   咱们有两个原则:一是拥塞不可避免,单纯增长资源并不能避免拥塞的发生,只能用动态的方法加以解决;二是数据包守恒原则。政府花费很大资金修路,并不能避免堵车,只能从源头控制,例如首先限制车辆进入主路,根据实际状况,再慢慢增长每个路口的车流量,可是,当达到一个阀值,增长速度要放缓,并不时探测整个主路的拥堵状况,若是状况危急,马上封闭半个路口,并将车流量降到最低,也就是从新回复到慢启动状态。   呵呵,有趣!