TCP的数据流——滑动窗口,拥塞窗口,慢启动,Nagle算法,经受时延的确认等

(1).窗口机制
    滑动窗口协议的基本原理就是在任意时刻,发送方都维持了一个连续的允许发送的帧的序号,称为发送窗口;同时,接收方也维持了一个连续的允许接收的帧的序号,称为接收窗口。发送窗口和接收窗口的序号的上下界不一定要一样,甚至大小也可以不同。不同的滑动窗口协议窗口大小一般不同。发送方窗口内的序列号代表了那些已经被发送,但是还没有被确认的帧,或者是那些可以被发送的帧。下面举一个例子(假设发送窗口尺寸为2,接收窗口尺寸为1):


   分析:①初始态,发送方没有帧发出,发送窗口前后沿相重合。接收方0号窗口打开,等待接收0号帧;②发送方打开0号窗口,表示已发出0帧但尚确认返回信息。此时接收窗口状态不变;③发送方打开0、1号窗口,表示0、1号帧均在等待确认之列。至此,发送方打开的窗口数已达规定限度,在未收到新的确认返回帧之前,发送方将暂停发送新的数据帧。接收窗口此时状态仍未变;④接收方已收到0号帧,0号窗口关闭,1号窗口打开,表示准备接收1号帧。此时发送窗口状态不变;⑤发送方收到接收方发来的0号帧确认返回信息,关闭0号窗口,表示从重发表中删除0号帧。此时接收窗口状态仍不变;⑥发送方继续发送2号帧,2号窗口打开,表示2号帧也纳入待确认之列。至此,发送方打开的窗口又已达规定限度,在未收到新的确认返回帧之前,发送方将暂停发送新的数据帧,此时接收窗口状态仍不变;⑦接收方已收到1号帧,1号窗口关闭,2号窗口打开,表示准备接收2号帧。此时发送窗口状态不变;⑧发送方收到接收方发来的1号帧收毕的确认信息,关闭1号窗口,表示从重发表中删除1号帧。此时接收窗口状态仍不变。

    若从滑动窗口的观点来统一看待1比特滑动窗口、后退n及选择重传三种协议,它们的差别仅在于各自窗口尺寸的大小不同而已。1比特滑动窗口协议:发送窗口=1,接收窗口=1;后退n协议:发窗口>1,接收窗口>1;选择重传协议:发送窗口>1,接收窗口>1。

(2).1比特滑动窗口协议

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

         

(3).后退n协议

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

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

(4).选择重传协议

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


转自:http://blog.csdn.net/yujun00/archive/2006/03/23/636495.aspx


==========================================文二=====================================================================

TCP的半关闭
   TCP提供了连接的一端在结束它的发送后还能接收来自另一端数据的能力,这就是TCP的半关闭。
客户端发送FIN,另一端发送对这个FIN的ACK报文段。当收到半关闭的一端在完成它的数据传送后,才发送FIN关闭这个方向的连接,客户端再对这个FIN确认,这个连接才彻底关闭。

回顾TCP协议那些事儿 -[技术前沿]

版权声明:转载时请以超链接形式标明文章原始出处和作者信息及 本声明

http://bigwhite.blogbus.com/logs/11582229.html

我不是计算机科班出身。记得大学的时候旁听计算机系的网络课,当时计算机系使用教材是" 计算机网络--自顶向下方法与Internet特色"的影印版,这本教材与众不同的一个地方就是作者 JAMESF.KUROSE和 KEITHW.ROSS采用了'自顶向下'的编排思路,先从应用层开始,最后讲到物理层。而且这本书在语言上形象生动,通俗易懂。只怪我当初没有一心一意听讲,到现在存在我的脑子中的基本概念居多,深刻理解甚少。以致于工作后遇到此类的问题,只能恶补。这不,在12月1日凌晨全国统一短信类服务接入代码的调整工作中,我就遇到了此类问题,不得不再次抱起W.RichardStevens的' TCP详解卷一'啃了啃,回顾一下 TCP协议那些事儿。

做应用层网络程序开发的,手头上都有一把利器:抓包工具,更专业的名词就是协议分析工具,常用的且功能强大的协议分析工具有: TCPDUMP(Windows平台上的叫 Windump)、 Ethereal等。工作中常常会遇到因应用层程序在协议字段发送和接收解析上不一致而出现'纠纷'问题,这时我们一般采用的在TCP层用协议分析工具抓取该层原始数据包作为'对峙'的证据;还有的就是在客户端与服务器端链接问题上的一些现象也需要到TCP层去分析原因,这就需要对TCP层的基本工作原理有一个清晰的认识。

