关于TCP的问题总结

一. 什么是TCP

TCP 协议是一种面向连接的,为不同主机进程间提供可靠数据传输的协议。TCP 协议假定其所使用的网络栈下层协议(如IP 协议)是非可靠的,其自身提供机制保证数据的可靠性传输。在目前的网络栈协议族中,在需要提供可靠性数据传输的应用中,TCP 协议是首选的,有时也是唯一的选择。TCP 协议是在最早由Cerf Kahn[1]所提出的有关网络数据包传输协议的概念之上建立的。TCP 协议被设计成符合分层协议结构,工作在ISO/OSI 七层网络模型中的传输层中,使用网络层协议(如最常见的IP 协议)提供的服务。网络层协议尽最大努力传输上层提供的数据但并不保证数据传输的可靠性。可靠性保证必须由上层协议(如TCP 协议)提供。网络层协议主要完成的工作有:

1> 实现不同网络(主机)间的数据包路由传递。

2> 在发送端(或中转站)提供数据包分片功能以使数据包大小满足 PMTUPath-MTU)。

3> 在接收端提供数据包分片重组功能。

4> 负责数据包优先级,安全性等问题。

 

传输层协议(主要针对 TCP 协议而言)主要完成的工作有(并非所有的传输层协议都需要提供这些功能如UDP 协议就不提供可靠性数据传输):

1> 提供多路复用。

2> 实现数据基本传输功能。

3> 建立通信通道。(三次握手)

4> 提供流量控制。(滑动窗口)

5> 提供数据可靠性传输保证。

 

数据可靠性传输保证是其中最为重要的方面,也是TCP 协议区别于其它协议的最重要特性。所谓提供数据可靠性传输不仅仅指将数据成功的由本地主机传送到远端主机,数据可靠性传输包括如下内容:

1> 能够处理数据传输过程中被破坏问题。

2> 能够处理重复数据接收问题。

3> 能够发现数据丢失以及对此进行有效解决。

4> 能够处理接收端数据乱序到达问题。

二. 如何保证TCP传输的可靠性(数据超时重传+数据应答机制)

TCP 协议必须提供对所有这些问题的解决方案方可保证其所声称的数据可靠性传输。TCP协议规范和当前绝大多数TCP 协议实现代码均采用数据重传和数据确认应答机制来完成TCP 协议的可靠性数据传输。数据超时重传和数据应答机制的基本前提是对每个传输的字节进行编号,即我们通常所说的序列号。数据超时重传是发送端在某个数据包发送出去,在一段固定时间后如果没有收到对该数据包的确认应答,则(假定该数据包在传输过程中丢失)重新发送该数据包。而数据确认应答是指接收端在成功接收到一个有效数据包后,发送一个确认应答数据包给发送端主机,该确认应答数据包中所包含的应答序列号即指已接收到的数据中最后一个字节的序列号加1,加1 的目的在于指出此时接收端期望接收的下一个数据包中第一个字节的序列号。数据超时重传和数据确认应答以及对每个传输的字节分配序列号TCP 协议提供可靠性数据传输的核心本质。

 

1)数据确认应答数据包中应答序列号的含义

应答序列号并非其表面上所显示的意义,其实际上是指接收端希望接收的下一个字节的序列号。所以接收端在成功接收到部分数据后,其发送的应答数据包中应答序列号被设置为这些数据中最后一个字节的序列号加一。所以从其含义上来说,应答序列号称为请求序列号有时更为合适。应答序列号在TCP 首部中应答序列号字段中被设置。而TCP 首部中序列号字段表示包含该TCP 首部的数据包中所包含数据的第一个字节的序列号(令为N)。如果接收端成功接收该数据包,之前又无丢失数据包,则接收端发送的应答数据包中的应答序列号应该为:N+LEN。其中LEN 为接收的数据包的数据长度。该应答序列号也是发送端将要发送的下一个数据包中第一个字节的序列号(由此亦可看出上文中将应答序列号称为请求序列号的原因所在)。

 

