(四)计网五层模型--运输层篇之TCP

在上一篇中讲了关于运输层作为的作用为运行在不同主机上的进程之间提供了逻辑通信,功能是端到端的。还有关于进程之间如何如何通信的问题,以及在运输层中两个重要协议UDP和TCP,而关于TCP协议的部分内容实在很多,为此新开一篇讲述。在这一篇中会涉及到许多基本原理,其中包括差错检测、重传、累积确认、定时器以及用于序号和确认号的首部字段。并且我们还有两个拭待解决的问题:      

  1. 可靠数据传输的基本原理如何体现在TCP协议中?
  2. 如何控制运输层实体的传输速率以及避免网络中的拥塞,或从拥塞中恢复过来?

1. TCP连接管理

TCP被称为是面向连接的(connection-oriental),这是因为在一个应用进程可以开始向另一个应用进程发送数据之前,这两个进程必须先进行相互“握手”,即它们必须相互发送某些预备报文段,以建立确保数据传输的参数。每一个TCP连接都有三个阶段:连接建立、数据传送和连接释放。

在TCP连接建立过程中要解决三个问题:

  • 要使每一方知道对方的存在;
  • 要允许双方协商一些参数(如最大窗口值、是否使用窗口扩大选项和时间戳选项以及服务质量等);
  • 能够对运输实体资源(如缓存大小、连接表中的项目等)进行分配。

TCP连接的建立是采用客户服务器方式。主动发起连接建立的应用进程叫做客户(client),而被动等待连接建立的应用进程叫做服务器(sever)。每一条TCP连接唯一地被通信两端的两个端点所确定。

1.1 TCP连接的建立

TCP连接的建立会显著地增加感受到的时延,此外,许多常见的网络攻击(包括SYN洪范攻击),现在来观察一下它是如何建立的,建立会经历以下3个步骤,通常称为“三次握手”。

用“三次握手”建立TCP连接
  1. A的客户端进程首先创建TCP,然后向B发送连接请求报文段。这时首部中的同步号SYN = 1,同时选择一个初始序号seq = x。TCP规定,SYN(SYN = 1)报文段不能携带数据,但是要消耗一个序号。这时客户机A进入同步已发送状态(SYN-SENT)。
  2. B收到连接请求的报文段后,如果同意建立连接,则向A发送确认。在确认报文段中应当把SYN=1ACK=1确认号是ack = x + 1,同时也为自己初始化一个序号seq = y。注意该报文也不能携带数据,但是需要消耗掉一个序号。此时TCP服务器进程进入同步收到状态(SYN-RCVD)。
  3. TCP客户进程收到服务器端的确认后,还要想B发送确认。报文段的ACK置为1,确认号ack = y + 1,而自己的序号为seq = x + 1。TCP的标准规定,ACK报文段可以携带数据,如果不携带数据则不消耗序号,在这种情况下,下一个报文段的序号仍是seq = x + 1.这时TCP连接已经建立,此时A已经进入ESTABLISHED状态。当B收到确认后,也进入ESTABLISHED状态。

至此,A与B已经建立连接,我们称作“三次握手”或者“三次联络”。TCP提供的是全双工通信,因此通信双方的应用进程在任何时候都能发送数据。另外,服务器端的资源是在完成第二次握手时分配,而客户端的资源实在完成第三次握手时分配,这就使得服务器已于受到SYN洪范攻击。

为什么不采用“两次握手”建立连接呢,为什么A还要发送一次确认?

考虑一种情况,客户发出连接请求后,但因连接请求报文丢失而未收到确认。于是客户再重传一次连接请求。后来收到了确认,建立了连接。数据传输完毕后,就释放了连接。客户共发送了两个连接请求报文段,其中第一个丢失,第二个到达了服务器。没有“已失效的连接请求报文段”。 
现假定一种异常情况。即客户发出的第一个连接请求报文段并没有丢失,而是在某些网络结点长时间滞留了,以致延误到连接释放以后的某个时间才到达服务器。本来这是一个早已失效的报文段,但服务器收到此失效的连接请求后,就误认为是客户又一次发出一次新的连接请求。于是就向客户发出确认报文段,同意建立连接。假定不采用三次握手,那么只要服务器发出确认,新的连接就建立了。 
由于现在客户端并没有发出建立连接的请求,因此不会理睬服务器的确认,也不会向服务器发送数据。但服务器却以为新的连接已经建立了,并一直等待客户发送数据。服务器的许多资源就这样白浪费了。 
采用三次握手的办法可以防止上述现象的发生。例如刚才的情况下,客户不会向服务器的确认发出确认,由于服务器收不到确认,就知道客户并没有要求建立连接。

