TCP详解(3):重传、流量控制、拥塞控制……

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/kzq_qmi/article/details/46940463

数据传输

  在TCP的数据传送状态,很多重要的机制保证了TCP的可靠性和强壮性。它们包括:使用序号,对收到的TCP报文段进行排序以及检测重复的数据;使用校验和来检测报文段的错误;使用确认和计时器来检测和纠正丢包或延时。
  在TCP的连接创建状态,两个主机的TCP层间要交换初始序号(ISN:initial sequence number)。这些序号用于标识字节流中的数据,并且还是对应用层的数据字节进行记数的整数。通常在每个TCP报文段中都有一对序号和确认号。TCP报文发送者认为自己的字节编号为序号,而认为接收者的字节编号为确认号。TCP报文的接收者为了确保可靠性,在接收到一定数量的连续字节流后才发送确认。这是对TCP的一种扩展,通常称为选择确认(Selective Acknowledgement)。选择确认使得TCP接收者可以对乱序到达的数据块进行确认。每一个字节传输过后,ISN号都会递增1。
  通过使用序号和确认号,TCP层可以把收到的报文段中的字节按正确的顺序交付给应用层。序号是32位的无符号数,在它增大到2^32-1时,便会回绕到0。对于ISN的选择是TCP中关键的一个操作,它可以确保强壮性和安全性。
来看个例子:
  1)发送方首先发送第一个包含序列号为1(可变化)和1460字节数据的TCP报文段给接收方。接收方以一个没有数据的TCP报文段来回复(只含报头),用确认号1461来表示已完全收到并请求下一个报文段。
  2)发送方然后发送第二个包含序列号为1461和1460字节数据的TCP报文段给接收方。正常情况下,接收方以一个没有数据的TCP报文段来回复,用确认号2921(1461+1460)来表示已完全收到并请求下一个报文段。发送接收这样继续下去。
  3)然而当这些数据包都是相连的情况下,接收方没有必要每一次都回应。比如,他收到第1到5条TCP报文段,只需回应第五条就行了。在例子中第3条TCP报文段被丢失了,所以尽管他收到了第4和5条,然而他只能回应第2条。
  4)发送方在发送了第三条以后,没能收到回应,因此当时钟(timer)过时(expire)时,他重发第三条。(每次发送者发送一条TCP报文段后,都会再次启动一次时钟:RTT)。
  5)这次第三条被成功接收,接收方可以直接确认第5条,因为4,5两条已收到。
  
  

  Acknowledgment Number Out = Sequence Number In + Bytes of Data Received

1.TCP重传
  报文重传是TCP最基本的错误恢复功能,它的目的是防止报文丢失。
  报文丢失的可能因素有很多种,包括应用故障,路由设备过载,或暂时的服务宕机。报文级别速度是很高的,而通常报文丢失是暂时的,因此TCP能够发现和恢复报文丢失显得尤为重要。

  重传机制在实现数据可靠传输功能的同时,也引起了相应的性能问题:何时进行数据重传?如何保证较高的传输效率?
  重传时间过短:在网络因为拥塞引起丢包时,频繁的重传会进一步加剧网络拥塞,引起丢包,恶化网络传输性能。
  重传时间过长:接收方长时间无法完成数据接收,引起长时间占用连接线路造成资源损耗、传输效率较低等问题。
  针对上述问题,TCP中设计了超时重传机制。该机制规定当发送方A向B发送数据包P1时,开启时长为RTO(Retransmission Timeout)的重传定时器,如果A在RTO内未收到B对P1的确认报文,则认为P1在网络中丢失,此时重新发送P1。由此,引出RTO大小的设定问题。

  决定报文是否有必要重传的主要机制是重传计时器(retransmission timer),它的主要功能是维护重传超时(RTO)值。当报文使用TCP传输时,重传计时器启动,收到ACK时计时器停止。报文发送至接收到ACK的时间称为往返时间(RTT)。对若干次时间取平均值,该值用于确定最终RTO值。在最终RTO值确定之前,确定每一次报文传输是否有丢包发生使用重传计时器,下图说明了TCP重传过程。
  

  当报文发送之后,但接收方尚未发送TCP ACK报文,发送方假设源报文丢失并将其重传。重传之后,RTO值加倍;如果在2倍RTO值到达之前还是没有收到ACK报文,就再次重传。如果仍然没有收到ACK,那么RTO值再次加倍。如此持续下去,每次重传RTO都翻倍,直到收到ACK报文或发送方达到配置的最大重传次数。
  最大重传次数取决于发送操作系统的配置值。默认情况下,Windows主机默认重传5次。大多数Linux系统默认最大15次。两种操作系统都可配置。