2)数据确认应答中的累积效应

TCP 协议中接收端对所接收数据的应答是累积的。累积的含义有二:

1>应答序列号是逐渐递增的,这与发送端数据编号是递增的相吻合。

2>不可进行跨越式数据应答。

 

所谓不可进行跨越式数据应答,可以以数据包乱序到达为例进行说明。如果由于发送端所选择传输路径的不同,较后发送的序列号较大的数据包先到达接收端,而先发送的序列号较小的数据包由于线路问题(或路由器故障)被暂时延迟在网络中,此时接收端不可对这些序列号较大的数据进行应答。如果接收端需要发送一个应答数据包,则应答序列号仍然应该设置成对序列号较小的数据包的请求(注意应答序列号指的是接收端希望接收的下一个字节的序列号,故在数据传输过程中将应答数据包称为数据请求数据包更为合适)。举例来说,如果接收端目前的应答序列号为201,表示接收端正在等待发送端发送从201 开始编号的数据,之后发送端连续发送了两个数据包,第一个数据包中数据序列号范围为201-300,第二个数据包中数据序列号范围为301-400。如果由于选择了不同的传输路径造成第二个数据包最先到达接收端,而第一个数据包在网络中延迟了一段时间,则接收端不可对第二个数据包进行应答,即不可发送应答序列号为401 的确认应答数据包,而是不断发送应答序列号为201的应答数据包直到该序列号的数据到达。我们通常所说的快速重传机制即发送端在连续接收3 个相同序列号的应答数据包后需要立刻重传应答序列号所表示的数据。因为此时表示极有可能出现了数据包丢失的情况,如上例中第一个数据包如果丢失在网络中并且发送端重传的相同数据包由于选择相同的线路也未能到达接收端,则接收端将不断发送应答序列号为201 的应答数据包而不会将应答序列号设置为401。注意此时接收端已接收到序列号从301-400 的数据。

 

3)重传应答机制与序列号结合:

1> 能够处理数据在传输过程中被破坏的问题。

首先通过对所接收数据包的校验,确认该数据包中数据是否存在错误。如果有,则简单丢弃或者发送一个应答数据包重新对这些数据进行请求。发送端在等待一段时间后,则会重新发送这些数据。本质上,数据传输错误的解决是通过数据重传机制完成的。

2> 能够处理接收重复数据问题。

首先利用序列号可以发现数据重复问题。因为每个传输的数据均被赋予一个唯一的序列号,如果到达的两份数据具有重叠的序列号(如由发送端数据包重传造成),则表示出现数据重复问题,此时只须丢弃其中一份保留另一份即可。多个数据包中数据重叠的情况解决方式类似。本质上,数据重复问题的解决是通过检查序列号完成的。

3> 能够发现数据丢失以及进行有效解决。

首先必须说明,此处数据包丢失的概念是指在一段合理时间内,应该到达的数据包没有到达,而非我们平常所理解的永远不到达。所以数据包丢失与数据包乱序到达有时在判断上和软件处理上很难区分。数据丢失的判断是猜测性的,我们无法确定一个数据包一定丢失在传输过程中,大多是被延迟在网络中,即实质的问题只是数据包乱序到达。将二者区分开来的一个主要依据是在合理的时间内,由这个可能丢失的数据包所造成的序列号“空洞“是否能够被填补上。可能的数据丢失一个显然的结果是在接收端接收的数据出现序列号不连续现象。如接收端只接收到序列号从1 100 的数据包,之后又接收到序列号从200 300 的数据包,而且在一段合理的