1.2 TCP连接的释放

当数据传输结束后,通信的上方都可以释放连接。TCP连接释放的过程通常被称为“四次挥手”;

用“四次挥手”释放TCP连接
  • A的应用进程先向其TCP发出连接释放报文段,然后停止发送数据,主动关闭TCP连接。A的连接释放报文段把FIN=1,其序号为seq = u,它等于前面已传送过的最后一个字节的序号加1。此时A进入FIN-WAIT-1状态,等待B的确认。TCP规定,FIN不携带数据,但是要消耗掉一个序号。
  • B收到连接释放报文段后向A发出确认,确认号是ack = u + 1,这个报文段自己的序号是seq=v,等于B前面已传送数据的最后一个字节的序号加1。然后B进入CLOSE-WAIT状态。TCP服务器进程这时通知高层应用进程,因而从A到B这个方向的连接就释放了,这时TCP的连接处于半关闭状态,即A已经没有数据向B发送了,但是若B仍要发送数据,A依旧要接受。也就是说从B到A这个方向的连接并未关闭。
  • A收到来自B的报文段后进入FIN-WAIT-2状态,等待B的连续释放报文。
  • 如果B已经没有数据向A发送了,其应用进程就会通知TCP释放连接。这时B发送连续确认报文段必须使FIN = 1,现在B的序号为seq=w(在半关闭状态,B可能又发送了一段数据)B还必须重复已经发送过的确认号ack = u + 1。这时B进入了LAST-ACK状态,等待A的确认。
  • A在收到B的报文段后进行确认,其确认号为w + 1(TCP规定,FIN报文段需要消耗一个序号),其自己的序号为seq = u + 1。然后进入到TIME-WAIT状态。这时需要注意的是TCP连接还没有释放掉,必须经过时间等待计时器(TIME-WAIT timer)设置的2MSL(Maximum Segment Lifetime),A才进入关闭状态。MSL叫做最大报文段寿命。

从A撤销相应的传输控制块TCB后,就结束了这次的TCP连接。至此需要回答:

为什么要有四次挥手的TIME_WAIT的状态? 

第一,为了保证A发送的最后一个ACK报文段能够到达B。如果A不等待2MSL,若A返回的最后ACK丢失,则B不能进入正常关闭状态,而A此时已经关闭,也不可能再重传。
第二,防止出现“已失效的连接请求报文段”。A在发送完最后一个确认报文段后,再经过2MSL可保证本连接持续的时间内所产生的所有报文段从网络中消失。这样就可以使下一个新的连接中不会出现这种旧的连接请求报文段。

2. TCP可靠传输

TCP在IP不可靠的尽力而为的服务至上建立了一种可靠数据传输服务(reliable data transfer service)。TCP提供的可靠数据传输服务就是要保证接收方进程从缓存区读取的字节流与发送方发出的字节流是完全一样的。TCP使用了校验、序号、确认和重传等机制来达到这个目的。

2.1 可靠传输的工作原理

理想的传输条件应该满足下面两个要求:
(1)传输的信道不产生差错;
(2)保证传输数据的正确性,无差错、不丢失、不重复、并且按序到达。
然而实际的网络都不具备以上这两个条件,但我们可以采用一些可靠地传输协议,当出现差错时让发送方重传出现差错的数据,同时在接收方来不及处理数据是,及时告诉发送方适当降低发送的速度。下面来分析几个简单的可靠传输协议。

2.1.1 停止等待协议

TCP 连接是全双工的连接,也就是说在通信的时候,双方既是发送方,也是接收方。下面只考虑一方发送,一方接受的情况。其中,A作为发送方,B作为接收方。

(1)无差错的情况

A发送分组M1,发送完就暂停发送,等待B的确认。B收到M1就向A发送确认。A在收到了对M1的确认后,就再发送下一个分组M2。如下图所示:

无差错情况
超时重传