1)超时重传
  超时重传机制用来保证TCP传输的可靠性。每次发送数据包时,发送的数据报都有seq号,接收端收到数据后,会回复ack进行确认,表示某一seq号数据已经收到。发送方在发送了某个seq包后,等待一段时间,如果没有收到对应的ack回复,就会认为报文丢失,会重传这个数据包。
2)快速重传
  接受数据一方发现有数据包丢掉了(并不是所期望的值。这意味着报文在传送中丢失。接收端注意到报文乱序,并且在第三个报文中发送重复ACK)。就会发送重复ACK报文告诉发送端重传丢失的报文。
  当重传主机从发送端接收到3个重复ACK时,它会假设此报文确实在传送中丢失,并且立即发送一个快速重传。一旦触发了快速重传,所有正在传输的其他报文都被放入队列中,直到快速重传报文发送为止。过程如下图所示:

  

  比较超时重传和快速重传,可以发现超时重传是发送端在傻等超时,然后触发重传;而快速重传则是接收端主动告诉发送端数据没收到,然后触发发送端重传。
  由此可看出,快速重传机制在一定程度上弥补了超时重传机制,使得重传更加及时。

2.流量控制
  这里主要说TCP滑动窗口流量控制。滑动窗口(Sliding window )是一种流量控制技术。早期的网络通信中,通信双方不会考虑网络的拥挤情况直接发送数据。由于大家不知道网络拥塞状况,一起发送数据,导致中间结点阻塞掉包,谁也发不了数据。所以就有了滑动窗口机制来解决此问题。
  为了理解TCP的窗口大小是怎么样变化的,我们先需要理解它的含义。最简单的方式就是认为窗口大小”意味着接收方能接收数据的大小”,这也是说接收端设备再应用程序读取buffer中数据之前,能从对端连接处理多少数据。比如说server端窗口大小是360,那么就意味着server端一次只能从客户端接收不超过360bytes的数据。当server端收到数据,它会将数据放到buffer里,然后server端必须对这份数据做两件事:
  1)server端必须发送一个 ACK 到client端来确认数据已经收到
  2)server端必须处理这份数据,把它交给对应的应用程序
  要区分上面两件事对理解窗口很重要,接收方收到数据后会确认,但是数据并不一定是里面就是从buffer里取出的,这是受应用层逻辑控制的。所以很有可能如果接收数据过快,而取出数据更慢,就会导致buffer满。一旦这种情况发生,窗口大小就开始调整来防止接收方负载过高。

  TCP头里有一个字段叫Window,又叫Advertised-Window,这个字段是接收端告诉发送端自己还有多少缓冲区可以接收数据。于是发送端就可以根据这个接收端的处理能力来发送数据,而不会导致接收端处理不过来。   Window是一个16bit位字段,它代表的是窗口的字节容量,也就是TCP的标准窗口最大为2^16-1=65535个字节。
  另外在TCP的选项字段中还包含了一个TCP窗口扩大因子,option-kind为3,option-length为3个字节,option-data取值范围0-14。窗口扩大因子用来扩大TCP窗口,可把原来16bit的窗口,扩大为31bit。
  
  

  1)对于TCP会话的发送方,任何时候在其发送缓存内的数据都可以分为4类,“已经发送并得到对端ACK的”,“已经发送但还未收到对端ACK的”,“未发送但对端允许发送的”,“未发送且对端不允许发送”。“已经发送但还未收到对端ACK的”和“未发送但对端允许发送的”这两部分数据称之为发送窗口。

  

  当收到接收方新的ACK对于发送窗口中后续字节的确认是,窗口滑动,滑动原理如下图:

  

  一个例子:

  

滑动窗口协议
1)比特滑动窗口协议
  当发送窗口和接收窗口的大小固定为1时,滑动窗口协议退化为停等协议(stop-and-wait)。该协议规定发送方每发送一帧后就要停下来,等待接收方已正确接收的确认(acknowledgement)返回后才能继续发送下一帧。由于接收方需要判断接收到的帧是新发的帧还是重新发送的帧,因此发送方要为每一个帧加一个序号。由于停等协议规定只有一帧完全发送成功后才能发送新的帧,因而只用一比特来编号就够了。