时间内(由此基本排除乱序问题),序列号从101 199 的数据一直未到达,则表示包含序列号从101 199 的数据包在传输过程中很可能丢失(或者有极不正常的延迟)。对数据包是否丢失判断的另外一个干扰因素是发送端的重传机制,如果一个序列号较前的数据包在网络中丢失,造成序列号较后的数据包提前到达接收端,也会暂时造成序列号不连续,但由于发送端在没有接收到确认应答时,会重新发送序列号较前的那个数据包,如果此后接收端接收到一个重传的数据包,则仅仅只会在接收端造成数据包乱序到达的表象。无论实质如何,如果软件实现判断出数据包丢失,则接收端将通过不断发送对这些丢失的数据的请求数据包(也即应答数据包,见前文中对数据应答数据包和数据应答累积效应的说明)来迫使发送端重新发送这些数据。通常发送端自身会自发的重传这些未得到对方确认的数据,但由于重传机制采用指数退避算法,每次重传的间隔时间均会加倍,所以通过发送方主动重传机制恢复的时间较长,而接收端通过不断发送对这些丢失数据的请求,发送端在接收到三个这样的请求数据包后(三个请求数据包中具有同一个请求序列号--也即前文中所说的应答序列号),会立刻触发对这些数据的重新发送,这称为快速恢复或者快速重传机制。本质上,对于数据丢失问题的解决是通过数据重传机制完成的。在此过程中序列号和数据确认应答起着关键的作用。

4> 能够处理接收端数据乱序到达问题。

如果通信双方存在多条传输路径, 则有可能出现数据乱序问题,即序列号较大的数据先于序列号较小的数据到达,而发送端确实是按序列号由小到大的顺序发送的。数据乱序的本质是数据都成功到达了,但到达的顺序不尽如人意。对这个问题的解决相对比较简单,只需对这些数据进行重新排序即可。本质上,对数据乱序问题的解决是通过排序数据序列号完成的。

 

2TCP 协议可靠性数据传输软件实现基本原理

由上文可见,序列号,数据超时重传和数据确认应答机制保证了 TCP 协议可靠性传输的要求。由于需要对所发送的数据进行编号,又需要对接收的数据进行应答,所以使用TCP 议的通信双方必须通过某种机制了解对方的初始序列号。只有在确切知道对方的初始序列号的情况下,才能从一开始对所接收数据的合法性进行判断。另外还需要在本地维护一个对方应答的序列号,以随时跟随对方的数据请求。在最后通信通道关闭时,可以确知本地发送的数是否已被对方完全接收;此外这个对方应答序列号在控制本地数据通量方面也发挥着重要的作用:用本地发送序列号减去对方应答序列号则可以立刻得知目前发送出去的数据有多少没有得到对方的应答。

 

综上所述,可靠性传输要求通信双方维护如下序列号:

SND.NXT本地将要发送的下一个序列号。该变量对应TCP 首部中序列号字段。表示该数据包中所包含数据的第一个字节的序列号。每次发送一个数据包,该变量都需要进行更新:SND.NXT = SND.NXT + 本次发送的数据包中包含的数据长度SND.ACKED

对方对本地所发送数据到目前为止进行了应答的序列号,换句话说,SND.ACKED+1 表示本地已发送出去但尚未得到对方应答的数据集中对应的第一个(最小的)序列号。RCV.NXT本地希望接收的下一个序列号。该序列号被称为应答序列号,也可称为请求序列号,在本地发送的应答报文中,TCP 首部中应答序列号字段即设置为该变量的值,表示本地希望从对方接收的下一个字节的序列号。

 

 

1(上图)显示了TCP 首部格式。序列号字段对应前文中SND.NXT 变量,应答序列号字段对应前文中RCV.NXT 变量。ACK 标志位设置为1 表示这是一个应答数据包。实际上对于 TCP 协议而言,在成功建立连接后,此后发送的所有数据包的ACK 标志位均被设置为1,即在传送正常数据的同时传送应答,如此处理可以减少网络中传输的数据包数量。

 

 

三.TCP如何建立连接