(2)出现差错

如果A发送的过程中出现差错,B在接收M1时检测出了差错,就丢弃M1,其他什么都不做(也不会通知A收到有差错的分组)。又或者A传送的过程中分组丢失了,以上这两种情况下,B不会发送任何信息。
既然说它是可靠传输协议,那自然有它可靠的方法,如果发生以上的情况,A只要超过了一段时间仍然没有收到确认,就认为刚才发送的分组丢失了,所以它会重传刚刚的发送过的分组,也就是所谓的超时重传
超时重传的原理也很简单:发送方发送完一个分组后,就会设置一个超时计时器,如果超时计时器到期之前没有收到接收方发来的确认信息,则会重发刚发送过的分组;如果收到确认信息,则撤销该超时计时器。

值得注意的是:

  • 既然发送方发送的分组可能丢失或者有差错,可能需要重传,那么它必须暂时保留已发送的分组副本,只有收到确认后,才清除这个副本。
  • 分组和确认分组信息都应该有各自的编号,用来标示每一个分组和确认信息。(这样才知道需要发送哪个分组,收到了哪个分组的确认信息)
  • 超时计时器设置的时间应该略长于分组传送往返时间

(3)确认丢失和确认延迟

如果A发送了M1分组,到达B,B发送了M1确认信息,但由于网络原因,该确认信息丢失。那么这个时候,A在超时重传时间内,没有收到B的确认信息,而且它并不知道是自己的分组有差错、丢失,还是B发生的确认丢失了。因此,A会在超时重传过后,重传M1分组。

这时接收方B应采取两个行动:
1)丢弃M1分组,不向上层交付。(B之前已经收到过M1分组了)
2)向A发送确认(因为A重发了,表示A没有收到对M1的确认)

确认丢失
确认收到

还有可能是另一种情况,就是B发送了确认,没有丢失,但是延迟了。也就是说,B发送的确认在A超时计时器过期后才到达。
这种情况下,A收到确认信息后会丢弃,然后重传刚才的分组,B收到后,丢弃重复的分组,并重传确认信息。

(4)信道利用率

停止等待协议的优点是简单,但缺点是信道利用率太低,传输效率不高。从下图可以看出,假设A发送分组需要的时间为TD,B发送确认分组时间为TA,往返时间为RTT,那么完整的发送一个分组,需要耗费的时间为:TD+RTT+TA。过了这个时间后,第二个分组才开始发送。因此,信道利用率可以用如下公式计算:U=TD/(TD+RTT+TA)

信道利用率太低

例如,假定1200km的信道的往返时间RTT=20ms。分组长度是1200bit,发送速率是1Mb/s。忽略处理时间和TA,则可以计算出利用率U=5.66%。但假如把发送速率提高到10Mb/s,则U=5.96*10^-4。

还应该注意的是,上面并没有考虑出现差错后的分组重传。若出现分组重传,则对传送有用的信息来说,信道的利用率就还要降低。

为了提高传输效率,发送方可以不使用低效率的停止等待协议,而采用流水线传输。流水线传输就是发送方可以连续发送多个分组,不必每发完一个分组就停下来等待对方确认。

当使用流水线传输时,就要使用下面的连续APQ协议和滑动窗口协议。

流水线传输

2.1.2 连续ARQ协议

由于停止等待ARQ协议信道利用率太低,所以需要使用连续ARQ协议来进行改善。这个协议会连续发送一组数据包,然后再等待这些数据包的ACK。
发送方采用流水线传输。流水线传输就是发送方可以连续发送多个分组,不必每发完一个分组就停下来等待对方确认,如上图所示。
连续ARQ协议通常是结合滑动窗口协议来使用的,发送方需要维持一个发送窗口,如下图所示:

连续ARQ协议的工作原理