2)后退n协议
  由于停等协议要为每一个帧进行确认后才继续发送下一帧,大大降低了信道利用率,因此又提出了后退n协议。后退n协议中,发送方在发完一个数据帧后,不停下来等待应答帧,而是连续发送若干个数据帧,即使在连续发送过程中收到了接收方发来的应答帧,也可以继续发送。且发送方在每发送完一个数据帧时都要设置超时定时器。只要在所设置的超时时间内仍未收到确认帧,就要重发相应的数据帧。如:当发送方发送了N个帧后,若发现该N帧的前一个帧在计时器超时后仍未返回其确认信息,则该帧被判为出错或丢失,此时发送方就不得不重新发送出错帧及其后的N帧。

来看下面的例子,这里假设n=9:

  

  首先发送方一口气发送10个数据帧,前面两个帧正确返回了,数据帧2出现了错误,这时发送方被迫重新发送2-8这7个帧,接受方也必须丢弃之前接受的3-8这几个帧。

  从这里不难看出,后退n协议一方面因连续发送数据帧而提高了效率,但另一方面,在重传时又必须把原来已正确传送过的数据帧进行重传(仅因这些数据帧之前有一个数据帧出了错),这种做法又使传送效率降低。由此可见,若传输信道的传输质量很差因而误码率较大时,连续测协议不一定优于停止等待协议。此协议中的发送窗口的大小为k,接收窗口仍是1。

3)选择重传协议
  在后退n协议中,接收方若发现错误帧就不再接收后续的帧,即使是正确到达的帧,这显然是一种浪费。另一种效率更高的策略是当接收方发现某帧出错后,其后继续送来的正确的帧虽然不能立即递交给接收方的高层,但接收方仍可收下来,存放在一个缓冲区中,同时要求发送方重新传送出错的那一帧。
  但是必须强调一点,接收方永远不会把分组失序地交给应用层.在他们被交付给应用层之前,先要等待那些更早发出来的分组到达。一旦收到重新传来的帧后,就可以原已存于缓冲区中的其余帧一并按正确的顺序递交高层。这种方法称为选择重发(SELECTICE REPEAT),其工作过程如图所示。显然,选择重发减少了浪费,但要求接收方有足够大的缓冲区空间。
  
  

4)零窗口问题
  某些情况下,服务器无法再处理从客户端发送的数据。可能是由于内存不足,处理能力不够,或其他原因。这可能会造成数据被丢弃以及传输暂停,但接收窗口能够帮助减小负面影响。
  当上述情况发生时,服务器会发送窗口为0的报文。当客户端接收到此报文时,它会暂停所有数据传输,但会保持与服务器的连接以传输探测(keep-alive Zero Window Probe)报文。探测报文在客户端以稳定间隙发送,以查看服务器接收窗口状态。一旦服务器能够再次处理数据,将会返回非零值窗口大小,传输会恢复。下图示例了零窗口通知过程。

  

5)Silly Window Syndrome(糊涂窗口综合症)
  Silly window syndrome定义为一次仅发送少量的TCP负载数据,就像用一个飞机只运送你一个人(你又不是总统,哼),这种情况下带宽利用率很低,一般尽量避免。

  

  对接收端来说,window size小于某个值,可以直接ack(0)回sender,这样就把window给关闭了,也阻止了sender再发数据过来。当接收端size重新达到MSS或者接收端缓存区的一半.
  对于发送端来说,呵呵,不是有Nagle’s algorithm嘛。这个算法的思路也是延时处理,两个主要的条件1)要等到 Window Size>=MSS 或是 Data Size >=MSS,2)等待时间或是超时200ms,满足这两个条件之一再发送,否则就是就接着攒数据。