1TCP 首部格式中SYN 标志位仅使用在建立TCP 连接的过程中,TCP 建立连接的过程被称为“三路握手“连接,即一般通信双方共需要传输三个数据包方能成功建立一个TCP 接。我们通常将建立连接作为使用TCP 协议理所当然的前导过程,但很少去质疑这样一个建立连接过程的必要性。实际上,在上文中已经做出部分解释,使用TCP 协议必须首先建立一个连接是保证TCP 协议可靠性数据传输的基本前提(当然由于TCP 协议是一个有状态协议,必须通过某种机制进行通信双方状态上的同步,而建立连接就是这样一种机制)。至于为何需要三个数据包,原因是建立连接过程中信息的交换必须至少使用三个数据包,从下文的分析来看,建立连接最多需要使用四个数据包。需要再次提到的是:SYN 标志位只是用在建立连接的三个(或者四个)数据包中,一旦连接建立完成后,之后发送的所有数据包不可设置SYN 标志位。单从保证数据可靠性传输角度而言,TCP 协议需要在正式数据传输之前首先进行某些信息的交换,这个信息即是双方的初始序列号(另外的一些信息包括最大报文长度通报等)。

诚如前文所述,序列号的使用对于 TCP 协议而言至关重要,在正式数据传输之前,双方必须得到对方的初始字节数据的编号,这样才有可能对其所接收数据的合法性进行判断,才有其它的对数据重复,数据重叠等一系列问题的进一步判别和解决。故交换各自的初始序列号必须在正式数据传输之前完成,我们美其名曰这个过程为连接建立过程。至于双方TCP 议各自状态的更新主要是软件设计上可靠性保证的一个辅助,并非这个所谓的建立过程所主要关注的问题。

 

初始序列号的交换从最直接的角度来说需要四个数据包:

1> 主机 A 向主机B 发送其初始序列号。

2> 主机 B 向主机A 确认其发送的初始序列号。

3> 主机 B 向主机A 发送其初始序列号。

4> 主机 A 向主机B 确认其发送的初始序列号。

 

我们将<2><3>两步合为一步,即B A 确认其(A 之前发送的)初始序列号的同时发送其(即B 自己的)初始序列号。所谓确认数据包即将数据包的ACK 标志位设置为1 即可。注意这三个(或四个)数据包中SYN 标志位设置为1,而且SYN 标志位也仅在这三个(或四个)数据包中被设置为1

此处有一个问题:即AB 主机在通报各自初始序列号的同时能否传输一些正常数据,原理上可以(TCP 协议规范上并没有说不可以),但是大多数实现在通报初始序列号时都不附带正常数据,而是将其作为一个单独的过程,由此正式确立建立连接一说。

 

 

四.TCP如何拆除连接

当前连接的双方都可以发起拆除连接操作,但简单的拆除连接可能会造成数据丢失。为此,TCP采用四次握手的方式拆除连接。

四次握手与三次握手类似:

①1发拆除请求

②2收到请求,并发确认,1收到该确认后,不再发送数据,但任然会接收数据(半连接)

③2发拆除请求

④1收到请求,并确认,到此拆除完成