图(a)是发送方维持的发送窗口,它的意义是:位于发送窗口内的5个分组都可以连续发送出去,而不需要等待对方的确认,这样就提高了信道利用率。
连续ARQ协议规定,发送方每发送一个确认,就把发送窗口向前滑动一个分组的位置。例如图(b),当发送方收到第一个分组的确认,就把发送窗口向前移动一个分组的位置。如果已经发送了前5个分组,则现在可以发送窗口内的第6个分组。
接收方一般都是采用累积确认的方式。也就是说接收方不必对收到的分组逐个发送确认。而是在收到几个分组后,对按序到达的最后一个分组发送确认。如果收到了这个分组确认信息,则表示到这个分组为止的所有分组都已经正确接收到了。
累积确认的优点是容易实现,即使确认丢失也不必重传。缺点是,不能正确的反映收到的分组,比如发送方发送了前5个分组,而中间的第3个分组丢失了,这时候接收方只能对前2个发出确认。而不知道后面3个分组的下落,因此只能把后面的3个分组都重传一次,这种机制叫Go-back-N(回退N),表示需要再退回来重传已发送过的N个分组(该协议会在下文进一步论述)。可见当通信线路质量不好时,连续ARQ协议会带来负面影响。

2.2 可靠传输的实现

2.2.1 提供可靠数据传输服务机制

1. TCP校验

TCP的校验机制与UDP是一样的,这里贴出UDP的校验和计算方法:

UDP的校验和需要计算UDP首部加数据荷载部分,但也需要加上UDP伪首部。
这个伪首部指,源地址、目的地址、UDP数据长度、协议类型(0x11),协议类型就一个字节,但需要补一个字节的0x0,构成12个字节。
伪首部+UDP首部+数据 一起计算校验和。

UDP检验和的计算方法是:
1.按每16位求和得出一个32位的数;
2.如果这个32位的数,高16位不为0,则高16位加低16位再得到一个32位的数;
3.重复第2步直到高16位为0,将低16位取反,得到校验和。

2. 字节编号机制(序号)

TCP把数据看成一个无结构但是有序的字节流,而序号是建立在传送的字节流之上的。TCP 发送的数据段中“数据”部分(不包括数据段头部),每个字节都有一个序号,数据段以字节为单位对数据段中的“数据”部分进行一一编号,确保每个字节的数据都可以有序传送和接受。

3. 数据段确认机制(确认)

TCP 要求每接受一个数据段都必须由接收端向发送端返回一个确认数据段(可以用一个数据段一次确认全面多个数据段),其中的“确认号”表明接收端已正确接受的数据段序号(“确认号”前面的所有数据段,确认号表示将要接收的下一个数据段编号)。TCP默认使用累计确认,即只确认数据流中至第一个丢失的字节为止的字节。

4. 重传机制

有两种情况会导致TCP对报文进行重传:

(1)超时重传

TCP 中有一个重传定时器(Retransmission Timer),在发送一个数据段的同时也启动了该定时器。TCP每发送一个报文段,就对这个报文段设置一次计时。如果在定时器过期之前该数据段还没有被对方确认的话,且定时器停止,然后重传对应序号的数据段,直到发送成功为止。需要注意的是,并不是 RTT 定时器一到,就会立即重传数据,毕竟从“发送窗口”缓存中找到对应的数据段,然后安排重新发送都是需要时间的,实际上,超时重发时间间隔(Retransmission Time Out)要大于 RTT 值。

这里涉及两个重要问题:

一是如何确定 RTT 值?
表面上看起来 RTT 值很容易确定,因为它就是一个数据段往返发送端和接收端的时间总和,但在 TCP 通信中,中间可能经过了多个性能不一样的网络,而且不同时刻网络的拥塞程度可能不一样,这就造成了不同数据段的 RTT 时间并不一致,甚至波动非常大。但 TCP 必须适应这种情况,必须能动态地跟踪这些变化并相应地改变其超时重传时间,因此TCP采用一种自适应算法,它记录一个报文段发出时间,以及相应确认的时间,这两个时间之差叫作往返时间(Round-Trip Time,RTT)。

正因为 RTT 值不是固定的,所以就出现平滑 RTT(SRTT)的概念,就是在充分考虑历史 RTT 值的情况下所设计的一个 RTT 值计算公式。在最初的 RFC793 中,SRTT 的计算公式如下: 

SRTT(新的SRTT) = αSRTT(旧的 SRTT 值)+ (1−α)(1−α)RTT (新的 RTT 样本值)

SRTT 的初始值就是第一个 RTT 值。这里 α 是一个平滑因子,它决定了旧的 SRTT 值所占的权重,0≤α<10≤α<1 。RFC2988 推荐的 α 典型值为0.125。如果 α 很接近1,则表示新的 SRTT 值与旧的 SRTT很接近,变化不大。相反,则表示变化很大。显然,用这种方法计算得出的各个时刻的 SRTT 值更加平滑,更加接近当时的网络环境。