3.拥塞控制
  滑动窗用来做流量控制。流量控制只关注发送端和接受端自身的状况,而没有考虑整个网络的通信情况。拥塞控制,则是基于整个网络来考虑的。考虑一下这样的场景:某一时刻网络上的延时突然增加,那么,TCP对这个事做出的应对只有重传数据,但是,重传会导致网络的负担更重,于是会导致更大的延迟以及更多的丢包,于是,这个情况就会进入恶性循环被不断地放大。试想一下,如果一个网络内有成千上万的TCP连接都这么行事,那么马上就会形成“网络风暴”,TCP这个协议就会拖垮整个网络。为此,TCP引入了拥塞控制策略。拥塞策略算法主要包括:慢启动,拥塞避免,拥塞发生,快速恢复。

  对于拥塞现象,我们可以进一步用图1来描述。当网络负载较小时,吞吐量基本上随着负载的增长而增长,呈线性关系,响应时间增长缓慢。当负载达到网络容量时,吞吐量呈现出缓慢增长,而响应时间急剧增加,这一点称为Knee。假如负载继续增加,路由器开始丢包,当负载超过一定量时,吞吐量开始急剧下降,这一点称为Cliff。
  拥塞控制机制实际上包含拥塞避免(congestion avoidance)和拥塞控制(congestion control)两种策略。前者的目的是使网络运行在Knee四周,避免拥塞的发生;而后者则是使得网络运行在Cliff的左侧区域。前者是一种“预防”措施,维持网络的高吞吐量、低延迟状态,避免进入拥塞;后者是一种“恢复”措施,使网络从拥塞中恢复过来,进入正常的运行状态。

         

  拥塞发生的主要原因在于网络能够提供的资源不足以满足用户的需求,这些资源包括缓存空间、链路带宽容量和中间节点的处理能力。由于互联网的设计机制导致其缺乏“接纳控制”能力,因此在网络资源不足时不能限制用户数量,而只能靠降低服务质量来继续为用户服务,也就是“尽力而为”的服务。
  拥塞虽然是由于网络资源的稀缺引起的,但单纯增加资源并不能避免拥塞的发生。例如增加缓存空间到一定程度时,只会加重拥塞,而不是减轻拥塞,这是因为当数据包经过长时间排队完成转发时,它们很可能早已超时,从而引起源端超时重发,而这些数据包还会继续传输到下一路由器,从而浪费网络资源,加重网络拥塞。同样如果单纯增加链路带宽,如果中间路由来不及好处理,就会造成大量丢包,引发拥塞。
  单纯地增加网络资源之所以不能解决拥塞问题,是因为拥塞本身是一个动态问题,它不可能只靠静态的方案来解决,而需要协议能够在网络出现拥塞时保护网络的正常运行。目前对互联网进行的拥塞控制主要是依靠在源端执行的基于窗口的TCP拥塞控制机制。网络本身对拥塞控制所起的作用较小。

  拥塞控制假设分组的丢失都是由网络繁忙造成的。拥塞控制有三种动作,分别对应主机感受到的情况:
  收到一条新确认。这很好,表明当前的单次发送量小于网络的承载量。
  收到三条对同一分组的确认,即三条重复的确认。单次发送量往往大于3,例如发送序号为0、10、20、30、40的5条长度为10字节的分组,其中序号20的丢了,则返回的确认是10、20、20、20。3个20就是重复的确认。
  对某一条分组的确认迟迟未到,即超时。例如发送序号为0、10、20、30、40的5条长度为10字节的分组,其中序号30的丢了,则返回的确认是10、20、30、30。这才只有两条重复确认。然而刚刚说过,单次发送量往往大于3,所以超时更可能是因为不止一条分组或确认丢失而引起的,这说明网络比上一情况中的更加繁忙。

  上面提到拥塞控制主要是四个算法:1)慢启动,2)拥塞避免,3)拥塞发生,4)快速恢复。
  1988年,TCP-Tahoe 提出了1)慢启动,2)拥塞避免,3)拥塞发生时的快速重传
  1990年,TCP Reno 在Tahoe的基础上增加了4)快速恢复

  

1)慢热启动算法(Slow Start)
  TCP拥塞控制所使用的一种算法称为慢性启动(slow start),这种算法是基于这样的想法,它在开始时设置拥塞窗口大小(cwnd)
为一个最长段长度(MSS),每次接到一个确认时,窗口的大小就增加一个MSS值。窗口是慢速启动的,但是按指数规则增长。
  开始     —> cwnd = 1
  经过1个RTT后 —> cwnd = 2*1 = 2
  经过2个RTT后 —> cwnd = 2*2= 4
  经过3个RTT后 —> cwnd = 4*2 = 8
  如果带宽为W,那么经过RTT*log2W时间就可以占满带宽。

  

  当然,慢速启动不能一直继续下去,到达某个值必须停止该阶段。发送方保存一个称为ssthresh(慢速启动阈值)变量,当拥塞窗口中的字节达到这个阈值时,慢速启动阶段结束而下一个阶段开始。在大多数实现中,ssthresh值是65536个字节。