首先我们要明确:TCP头部中设置的一系列域都是为了能达到分割、重传、查重、重组、流控、全双工的协议功能而设置的,这里比较重要的字段就是序列号和确认号。由于要达到重传、查重、重组、全双工这些目的,TCP层需要通过序列号和确认号来保证。序列号用来标识发送端传送数据包的顺序,并且指导接收端对多数据包进行顺序重组;发送端传送一个数据包后,它会把这个数据包放入重发队列中,同时启动计时器,如果收到了关于这个包的确认信息,便将此数据包从队列中删除;如果在计时器超时的时候仍然没有收到确认信息,则需要重新发送该数据包。

TCP层以"三次握手"建立链接而"闻名于世",三次握手的目的:建立链接,为后续的数据流传输奠基,因为TCP是双工的,因此在握手过程需告知彼此数据包发送的起始序列号。

Client --> 置SYN标志 序列号 = J,确认号 = 0----> Server
Client <-- 置SYN标志 置ACK标志 序列号 = K, 确认号 = J + 1<-- Server
Clinet --> 置ACK标志 序列号 = J + 1,确认号 = K + 1--> Server

链接建立后,接下来Client端发送的数据包将从J + 1开始,Server端发送的数据包将从K +1开始,这里要说明的是:建立链接时,Client端宣称自己的初始序列号是J,Server端宣称自己的初始序列号是K,但是建立连接后的数据包却各自中初始序列号+1开始,这是因为SYN请求本身需要占用一个序列号。

在数据传输阶段,按照常理应用层数据的传输是这样的:(我们假定建立连接阶段Client端最后的确认包中序列号 = 55555, 确认号= 22222)
Client --> 置PSH标志,置ACK标志 序列号 = 55555, 确认号 =22222,数据包长度 = 11 --->Server 
Client <-- 置ACK标志,序列号 = 22222, 确认号 = 55566 (=55555 +11),数据包长度 = 0 <--- Server
Client <-- 置PSH标志,置ACK标志 序列号 = 22223, 确认号 =55566,数据包长度 = 22 <---Server 
Client --> 置ACK标志,序列号 = 55566, 确认号 =22244(=22222+22),数据包长度 = 0 ---> Server

注:PSH标志指示接收端应尽快将数据提交给应用层。从我协议分析的经历来看,在数据传输阶段,几乎所有数据包的发送都置了PSH位;而ACK标志位在数据传输阶段也是一直是置位的。

但是实际我们在分析过程看到的却都是如下这样的:
Client --> 置PSH标志,置ACK标志 序列号 = 55555, 确认号 =22222,数据包长度 = 11 --->Server 
Client <-- 置PSH标志,置ACK标志 序列号 = 22222, 确认号 = 55566(=55555 + 11),数据包长度 = 22 <--- Server
Client --> 置PSH标志,置ACK标志 序列号 = 55566, 确认号 = 22244(=22222 + 22),数据包长度 = 33 ---> Server
Client <-- 置PSH标志,置ACK标志 序列号 = 22244, 确认号 = 55599(=55566 + 33),数据包长度 = 44 <--- Server

也就是说:数据接收端将上一个应答和自己待发送的应用层数据组合在一起发送了。TCP的传输原则是尽量减少小分组传输的数量,所以一般默认都开启"带时延的ACK"。一般实现中,时延在200ms。Nagle算法多用来实现"带时延的ACK",它要求一个TCP连接上最多只能有一个未被确认的小分组。在该分组的确认到达之前不能发送其他小分组。也就是说:发送端在发送一个分组后,需等待这个分组的ACK确认后,才可以进行下一个分组的发送。这样一来网络的传输效率被大大降低了。对于大数据块的传输来说,这样很多时候是难以忍受的。另一种拥塞控制策略被引入,那就是TCP的滑动窗口协议,滑动窗口协议是分组发送和分组确认不再同步,发送端可以连续发送n个分组,接收端同样也可以用一个确认包来一起确认这n个分组,通常n= 2。各种OS的TCP协议栈在实现上都是综合了Nagle算法和滑动窗口协议的,TCP层对应用层数据分组大小进行 多次判断(一般分组大小都是和MSS做比较的),以在Nagle和滑动窗口协议之间抉择到底选择哪一种控制方式进行发送。" TheLinux Network Architecture: Design and Implementation of NetworkProtocols in the LinuxKernel"一书介绍了Linux在TCP层上的设计和实现,当然最直观的还是去分析 Linux源代码了。