另一个是如何计 RTO 值?

在1988年,Jacobson 提出使用平均偏差作为标准偏差(就是 β SRTT)的新估计法,要求维护另一个被平滑的偏差 RTTD 。当一个确认数据段到达时,可以得出 SRRT 的新的 RTT 样本值之间的偏差 RTTD=(SRTT−RTT)。

第一次测量时,RTTD为测量到的 RTT 样本值的一半,在以后的测量中按照以下的公式进行计算: 

RTTD(新的RTTD)=βRTTD(旧的RTTD)+(1−β)×(SRTT−RTT)
这里的 β 可能与计算 SRTT 时的值相同,也可能不同,β通常取值 0.25.。SRTT 是平滑 RTT 值, RTT是新的 RTT 样本值。虽然 RTTD并不能完全等同于标准偏差,但已能足够的反应 SRTT 值的动态变化。现在大多数 TCP 实现都是使用这个算法,并且将超时重传时间设置为: 

RTO=SRTT+4RTTD

这里有一个重要的问题,那就是对于重传的数据段如何计算其 RTT 的值? 假设发送端发送了一个数据段,但在重传定时器内没有收到该数据段的确认数据段,于是重新发送了这个数据段,可过了一段时间,发送端又收到了该数据短短额确认数据段。这是发送端就迷糊了,这个确认数据段到底是对原来发送的那个数据段确认,还是对重发的数据段的确认呢?这两个数据段的序号是一样的,如果在上次发送的数据段后没有再重新发送新的数据段的话,接收端返回的确认数据段中的确认号都可能一样。如何收到的确认数据段是对重传数据段的确认,但却被发送端认为是对原数据段的确认,则这样计算出的 SRTT 和 RTO 值可定会偏大,否则会偏小。为此,发现这个问题的无线电爱好者 Karn 提出了一个建议,就是在计算加权平均 RTT 时,只要数据段被重发就不采用其往返时间作为计算 SRTT 和 RTO 的样本,这样得出的加权平均 SRTT 值和 RTO 值就比较准确了。

(2)冗余确认机制

在上面介绍的 TCP 重传机制中,如果在重传定时器超时后仍没收到一个数据段的确认,则可能会重传对应序号后面的所有数据段,因为后面的这些数据段均暂时不会被确认,这明显大大降低了 TCP 数据传输性能。冗余ACK就是再次确认某个报文段的ACK,而发送法先前已经收到过该报文段的确认。例如,发送方A发送了序号为1,2,3,4,5的TCP报文段,其中2号报文段在链路中丢失,他将无法到达接收方B。因此,3,4,5号报文段对于B来说就是失序报文段。TCP规定每当比期望序号大的失序报文段到达时,发送一个冗余ACK,指明下一个期待接收的序号[RFC 1122,RFC 2581]。如3,4,5号报文到达B,但它们不是B期望接受的下一个报文,于是B就发送3个对1号报文段的冗余ACK,表示自己希望接收2号报文段。TCP规定当发送方收到对同一个报文段的3个冗余ACK时,就可以认为跟在这个被确认报文段之后的报文段已经丢失。这时发送方A可以立即对2号报文执行重传,这种技术通常称为快速重传

2.2.2 滑动窗口机制

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协议

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

4. 选择重传协议

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

选择重传协议

2.2.3 TCP滑动窗口(Sliding Window)

在引入一个例子来说解释之前,先了解以下前提:

  • TCP协议的两端分别为发送者A和接收者B,由于是全双工协议,因此A和B应该分别维护着一个独立的发送缓冲区和接收缓冲区,由于对等性(A发B收和B发A收),我们以A发送B接收的情况作为例子;
  • 发送窗口是发送缓存中的一部分,是可以被TCP协议发送的那部分,其实应用层需要发送的所有数据都被放进了发送者的发送缓冲区;
  • 发送窗口中相关的有四个概念:已发送并收到确认的数据(不再发送窗口和发送缓冲区之内)、已发送但未收到确认的数据(位于发送窗口之中)、允许发送但尚未发送的数据以及发送窗口外发送缓冲区内暂时不允许发送的数据;
  • 每次成功发送数据之后,发送窗口就会在发送缓冲区中按顺序移动,将新的数据包含到窗口中准备发送;