2)拥塞避免:加法增加
  以慢速启动算法开始,则拥塞窗口大小按指数规则增长,这样增长太快了。为了在拥塞发生之前避免拥塞,必须降低指数增长的速度。
  拥塞避免的主要思想是加法增大,也就是cwnd的值不再指数级往上升,开始加法增加。此时当窗口中所有的报文段都被确认时,cwnd的大小加1,cwnd的值就随着RTT开始线性增加,这样就可以避免增长过快导致网络拥塞,慢慢的增加调整到网络的最佳值。

3)拥塞发生算法:乘性减少
  上面讨论的两个机制都是没有检测到拥塞的情况下的行为,那么当发现拥塞了cwnd又该怎样去调整呢?

  

  之前提到过,重传是在两种情况下发生:
  1)如果RTO超时,那么存在非常严重的拥塞的可能性;包可能已在网络中丢失。
  在这种情况下,TCP做出强烈的反应。
  a.设置阈值为cwnd的一半。
  b.重新设置cwnd为1。
  c.启动慢速启动阶段。

  2)如果收到3个相同的ACK,那么存在着轻度拥塞的可能性。TCP在收到乱序到达包时就会立即发送ACK,TCP利用3个相同的ACK来判定数据包的丢失,此时进行快速重传。
在这种情况下,TCP做出轻度的反应。
  a.设置阈值为cwnd的一半。
  b.设置cwnd为阈值(有些实现是阈值加上3)
  c.启动拥塞避免阶段。

4)快速恢复
  “快速恢复”算法是在“快速重传”算法后添加的,当收到3个重复ACK时,TCP最后进入的不是拥塞避免阶段,而是快速恢复阶段。快速重传和快速恢复算法一般同时使用。快速恢复的思想是“数据包守恒”原则,即同一个时刻在网络中的数据包数量是恒定的,只有当“老”数据包离开了网络后,才能向网络中发送一个“新”的数据包,如果发送方收到一个重复的ACK,那么根据TCP的ACK机制就表明有一个数据包离开了网络,于是cwnd加1。如果能够严格按照该原则那么网络中很少会发生拥塞,事实上拥塞控制的目的也就在修正违反该原则的地方。
  快速恢复状态是一种介于慢启动和拥塞避免之间的状态。它像慢启动,其中cwnd以指数增长,但是cwnd以ssthresh加3MSS(而不是1)开始。当TCP进入快速恢复阶段,可能发生三种主要事件。如果重复ACK继续到达,那么TCP保持这种状态,但是cwnd呈指数增长。如果发生超时,TCP假设网络中有真实的拥塞,并进入慢启动状态。如果一个新的(非重复)ACK到达,TCP进入拥塞避免阶段,但是将cwnd的大小减小到ssthresh值,好像三次重复ACK没有发生过一样,并且转换是从慢启动状态到拥塞避免状态。
  具体来说快速恢复的主要步骤是:
  1.当收到3个重复ACK时,把ssthresh设置为cwnd的一半,把cwnd设置为ssthresh的值加3,然后重传丢失的报文段,加3的原因是因为收到3个重复的ACK,表明有3个“老”的数据包离开了网络。
  2.再收到重复的ACK时,拥塞窗口增加1。
  3.当收到新的数据包的ACK时,把cwnd设置为第一步中的ssthresh的值。原因是因为该ACK确认了新的数据,说明从重复ACK时的数据都已收到,该恢复过程已经结束,可以回到恢复之前的状态了,也即再次进入拥塞避免状态。注意,如果在此过程出现超时,则重新进入慢启动阶段。

  好了,讲了这么多,完全可以一图概之:

  

参考:
http://baike.baidu.com/view/1199185.htm
http://www.knowsky.com/383954.html
http://blog.jobbole.com/71427/
http://blog.csdn.net/todd911/article/details/10026441
http://www.netis.com.cn/flows/2012/08/tcp-%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3%E7%9A%84%E7%AE%80%E4%BB%8B/
http://www.firefoxbug.com/index.php/archives/2798/
http://coolshell.cn/articles/11609.html
http://www.ccs-labs.org/teaching/rn/animations/gbn_sr/
http://www.cnblogs.com/fll/archive/2008/06/10/1217013.html
http://blog.csdn.net/todd911/article/details/10026441

注)本文有些图片未找到出处,仅作学习用。

展开阅读全文

没有更多推荐了,返回首页