拆除TCP连接过程用一句话表述就是:你关你的发送通道,我关我的发送通道(因为TCP是全双工)。当一方关闭发送通道后,仍可接收另一方发送过来数据,这样的情况就成为"半关闭"。然而多数情况下,"半关闭"使用的很少,而且半关闭需要SOCKETAIP支持在SOCKET上的shutdown(而不是调用close)。

正常的关闭流程是源于Fin报文的:
Client --> Fin ACK -->Server
Client <-- ACK <-- Server
Client <-- Fin ACK -- Server
Client --> ACK --> Server
发送Fin分组的一端会先将发送缓冲中的报文按序发完之后,再发出Fin;所以说Fin又叫做:orderlyrelease。

异常的关闭流程是源于Rst报文的。一个典型的例子就是当客户端所要链接的服务器端的端口并没有程序在listen,这时服务器端的TCP层会直接发送一个Rst报文,告诉客户端重置连接。Rst报文是无需确认的。客户端在收到Rst后会通知应用层对方异常结束链接(需通过SOCKETAPI的设置才能得知对方是异常关闭)。
===================================================文三============================================================


 TCP的数据流大致可以分为两类,交互数据流与成块的数据流。交互数据流就是发送控制命令的数据流,比如relogintelnetftp命令等等;成块数据流是用来发送数据的包,网络上大部分的TCP包都是这种包。

       很明显,TCP在传输这两种类型的包时的效率是不一样的,因此为了提高TCP的传输效率,应该对这两种类型的包采用不同的算法。

       总之,TCP的传输原则是尽量减少小分组传输的数量。

TCP的交互式数据流

?         经受时延的确认技术

TCP的交互式数据流通常使用“经过时延的确认”技术。通常Server在接收到从Client发送过来的数据时,并不马上发送ACK,而是等一小段时间,看看本机是否有数据要反馈给Client,如果有,就将数据包含在此ACK包中,以前发送给Client。一般情况下这个时延为200ms。需要注意的时这个200ms的定时器时相对于内核的时钟滴答的,也就是jeffs的。加入一个数据分组到达后,此定时器已经pass100ms,那么再过100ms ACK才会被发送,如果在这100ms内有数据要反馈,则在100msACK会和数据一起发送。

      

?         Nagle算法分析。

Nagle算法主要用来预防小分组的产生。在广域网上,大量TCP小分组极有可能造成网络的拥塞。

       Nagle时针对每一个TCP连接的。它要求一个TCP连接上最多只能有一个未被确认的小分组。在改分组的确认到达之前不能发送其他小分组。TCP会搜集这些小的分组,然后在之前小分组的确认到达后将刚才搜集的小分组合并发送出去。

       有时候我们必须要关闭Nagle算法,特别是在一些对时延要求较高的交互式操作环境中,所有的小分组必须尽快发送出去。

       我们可以通过编程取消Nagle算法,利用TCP_NODELAY选项来关闭Nagle算法。

 

TCP成块数据流

       TCP成块数据流相关的东西有很多,比如流量控制,紧急数据传输,数据窗口大小调整等等。

?         正常数据流

TCP通常不会对每个到达的数据分段进行确认操作,通常一个ACK报文可以确认多个成块数据段报文,通常情况下是两个成块数据报文段需要一个ACK报文确认。通常是由下面的原有造成的 :当收到一个报文后,此TCP连接被标识未一个未完成的时延确认,当再次收到一个数据报文后,此连接有两个未确认的报文段,TCP马上发送一个ACK,当第三个数据报文到达后,第四个报文到达前,通常此TCP连接已经经过了200ms延时,因此一个ACK被发送,这样的循环周而复始,从而出现了一个ACK确认两个数据报文的情况。当然,ACK的产生很大程度上和其接收数据报文段的时间紧密相关,也就是和Client段发送数据的频率相关,和网络拥塞程度相关,和ClientServer两端的处理能力相关,总是是一个多因素决定的结果。

 

?         TCP的滑动窗口协议

TCP使用滑动窗口协议来进行流量控制。特别需要注意的是,滑动窗口是一个抽象的概念,它是针对每一个TCP连接的,而且是有方向的,一个TCP连接应该有两个滑动窗口,每个数据传输方向上有一个,而不是针对连接的每一端的。