先假定A收到B发来的确认报文字段,其中窗口是20字节,而确认号是31字节。(表明B期望接收到的下一个序号是31,序号30之前的数据已经收到了)。如图所示。

A的发送窗口:
发送窗口表示:在没有收到B的确认的情况下,可以连续把窗口内的数据发送出去。凡是已经发送过的数据,在未收到确认之前都必须暂时保留,以便超时重传使用。
发送窗口有如下特点:
1)发送窗口里面的序号表示允许发送的序号(如31~50)
2)发送窗口的位置由窗口的前沿和后沿的位置共同确定。发送窗口的后沿可能不动(没有收到确认),或者前移(收到新的确认)。发送窗口的前沿通常是不断的向前移动,但也可能不动。

现在假定A发送了序号为31-41的数据,如上图所示,图中可以看出要描述一个发送窗口的状态需要三个指针P1, P2, P3。
小于P1的是已发送并收到确认的部分,大于P3的是不允许发送部分。
P3-P1 = A的发送窗口(又称为通知窗口)
P2-P1 = 已发送但尚未收到确认的字节数
P3-P2 = 允许发送但尚未收到的字节数(又称为可用窗口或有效窗口)

B的接收过程:
B的接收窗口大小为20。在接收窗口外面,到30号为止的数据均发送过确认并交付主机使用,因此B不再保留(之前的数据)。

如图所示,B收到了32和33的数据。这些数据没有按序到达,因为序号为31的数据没有收到。由于B只能对按序到达的数据中的最高序号给出确认,因此B的发送的确认号仍然是31,而不能是32或33。现在假定B收到序号为31的数据并把序号为31-33的数据交付给主机,然后B删除这些数据。接着把接收窗口向前移动3个序号,同时给A发出确认。其窗口值仍未20,但确认号34,表明B已经接收到序号33为止的数据。而B收到的37 38 40的数据没有按序到达,先暂存在接收窗口中。
如果按照以上的方式进行发送数据。当发送窗口已满,可用窗口减小到0,因此发送停止。如果发送窗口内所有数据都正确到达B,而发出的确认由于网络问题没有到达A,为保证传输,此时A只能认为B还没有收到这部分数据。于是A经过一段时间过(由超时计时器控制)重传这部分数据,直到收到B的确认为止。

缓存机制

发送方的应用进程把字节流写入了TCP的发送缓存,接收方的应用进程从TCP的接受缓存中读取字节流。注:缓存空间和序号空间都是有限的并且循环使用的。

发送缓存用来暂时存放:
1.发送应用程序传送给发送方TCP准备的数据
2.TCP已发送但尚未收到确认的数据。
发送窗口通常只是发送缓存的一部分。已被确认的数据应当从发送缓存中删除,因此发送缓存与发送窗口的后沿是重合的。发送应用程序必须控制写入缓存的速率,不能太快,否则发送缓存就会没有存放数据的空间。

接收缓存用来暂时存放:
1.按序到达的,但尚未被接收应用程序读取的数据。
2.未按序到达的数据。
如果收到的分组检测出有差错,则要丢弃。如果接收应用程序来不及读取收到的数据,接收缓存最终就会被填满。使接收窗口减小到0.反之,接收应用程序能够及时从接收缓存中读取收到数据,接收窗口就会变大,但最大也不能超过接收缓存的大小。

2.2.3 流量控制

关于流量控制需要考虑两个问题:一是TCP如何利用滑动窗口实现流量控制的机制二是如何考虑流量控制中的传输效率。

所谓流量控制,主要是接收方传递信息给发送方,使其不要发送数据太快,是一种端到端的控制。主要的方式就是返回的ACK中会包含自己的接收窗口的大小,并且利用大小来控制发送方的数据发送:

如图所示,说明了利用可变窗口大小进行流量控制。设主机A向主机B发送数据。双方确定的窗口值是400.再设每一个报文段为100字节长,序号的初始值为seq=1,图中的箭头上面大写ACK,表示首部中的却认为为ACK,小写ack表示确认字段的值。
接收方的主机B进行了三次流量控制。第一次把窗口设置为rwind=300,第二次减小到rwind=100最后减到rwind=0,即不允许发送方再发送过数据了。这种使发送方暂停发送的状态将持续到主机B重新发出一个新的窗口值为止。

 这里面涉及到一种情况,如果B已经告诉A自己的缓冲区已满,于是A停止发送数据;等待一段时间后,B的缓冲区出现了富余,于是给A发送报文告诉A我的rwnd大小为400,但是这个报文不幸丢失了,于是就出现A等待B的通知而B等待A发送数据的死锁状态。A一直等待收到B发送的非零窗口的通知,而B也一直等待A发送的数据。这样就死锁了。为了解决这种死锁状态,TCP为每个连接设有一个持续计时器。只要TCP连接的一方收到对方的零窗口通知,就启动持续计时器,若持续计时器设置的时间到期,就发送一个零窗口探测报文段(仅携带1字节的数据),而对方就在确认这个探测报文段时给出了现在的窗口值。

此时,还会出现一种情况,某些时候,由于发端或收端的数据很慢,会引起大量的1字节数据传输,单个发送字节单个确认,和窗口有一个空余即通知发送方发送一个字节,无疑增加了网络中的许多不必要的报文(请想想为了一个字节数据而添加的40字节头部吧!)

(1)发端的进程产生数据很慢,时不时的来个1字节数据,那么TCP就会1字节1字节的发送,效率很低。
解决方法(Nagle算法):

  • 将第一块数据发出去
  • 然后等到发送缓存有足够多的数据(最大报文段长度),或者等到收端确认的ACK时再发送数据。
  • 重复b的过程

(2)收端进程由于消耗数据很慢,所以可能会有这么一种情况,收端会发送其窗口大小为1的信息,然后有是1字节的传输
解决办法(2种)

  • Clark方法:在接收缓存的一半变空,或者有足够空间放最大报文长度之前,宣告接收窗口大小为0
  • 推迟确认:在对收到的报文段确认之前等待到足够的接收缓存,或者等待到一个时间段(现在一般定义500ms)

3. 拥塞控制

3.1 拥塞控制的原理

在某段时间,若对网络中的某一资源的需求超过了该资源所能提供的可用部分,网络的性能就要变化,这种情况叫做拥塞。网络拥塞往往是由许多因素引起的,简单的提高节点处理机的速度或者扩大结点缓存的存储空间并不能解决拥塞问题。例如当某个结点缓存容量扩展到非常大,于是凡到达该结点的分组均可在结点的缓存队列中排队,不受任何限制。由于输出链路的容量和处理机的速度并未提高,因此在这队列中的绝大多数的分组在排队等待时间会大大增加,结果上层软件只好把他们进行重传。
因此,问题往往是整个系统的各个部分不匹配,只有各个部分平衡了,问题才会得到解决。

3.2 拥塞控制和流量控制的差别

所谓拥塞控制就是防止过多的数据注入到网络中,这样可以使网络中的路由器或链路不致过载。拥塞控制所要做的都有一个前提,就是网络能承受现有的网络负荷。它是一个全局性的过程,涉及所有的主机、所有的路由器,以及与降低网络传输性能相关的所有因素。
流量控制往往指的是点对点通信量的控制,是个端到端的问题。流量控制所要做的就是控制发送端发送数据的速率,以便使接收端来得及接受。

3.3 拥塞控制设计

拥塞控制是很难设计的,因为它是一个动态的问题,许多情况下,甚至正式拥塞控制机制本身成为引起网络性能恶化甚至死锁的原因。从控制理论的角度来看拥塞控制这个问题,可以分为开环控制闭环控制两种方法。

闭环控制就是在设计网络时事先将有关拥塞发生的所有因素考虑周到,一旦系统运行起来就不能在中途改正。
开环控制是基于反馈环路的概念,包括如下措施:
1)监测网路系统以便检测拥塞在何时何地发生
2)把拥塞发生的信息传送到可采取行动的地方
3)调整网络系统的行动以解决出现的问题。

3.4 拥塞控制方法