五. 为什么拆除连接要比建立连接多一次握手

  • 这是因为服务端的LISTEN状态下的SOCKET当收到SYN报文的建连请求后,它可以把ACK和SYN(ACK起应答作用,而SYN起同步作用)放在一个报文里来发送。但关闭连接时,当收到对方的FIN报文通知时,它仅仅表示对方没有数据发送给你了;但未必你所有的数据都全部发送给对方了,所以你可以未必会马上会关闭SOCKET,也即你可能还需要发送一些数据给对方之后,再发送FIN报文给对方来表示你同意现在可以关闭连接了,所以它这里的ACK报文和FIN报文多数情况下都是分开发送的。
  • 六.TCP滑动窗口机制

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

     



       
    分析:初始态,发送方没有帧发出,发送窗口前后沿相重合。接收方0号窗口打开,等待接收0号帧;发送方打开0号窗口,表示已发出0帧但尚确认返回信息。此时接收窗口状态不变;发送方打开01号窗口,表示01号帧均在等待确认之列。至此,发送方打开的窗口数已达规定限度,在未收到新的确认返回帧之前,发送方将暂停发送新的数据帧。接收窗口此时状态仍未变;接收方已收到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时,滑动窗口协议退化为停等协议(stopandwait)。该协议规定发送方每发送一帧后就要停下来,等待接收方已正确接收的确认(acknowledgement)返回后才能继续发送下一帧。由于接收方需要判断接收到的帧是新发的帧还是重新发送的帧,因此发送方要为每一个帧加一个序号。由于停等协议规定只有一帧完全发送成功后才能发送新的帧,因而只用一比特来编号就够了。其发送方和接收方运行的流程图如图所示。


             

    (3).后退n协议

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


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

    (4).选择重传协议

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

    滑动窗口协议

    仍然考虑链路的延迟与带宽的乘积为8 K B,帧尺寸为1 K B的情形。让发送方在收到第一帧的A C K的同时准备发送第九帧。允许我们这样做的算法称为滑动窗口( sliding window),时间线如图2 - 2 1所示。


    1. 滑动窗口算法


    滑动窗口算法工作过程如下。首先,发送方为每1帧赋一个序号(sequence number),记作S e q N u m。现在,让我们忽略S e q N u m是由有限大小的头部字段实现的事实,而假设它能无限增大。发送方维护3个变量:发送窗口大小(send window size),记作S W S,给出发送方能够发 

    送但未确认的帧数的上界; L A R表示最近收到的确认帧( last acknowledgement re c e i v e d)的序号;L F S表示最近发送的帧(last frame sent)的序号,发送方还维持如下的不变式:

    LAR-LFR≤RWS 

     

    当一个确认到达时,发送方向右移动L A R,从而允许发送方发送另一帧。同时,发送方为所发的每个帧设置一个定时器,如果定时器在A C K到达之前超时,则重发此帧。注意:发送方必须存储最多S W S个帧,因为在它们得到确认之前必须准备重发。

    接收方维护下面3个变量:接收窗口大小(receive window size),记为RW S,给出接收方所能接收的无序帧数目的上界; L A F表示可接收帧(l a rgestacceptable frame)的序号;L F R表示最近收到的帧(last frame re ce i v e d)的序号。接收方也维持如下不变式:

    LFS-LAR≤SWS 

     

    当一个具有顺序号S e q N u m的帧到达时,接收方采取如下行动:如果S e q N u m≤L F RS e q N u m > L A F,那么帧不在接收窗口内,于是被丢弃;如果L F RSe q N u m≤L A F,那么帧在接收窗口内,于是被接收。现在接收方需要决定是否发送一个A C K。设S e q N u m To A C K表示未被确认帧的最大序号,则序号小于或等于S e q N u m To Ac k的帧都已收到。即使已经收到更高序号的分组,接收方仍确认S e q N u m To A c k的接收。这种确认被称为是累积的(c u m u l a t i v e)。然后它设置L F R = S e q Nu m To A c k,并调整L A F = L F R + RW S。例如,假设L F R= 5(即,上次接收方发送的A C K是为了确认顺序号5的),并且RWS = 4。这意味着L A F = 9。如果帧78到达,则存储它们,因为它们在接收窗口内。然而并不需要发送A C K,因为帧6还没有到达。帧78被称为是错序到达的。(从技术上讲,接收方可以在帧78到达时重发帧5A C K。)如果帧6当时到达了(或许它在第一次丢失后又重发从而晚到,或许它只是被延迟了),接收方确认帧8L F R置为8L A F置为1 2。如果实际上帧6丢失了,则出现发送方超时,重发帧6。我们看到,当发生超时时,传输数据量减少,这是因为发送方在帧6确认之前不能向前移动窗口。这意味着分组丢失时,此方案将不再保证管道满载。注意:分组丢失时间越长,这个问题越严重。
    注意,在这个例子中,接收方可以在帧7刚一到达时就为帧6发送一个认帧N A Knegative acknowledgment)。然而,由于发送方的超时机制足以发现这种情况,发送N A K反而为发送方增加了复杂性,因此不必这样做。正如我们已提到的,当帧78到达时为帧5发送一个额外的A C K是合理的;在某些情况下,发送方可以使用重复的A C K作为一个帧丢失的线索。这两种方法都允许早期的分组丢失检测,有助于改进性能。
    关于这个方案的另一个变种是使用选择确认(selective acknowledgements)。即,接收方能够准确地确认那些已收到的帧,而不只是确认按顺序收到最高序号的帧。因此,在上例中,接收方能够确认帧78的接收。如果给发送方更多的信息,就能使其较容易地保持管道满载,但增加了实现的复杂性。

    发送窗口大小是根据一段给定时间内链路上有多少待确认的帧来选择的;对于一个给定的延迟与带宽的乘积,S W S是容易计算的。另一方面,接收方可以将RW S设置为任何想要的值。通常的两种设置是:RW S= 1,表示接收方不存储任何错序到达的帧; RW S=S W S,表示接收方能够缓存发送方传输的任何帧。由于错序到达的帧的数目不可能超过S W S个,所以设置RWS >S W S没有意义。


    2. 有限顺序号和滑动窗口


    现在我们再来讨论算法中做过的一个简化,即假设序号是可以无限增大的。当然,实际上是在一个有限的头部字段中说明一个帧的序号。例如,一个3比特字段意味着有8个可用序号0 ~ 7。因此序号必须可重用,或者说序号能回绕。这就带来了一个问题:要能够区别同一序号的不同次发送实例,这意味着可用序号的数目必须大于所允许的待确认帧的数目。例如,停止等待算法允许一次有1个待确认帧,并有2个不同的序号。
    假设序号空间中的序号数比待确认的帧数大1,即S W S ≤ M A a xS e q N u m -1 ,其中M a x Seq N u m 是可用序号数。这就够了吗?答案取决于RW S 。如果RW S = 1,那么MaxSeqNum≥SWS+1是足够了。如果RW S等于S W S,那么有一个只比发送窗口尺寸大1M a x S e q N u m是不够的。为看清这一点,考虑有8个序号0 ~ 7的情况,并且S W S = RW S = 7。假设发送方传输帧0 ~ 6,并且接收方成功接收,但A C K丢失。接收方现在希望接收帧70 ~ 5,但发送方超时,然后发送帧0 ~ 6。不幸的是,接收方期待的是第二次的帧0 ~ 5,得到的却是第一次的帧0 ~ 5。这正是我们想避免的情况。
    结果是,当RW S = S W S时,发送窗口的大小不能大于可用序号数的一半,或更准确地说,SWS<(Maxseqnum+1)/2直观地,这说明滑动窗口协议是在序号空间的两半之间变换,就像停止等待协议的序号是在01之间变换一样。唯一的区别是,它在序号空间的两半之间连续滑动而不是离散的变换。
    注意,这条规则是特别针对RW S = S W S的。我们把确定适用于RW SS W S的任意值的更一般的规则留做一个练习。还要注意,窗口的大小和序号空间之间的关系依赖于一个很明显以至于容易被忽略的假设,即帧在传输中不重新排序。这在直连的点到点链路上不能发生,因为在传输过程中一个帧不可能赶上另一个帧。然而,我们将在第5章看到用在一个不同环境中的滑动窗口算法,并且需要设计另一条规则。

     

    3. 滑动窗口的实现


    下面的例程说明我们如何实现滑动窗口算法的发送和接收的两个方面。该例程取自一个正在使用的协议,称为滑动窗口协议S W PSliding Window Pro t o c o l)。为了不涉及协议图中的邻近协议,我们用H L P(高层协议)表示S W P上层的协议,用L I N K(链路层协议)表示S W P下层的协议。我们先定义一对数据结构。首先,帧头部非常简单:它包含一个序号( S e q N u m)和一个确认号( A c k N u m)。它还包含一个标志( F l a g s)字段,表明帧是一个A C K帧还是携带数据的帧。

    其次,滑动窗口算法的状态有如下结构。对于协议发送方,该状态包括如上所述的变量L A RL F S,以及一个存放已发出但尚未确认的帧的队列( s e n d Q)。发送方状态还包含一个计数信号量( counting semaphore),称为s e n d Wi n d o w N o t F u l l。下面我们将会看到如何使用它,但一般来说,信号量是一个支持s e m Wa i ts e m S i g n a l操作的同步原语。每次调用S e m S i g n al,信号量加1,每次调用S e m Wa i t,信号量减1。如果信号量减小,导致它的值小于0,那么调用进程阻塞(挂起)。一旦执行了足够的s e m S i g n a l操作而使信号量的值增大到大于0,在调用s e m Wa i t的过程中阻塞的进程就允许被恢复。
    对于协议的接收方,如前所述,该状态包含变量L F R ,加上一个存放已收到的错序帧的队列(r e c v Q)。最后,虽然未显示,发送方和接收方的滑动窗口的大小分别由常量S W SRW S表示。

    S W P的发送方是由s e n d S W P过程实现的。这个例程很简单。首先, s e m Wa i t使这个进程在一个信号量上阻塞,直到它可以发另一帧。一旦允许继续, s e n d S W P设置帧头部中的顺序号,将此帧的拷贝存储在发送队列( s e n d Q)中,调度一个超时事件以便处理帧未被确认的情况,并将帧发给低层协议。

    值得注意的一个细节是刚好在调用m s g A d d H dr之前调用s t o r e _ s w p _ h d r。该例程将存有S W P头部的C语言结构( s t a t e -> h d r)转化为能够安全放在消息前面的字节串( h b u f)。该例程(未给出)必须将头部中的每一个整数字段转化为网络字节顺序,并且去掉编译程序加入C语言结构中的任意填充。7 . 1节将详细讨论字节顺序的问题,但现在,假设该例程将多字整数中最高有效位放在最高地址字节就足够了。
    这个例程的另一个复杂性是使用s e m Wa i t s e n dW i n d ow N o t F u l l 信号量。S e n dWi n d o w N o t F u l l被初始化为发送方滑动窗口的大小S W S(未给出这一初始化)。发送方每传输一帧, s e m Wa i t操作将这个数减1,如果减小到0,则阻塞发送方进程。每收到一个A C K,在d e l i v e r SW P中调用s e m S i g n a l操作(见下面)将此数加1,从而激活正在等待的发送方进程。

    在继续介绍S W P的接收方之前,需要调整一个看上去不一致的地方。一方面,我们说过,高层协议通过调用s e n d操作来请求低层协议的服务,所以我们就希望通过S W P发送消息的协议能够调用s e n dS W P, p a c k e t)。另一方面,用来实现S W P的发送操作的过程叫做s e n d S W P,并且它的第一个参数是一个状态变量( S w p S t a t e)。结果怎样呢?答案是,操作系统提供了粘结代码将对s e n d的一般调用转化为对s e n d S W P的特定协议调用的粘结代码。这个粘结代码将s e n d的第一个参数(协议变量S W P)映射为一个指向s e n d S W P的函数指针和一个指向S W P工作时所需的协议状态的指针。我们之所以通过一般函数调用使高层协议间接调用特定协议函数,是因为我们想限制高层协议中对低层协议编码的信息量。这使得将来能够比较容易地改变协议图的配置。现在来看d e l i v e r操作的S W P的特定协议实现,它在过程d e l i v e r SW P中实现。这个例程实际上处理两种不同类型的输入消息:本结点已发出帧的A C K和到达这个结点的数据帧。在某种意义上,这个例程的ACK部分是与send SWP中所给算法的发送方相对应的。通过检验头部的F l a g s字段可以确定输入的消息是ACK还是一个数据帧。注意,这种特殊的实现不支持数据帧中捎带A C K。当输入帧是一个ACK时,delive rSWP仅仅在发送队列(send Q)中找到与此ACK相应的位置(slot),取消超时事件,并且释放保存在那一位置的帧。由于A C K可能是累积的,所以这项工作实际上是在一个循环中进行的。对于这种情况值得注意的另一个问题是子例程swp In Wind o w的调用。这个子例程在下面给出,它确保被确认帧的序号是在发送方当前希望收到的A C K的范围之内。
    当输入帧包含数据时, d e l i v e r S W P首先调用m s g S t r i pH d rl o a d _ s w p _ h d r以便从帧中提取头部。例程l o a d _ s w p_ h d r对应着前面讨论的s t o r e _ s w p _ h d r,它将一个字节串转化为容纳S W P头部的C语言数据结构。然后d e l i v e r SW P调用s w p I n Wi n d o w以确保帧序号在期望的序号范围内。如果是这样,例程在已收到的连续的帧的集合上循环,并通过调用d e l i v e r HL P例程将它们传给上层协议。它也要向发送方发送累积的A C K,但却是通过在接收队列上循环来实现的(它没有使用本节前面给出的s e q N u m To Ac k变量)。

     

    最后,s w p I n Window是一个简单的子例程,它检查一个给定的序号是否落在某个最大和最小顺序号之间。

     

    4. 帧顺序和流量控制


    滑动窗口协议可能是计算机网络中最著名的算法。然而,关于该算法易产生混淆的是,它可以有三个不同的功能,第一个功能是本节的重点,即在不可靠链路上可靠地传输帧。(一般来说,该算法被用于在一个不可靠的网络上可靠地传输消息。)这是该算法的核心功能。

    滑动窗口算法的第二个功能是用于保持帧的传输顺序。这在接收方比较容易实现,因为每个帧有一个序号,接收方要保证已经向上层协议传递了所有序号比当前帧小的帧,才向上传送该当前帧。即,接收方缓存了(即没有传送)错序的帧。本节描述的滑动窗口算法确实保持了帧的顺序,尽管我们可以想象一个变异,即接收方没有等待更早传送的帧都到达就将帧传给下一个协议。我们可以提出的一个问题是:我们是否确实需要滑动窗口协议来保持帧的顺序,或者,这样的功能在链路层是否是不必要的。不幸的是,我们还没有看到足够多的网络体系结构来回答这个问题我们首先需要理解的是,点到点链路序列如何由交换机连接而形成一条端到端的路径。
    滑动窗口算法的第三个功能是,它有时支持流量控制(f l o w c o n t ro l),它是一种接收方能够控制发送方使其降低速度的反馈机制。这种机制用于抑制发送方发送速度过快,即抑制传输比接收方所能处理的更多的数据。这通常通过扩展滑动窗口协议完成,使接收方不仅确认收到的帧,而且通知发送方它还可接收多少帧。可接收的帧数对应着接收方空闲的缓冲区数。在按序传递的情况下,在将流量控制并入滑动窗口协议之前,我们应该确信流量控制在链路层是必要的。
    尚未讨论的一个重要概念是系统设计原理,我们称其为相关性分离(separation of concerns)。即,你必须小心区别有时交织在一种机制中的不同功能,并且你必须确定每一个功能是必要的,而且是被最有效的方式支持的。在这种特定的情况下,可靠传输、按序传输和流量控制有时组合在一个滑动窗口协议里,我们应该问问自己,在链路层这样做是否正确。带着这样的疑问,我们将在第3章(说明X. 2 5网如何用它实现跳到跳的流量控制)和第5章(描述T C P如何用它实现可靠的字节流信道)重新考虑滑动窗口算法。

     

     

     

    猜你喜欢

    转载自iam42.iteye.com/blog/1703351
    今日推荐