窗口左边沿向右边滑动叫做窗口合拢,表示发送方发送了数据或者接收到了确认;窗口右边沿向右边滑动叫做窗口的张开,表示数据已经被用户空间进程接收并且释放了缓存;窗口左边沿向左移动则表明此ACK是重复ACK,应该丢弃;窗口右边沿向左移动叫做窗口收缩,一般不会有人这样做。

当左边沿和右边沿重合的时候表明窗口大小是0,此时发送方不应该在发送数据了,因为接收方的接收缓冲区已满,用户进程还没以接收。当用户进程接收完成后,接收方应该发送一个ACK,表明此时的接收窗口已经恢复,此ACK的序号同前一个win0ACK相同。

同样,在实现中,发送方不必发送一个全窗口的数据,但是它当然可以这样做。ACK总是将窗口向右边滑动,窗口的大小可以减小,接收方在发送ACK之前不必等待窗口被填满(即变为0),很多实现是收到两个数据报文段后立刻发送ACK

 

?         TCP窗口大小的调整

TCP窗口的大小通常由接收端来确认,也就是在TCP建立连接的第二个SYN+ACK报文的Win字段来确认。

当然,程序可以随时改变这个窗口(缓存)的大小。默认的窗口大小是4096字节,但是对于文件传输来说这并不是一个理想的数字,如果程序的主要目的是传输文件,那么最好将这个缓存设置到最大,但是这样可能会造成发送端连续发送多个数据报文段后,接收方才反馈一个ACK的情况,当然,这也没有什么不可以的,只要不超时,就不算错。

?         TCPPUSH

PUSHTCP报头中的一个标志位,发送方在发送数据的时候可以设置这个标志位。该标志通知接收方将接收到的数据全部提交给接收进程。这里所说的数据包括与此PUSH包一起传输的数据以及之前就为该进程传输过来的数据。

Server端收到这些数据后,它需要立刻将这些数据提交给应用层进程,而不再等待是否还有额外的数据到达。

那么应该合适设置PUSH标志呢?实际上现在的TCP协议栈基本上都可以自行处理这个问题,而不是交给应用层处理。如果待发送的数据会清空发送缓存,那么栈就会自动为此包设置PUSH标志,源于BSD的栈一般都会这么做,而且,BSD TCP STACK也从来不会将收到的数据推迟提交给应用程序,因此,在BSD TCP STACK中,PUSH位是被忽略的,因为根本就没有用。

 

?         TCP的慢启动(拥塞窗口)

TCP在局域网环境中的效率是很高的,但是到了广域网的环境中情况就不同了,在发送方和接收方之间可能存在多个Router以及一些速率比较慢的链路,而且一些中继路由器必须缓存分组,还可能分片,所以在广域网的环境中,TCP的效率可能出现问题。

为了解决这个问题,现在的TCP栈都支持“慢启动”算法,即拥塞窗口控制算法。该算法通过观察到新分组进入网络的速率与另一端返回ACK的速率相同而工作。其实,拥塞窗口是发送方使用的一种流量控制算法。

慢启动为TCP的发送方增加了一个拥塞窗口,当连接建立时,拥塞窗口被初始化为一个报文段大小,每收到一个ACK,拥塞窗口就会增加一个报文段,发送方取拥塞窗口与通过窗口的最小值作为发送的上限。

 

?         TCP成块数据吞吐量

TCP窗口大小,窗口流量控制,慢启动对TCP的成块数据传输综合作用,可能对TCP的数据传输有意想不到的影响。

       RTTRound-Trip Time :往返时间。是指一个报文段从发出去到收到此报文段的ACK所经历的时间。通常一个报文段的RTT与传播时延和发送时延两个因素相关。

       在发送的过程中有可能发生这样的情况,即TCP两端的传输“管道”被填满,即整个管道上都有数据在跑,此时不管拥塞窗口和通告窗口是多少,管道上都不能在容纳更多的数据了。此时每当接收方从网络上移去一个报文段,发送方就发送一个,但是管道上的ACK总是固定的,这种情况就是连接的理想稳定状态。

       一般情况下带宽*时延就是一条线路的容量,因此吧RTT减小可以增加一条线路的容量,注意RTT加大的意思时传输时间减小!

       当 数据由一个大的管道向一个小的管道传输时,就有可能发生拥塞,例如,当若干输入流到达一个路由器,而此路由器的输出带宽小于这些输入流的带宽总和时,就会 发生拥塞。这种情况普遍见于局域网与广域网的接口处。如果发送方处于局域网,而且不使用慢启动,使用局域网的带宽尽快的发送报文,那么返回的ACK之间的间隔与最慢的广域网链路一致。而且,由于路由器转发包速度慢,所以路由器就有可能主动丢失分组包。

 