因特网建议标准RFC2581定义了进行拥塞控制的四种算法,即慢开始(Slow-start),拥塞避免(Congestion Avoidance)、快重传(Fast Restrangsmit)和快恢复(Fast Recovery)。我们假定
1)数据是单方向传送,而另外一个方向只传送确认
2)接收方总是有足够大的缓存空间,因为发送窗口的大小由网络的拥塞程度来决定。

1. 慢开始和拥塞避免

慢开始算法的思路是这样的:先令拥塞窗口cwnd=1,即一个最大报文段长度MSS,而在每收到一个对新的报文段的确认之后,将cwnd增加一个MSS。

  1. 发送方维持一个叫做“拥塞窗口”的变量,该变量和接收端口共同决定了发送者的发送窗口;
  2. 当主机开始发送数据时,避免一下子将大量字节注入到网络,造成或者增加拥塞,选择发送一个1字节的试探报文;
  3. 当收到第一个字节的数据的确认后,就发送2个字节的报文;(确认后再增大拥塞窗口
  4. 若再次收到2个字节的确认,则发送4个字节,依次递增2的指数级;
  5. 最后会达到一个提前预设的“慢开始门限”,比如24,即一次发送了24个分组,此时遵循下面的条件判定:
  • cwnd < ssthresh,继续使用慢开始算法;
  • cwnd > ssthresh,停止使用慢开始算法,改用拥塞避免算法;
  • cwnd = ssthresh,既可以使用慢开始算法,也可以使用拥塞避免算法;

拥塞避免算法就是:每经过一个往返时间RTT就把发送方的拥塞窗口增加一个MSS的大小,即让拥塞窗口缓慢地增大(加法增大),按照线性规律增长;
当出现网络拥塞,比如丢包时,将慢开始门限设为原先的一半(乘法减小),然后将cwnd设为1,执行慢开始算法(较低的起点,指数级增长);

网络拥塞的处理

当网络出现拥塞时,无论是在慢开始阶段还是在拥塞避免阶段,只要发送方检测到超时事件,就要把慢开始门限ssthresh设置为出现拥塞时的发送方cwnd值的一半(但不能小于2)。然后把拥塞窗口cwnd重新设置为1,执行慢开始算法。这样可以迅速减少主机发送到网络中的分组数,使得发生拥塞的路由器有足够时间把队列中积压的分组处理完毕。

2. 快重传和快恢复

一条TCP连接有时会因等待重传计时器的超时而空闲较长的时间,慢开始和拥塞避免无法很好的解决这类问题,因此提出了快重传和快恢复的拥塞控制方法。

快重传算法并非取消了重传机制,只是在某些情况下更早的重传丢失的报文段(如果当发送端接收到三个重复的确认ACK时,则断定分组丢失,立即重传丢失的报文段,而不必等待重传计时器超时)。慢开始算法只是在TCP建立时才使用。

快重传的机制是:

  1. 接收方建立这样的机制,如果一个包丢失,则对后续的包继续发送针对该包的重传请求;
  2. 一旦发送方接收到三个一样的确认,就知道该包之后出现了错误,立刻重传该包;
  3. 此时发送方开始执行“快恢复”算法:
  •     慢开始门限减半;
  •     cwnd设为慢开始门限减半后的数值;
  •     执行拥塞避免算法(高起点,线性增长);

 快恢复算法有以下两个要点:
1)当发送方连续收到三个重复确认时,就执行“乘法减小”算法,把慢开始门限减半,这是为了预防网络发生拥塞。
2)由于发送方现在认为网络很可能没有发生拥塞,因此现在不执行慢开始算法,而是把cwnd值设置为慢开始门限减半后的值,然后开始执行拥塞避免算法,使拥塞窗口的线性增大。

参考资料:
[1]TCP运输连接管理 Jack_Channy
[2]TCP的连接管理 haozlee
[3]UDP校验和计算 骆驼阿甘
[4]TCP/IP(三)—— 可靠传输工作原理 10730
[5]计算机网络【七】:可靠传输的实现 Windhawk'CU
[6]TCP/IP之TCP协议(3):流量控制(滑动窗口协议)风不清云不淡
[7]TCP/IP详解学习笔记(14)-- TCP可靠传输的实现 newwy
[8]《计算机网络(第6版)》,谢希仁著
[9]《深入理解计算机网络》 王达著 机械工业出版社

猜你喜欢

转载自blog.csdn.net/Ronnyz/article/details/81544383