?         TCP的紧急方式

TCP提供了一种“紧急方式”的数据传输方式,TCP的一端可以告诉另一端有些具有某种方式的紧急数据被放在了普通的数据流中,接收方可以自行选择处理。紧急方式客厅通过设置TCPURG标识位与紧急指针的偏移量来设置。这个紧急指针指向紧急数据的最后一个字节(也有可能是最后一个字节的下一个字节)。

现在有许多实现将紧急方式叫做“带外数据”,其实这是不正确的。

       目前紧急指针被用来禁止停止FTP的数据传输。不过总的来说,用的不多。

       对于数据传输来说,如果用紧急数据来传输大量数据,这种方法显然是不可取的,再建立一个TCP连接不是更简单有效吗?

 

====================================================================

http://zhidao.baidu.com/question/98620785

为了防止网络的拥塞现象,TCP提出了一系列的拥塞控制机制。最初由V. Jacobson在1988年的论文中提出的TCP的拥塞控制由“慢启动(Slow start)”和“拥塞避免(Congestion avoidance)”组成,后来TCP Reno版本中又针对性的加入了“快速重传(Fast retransmit)”、“快速恢复(Fast Recovery)”算法,再后来在TCP NewReno中又对“快速恢复”算法进行了改进,近些年又出现了选择性应答( selective acknowledgement,SACK)算法,还有其他方面的大大小小的改进,成为网络研究的一个热点。

TCP的拥塞控制主要原理依赖于一个拥塞窗口(cwnd)来控制,在之前我们还讨论过TCP还有一个对端通告的接收窗口(rwnd)用于流量控制。窗口值的大小就代表能够发送出去的但还没有收到ACK的最大数据报文段,显然窗口越大那么数据发送的速度也就越快,但是也有越可能使得网络出现拥塞,如果窗口值为1,那么就简化为一个停等协议,每发送一个数据,都要等到对方的确认才能发送第二个数据包,显然数据传输效率低下。TCP的拥塞控制算法就是要在这两者之间权衡,选取最好的cwnd值,从而使得网络吞吐量最大化且不产生拥塞。

由于需要考虑拥塞控制和流量控制两个方面的内容,因此TCP的真正的发送窗口=min(rwnd, cwnd)。但是rwnd是由对端确定的,网络环境对其没有影响,所以在考虑拥塞的时候我们一般不考虑rwnd的值,我们暂时只讨论如何确定cwnd值的大小。关于cwnd的单位,在TCP中是以字节来做单位的,我们假设TCP每次传输都是按照MSS大小来发送数据的,因此你可以认为cwnd按照数据包个数来做单位也可以理解,所以有时我们说cwnd增加1也就是相当于字节数增加1个MSS大小。

慢启动:最初的TCP在连接建立成功后会向网络中发送大量的数据包,这样很容易导致网络中路由器缓存空间耗尽,从而发生拥塞。因此新建立的连接不能够一开始就大量发送数据包,而只能根据网络情况逐步增加每次发送的数据量,以避免上述现象的发生。具体来说,当新建连接时,cwnd初始化为1个最大报文段(MSS)大小,发送端开始按照拥塞窗口大小发送数据,每当有一个报文段被确认,cwnd就增加1个MSS大小。这样cwnd的值就随着网络往返时间(Round Trip Time,RTT)呈指数级增长,事实上,慢启动的速度一点也不慢,只是它的起点比较低一点而已。我们可以简单计算下:

   开始           --->     cwnd = 1

   经过1个RTT后   --->     cwnd = 2*1 = 2

   经过2个RTT后   --->     cwnd = 2*2= 4

   经过3个RTT后   --->     cwnd = 4*2 = 8

如果带宽为W,那么经过RTT*log2W时间就可以占满带宽。

拥塞避免:从慢启动可以看到,cwnd可以很快的增长上来,从而最大程度利用网络带宽资源,但是cwnd不能一直这样无限增长下去,一定需要某个限制。TCP使用了一个叫慢启动门限(ssthresh)的变量,当cwnd超过该值后,慢启动过程结束,进入拥塞避免阶段。对于大多数TCP实现来说,ssthresh的值是65536(同样以字节计算)。拥塞避免的主要思想是加法增大,也就是cwnd的值不再指数级往上升,开始加法增加。此时当窗口中所有的报文段都被确认时,cwnd的大小加1,cwnd的值就随着RTT开始线性增加,这样就可以避免增长过快导致网络拥塞,慢慢的增加调整到网络的最佳值。

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

首先来看TCP是如何确定网络进入了拥塞状态的,TCP认为网络拥塞的主要依据是它重传了一个报文段。上面提到过,TCP对每一个报文段都有一个定时器,称为重传定时器(RTO),当RTO超时且还没有得到数据确认,那么TCP就会对该报文段进行重传,当发生超时时,那么出现拥塞的可能性就很大,某个报文段可能在网络中某处丢失,并且后续的报文段也没有了消息,在这种情况下,TCP反应比较“强烈”:

1.把ssthresh降低为cwnd值的一半

2.把cwnd重新设置为1

3.重新进入慢启动过程。

从整体上来讲,TCP拥塞控制窗口变化的原则是AIMD原则,即加法增大、乘法减小。可以看出TCP的该原则可以较好地保证流之间的公平性,因为一旦出现丢包,那么立即减半退避,可以给其他新建的流留有足够的空间,从而保证整个的公平性。

其实TCP还有一种情况会进行重传:那就是收到3个相同的ACK。TCP在收到乱序到达包时就会立即发送ACK,TCP利用3个相同的ACK来判定数据包的丢失,此时进行快速重传,快速重传做的事情有:

1.把ssthresh设置为cwnd的一半

2.把cwnd再设置为ssthresh的值(具体实现有些为ssthresh+3)

3.重新进入拥塞避免阶段。

后来的“快速恢复”算法是在上述的“快速重传”算法后添加的,当收到3个重复ACK时,TCP最后进入的不是拥塞避免阶段,而是快速恢复阶段。快速重传和快速恢复算法一般同时使用。快速恢复的思想是“数据包守恒”原则,即同一个时刻在网络中的数据包数量是恒定的,只有当“老”数据包离开了网络后,才能向网络中发送一个“新”的数据包,如果发送方收到一个重复的ACK,那么根据TCP的ACK机制就表明有一个数据包离开了网络,于是cwnd加1。如果能够严格按照该原则那么网络中很少会发生拥塞,事实上拥塞控制的目的也就在修正违反该原则的地方。

具体来说快速恢复的主要步骤是:

1.当收到3个重复ACK时,把ssthresh设置为cwnd的一半,把cwnd设置为ssthresh的值加3,然后重传丢失的报文段,加3的原因是因为收到3个重复的ACK,表明有3个“老”的数据包离开了网络。 

2.再收到重复的ACK时,拥塞窗口增加1。

3.当收到新的数据包的ACK时,把cwnd设置为第一步中的ssthresh的值。原因是因为该ACK确认了新的数据,说明从重复ACK时的数据都已收到,该恢复过程已经结束,可以回到恢复之前的状态了,也即再次进入拥塞避免状态。

快速重传算法首次出现在4.3BSD的Tahoe版本,快速恢复首次出现在4.3BSD的Reno版本,也称之为Reno版的TCP拥塞控制算法。

可以看出Reno的快速重传算法是针对一个包的重传情况的,然而在实际中,一个重传超时可能导致许多的数据包的重传,因此当多个数据包从一个数据窗口中丢失时并且触发快速重传和快速恢复算法时,问题就产生了。因此NewReno出现了,它在Reno快速恢复的基础上稍加了修改,可以恢复一个窗口内多个包丢失的情况。具体来讲就是:Reno在收到一个新的数据的ACK时就退出了快速恢复状态了,而NewReno需要收到该窗口内所有数据包的确认后才会退出快速恢复状态,从而更一步提高吞吐量。

SACK就是改变TCP的确认机制,最初的TCP只确认当前已连续收到的数据,SACK则把乱序等信息会全部告诉对方,从而减少数据发送方重传的盲目性。比如说序号1,2,3,5,7的数据收到了,那么普通的ACK只会确认序列号4,而SACK会把当前的5,7已经收到的信息在SACK选项里面告知对端,从而提高性能,当使用SACK的时候,NewReno算法可以不使用,因为SACK本身携带的信息就可以使得发送方有足够的信息来知道需要重传哪些包,而不需要重传哪些包

你可能感兴趣的:(网络编程)