您的位置:首页 > 理论基础 > 计算机网络

第20章 TCP的成块数据流

2018-11-11 22:09 3523 查看

20.1 引言

在第15章我们看到TFTP使用了停止等待协议。数据发送方在发送下一个数据块之前需要等待接收对已发送数据的确认。本章我们将介绍TCP所使用的被称为滑动窗口协议的另一种形式的流量控制方法。该协议允许发送方在停止并等待确认前可以连续发送多个分组。由于发送方不必每发一个分组就停下来等待确认,因此该协议可以加速数据的传输。

我们还将介绍TCP的PUSH标志,该标志在前面的许多例子中都出现过。此外,我们还要介绍慢启动,TCP使用该技术在一个连接上建立数据流,最后介绍成块数据流的吞吐量。

20.2 正常数据流

我们以从主机svr4单向传输8192个字节到主机bsdi开始。在bsdi上运行sock程序作为服务器:

bsdi % sock -i -s 7777

其中,标志-i-s指示程序作为一个“吸收(sink)”服务器运行(从网络上读取并丢弃数据),服务器端口指明为7777。相应的客户程序运行为:

svr4 % sock -i -n8 bsdi 7777

该命令指示客户向网络发送8个1024字节的数据。图20-1显示了这个过程的时间系列。我们在输出的前3个报文段中显示了每一端MSS的值。

发送方首先传送3个数据报文段(4~6)。下一个报文段(7)仅确认了前两个数据报文段,这可以从其确认序号为2048而不是3073看出来。

报文段7的ACK的序号之所以是2048而不是3073是由以下原因造成的:当一个分组到达时,它首先被设备中断例程进行处理,然后放置到IP的输入队列中。三个报文段4、5和6依次到达并按接收顺序放到IP的输入队列。IP将按同样顺序将它们交给TCP。当TCP处理报文段4时,该连接被标记为产生一个经受时延的确认。TCP处理下一报文段(5),由于TCP现在有两个未完成的报文段需要确认,因此产生一个序号为2048的ACK(报文段7),并清除该连接产生经受时延的确认标志。TCP处理下一个报文段(6),而连接又被标志为产生一个经受时延的确认。在报文段9到来之前,由于时延定时器溢出,因此产生一个序号为3073的ACK(报文段8)。报文段8中的窗口大小为3072,表明在TCP的接收缓存中还有1024个字节的数据等待被应用程序读取。

报文段11~16说明了通常使用的“隔一个报文段确认”的策略。报文段11、12和13到达并被放入IP的接收队列。当报文段11被处理时,连接被标记为产生一个经受时延的确认。当报文段12被处理时,它们的ACK(报文段14)被产生且连接的经受时延的确认标志被清除。报文段13使得连接再次被标记为产生经受时延。但在时延定时器溢出之前,报文段15处理完毕,因此该确认立刻被发送。

210TCP/IP详解,卷1:协议

图20-1 从svr4传输8192个字节到bsdi

注意到报文段7、14和16中的ACK确认了两个收到的报文段是很重要的。使用TCP的滑动窗口协议时,接收方不必确认每一个收到的分组。在TCP中,ACK是累积的—它们表示接收方已经正确收到了一直到确认序号减1的所有字节。在本例中,三个确认的数据为2048字节而两个确认的数据为1024字节(忽略了连接建立和终止中的确认)。

tcpdump看到的是TCP的动态活动情况。我们在线路上看到的分组顺序依赖于许多无法控制的因素:发送方TCP的实现、接收方TCP的实现、接收进程读取数据(依赖于操作系统的调度)和网络的动态性(如以太网的冲突和退避等)。对这两个TCP而言,没有一种单一的、正确的方法来交换给定数量的数据。

为显示情况可能怎样变化,图20-2显示了在同样两个主机之间交换同样数据时的另一个时间系列,它们是在图20-1所示的几分钟之后截获的。

一些情况发生了变化。这一次接收方没有发送一个序号为3073的ACK,而是等待并发送序号为4097的ACK。接收方仅发送了4个ACK(报文段7、10、12和15):三个确认了2 0 4 8字节,另一个确认了1024字节。最后1024字节数据的ACK出现在报文段17中,它与FIN的ACK一道发送(比较该图中的报文段17与图20-1中的报文段16和18)。

第20章 TCP的成块数据流211

图20-2 从svr4bsdi的另外8192字节数据的传输过程

快的发送方和慢的接收方

图20-3显示了另外一个时间系列。这次是从一个快的发送方(一个Sparc工作站)到一个慢的接收方(配有慢速以太网卡的80386机器)。它的动态活动情况又有所不同。

发送方发送4个背靠背(back-to-back)的数据报文段去填充接收方的窗口,然后停下来等待一个ACK。接收方发送ACK(报文段8),但通告其窗口大小为0,这说明接收方已收到所有数据,但这些数据都在接收方的TCP缓冲区,因为应用程序还没有机会读取这些数据。另一个ACK(称为窗口更新)在17.4ms后发送,表明接收方现在可以接收另外的4096个字节的数据。虽然这看起来像一个ACK,但由于它并不确认任何新数据,只是用来增加窗口的右边沿,因此被称为窗口更新。

发送方发送最后4个报文段(10~13),再次填充了接收方的窗口。注意到报文段13中包括两个比特标志:PUSH和FIN。随后从接收方传来另外两个ACK,它们确认了最后的4096字节的数据(从4097到8192字节)和FIN(标号为8192)。

212TCP/IP详解,卷1:协议

图20-3 从一个快发送方发送8192字节的数据到一个慢接收方

20.3 滑动窗口

图20-4用可视化的方法显示了我们在前一节观察到的滑动窗口协议。

图20-4 TCP滑动窗口的可视化表示

在这个图中,我们将字节从1至11进行标号。接收方通告的窗口称为提出的窗口(offered window),它覆盖了从第4字节到第9字节的区域,表明接收方已经确认了包括第3字节在内的数据,且通告窗口大小为6。回顾第17章,我们知道窗口大小是与确认序号相对应的。发送方计算它的可用窗口,该窗口表明多少数据可以立即被发送。

当接收方确认数据后,这个滑动窗口不时地向右移动。窗口两个边沿的相对运动增加或减少了窗口的大小。我们使用三个术语来描述窗口左右边沿的运动:

第20章 TCP的成块数据流213

图20-5 窗口边沿的移动

如果左边沿到达右边沿,则称其为一个零窗口,此时发送方不能够发送任何数据。

一个例子

图20-6显示了在图20-1所示的数据传输过程中滑动窗口协议的动态性。

图20-6 图20-1的滑动窗口协议

以该图为例可以总结如下几点:

  1. 发送方不必发送一个全窗口大小的数据。
  2. 来自接收方的一个报文段确认数据并把窗口向右边滑动。这是因为窗口的大小是相对于确认序号的。
  3. 正如从报文段7到报文段8中变化的那样,窗口的大小可以减小,但是窗口的右边沿却不能够向左移动。
  4. 接收方在发送一个ACK前不必等待窗口被填满。在前面我们看到许多实现每收到两个报文段就会发送一个ACK。

214TCP/IP详解,卷1:协议

图20-7 接收方提供一个6144字节的接收窗口的情况下的数据传输

20.5 PUSH标志

在每一个TCP例子中,我们都看到了PUSH标志,但一直没有介绍它的用途。发送方使用该标志通知接收方将所收到的数据全部提交给接收进程。这里的数据包括与PUSH一起传送的数据以及接收方TCP已经为接收进程收到的其他数据。

在最初的TCP规范中,一般假定编程接口允许发送进程告诉它的TCP何时设置PUSH标志。例如,在一个交互程序中,当客户发送一个命令给服务器时,它设置PUSH标志并停下来等待服务器的响应(在习题19.1中我们假定当发送12字节的请求时客户设置PUSH标志)。通过允许客户应用程序通知其TCP设置PUSH标志,客户进程通知TCP在向服务器发送一个报文段时不要因等待额外数据而使已提交数据在缓存中滞留。类似地,当服务器的TCP接收到一个设置了PUSH标志的报文段时,它需要立即将这些数据递交给服务器进程而不能等待判断是否还会有额外的数据到达。

然而,目前大多数的API没有向应用程序提供通知其TCP设置PUSH标志的方法。的确,许多实现程序认为PUSH标志已经过时,一个好的TCP实现能够自行决定何时设置这个标志。

如果待发送数据将清空发送缓存,则大多数的源于伯克利的实现能够自动设置PUSH标志。这意味着我们能够观察到每个应用程序写的数据均被设置了PUSH标志,因为数据在写的时候就立即被发送。

216TCP/IP详解,卷1:协议

图20-8 慢启动的例子

我们观察到发送方发送一个长度为512字节的报文段,然后等待ACK。该ACK在716 ms后收到。这个时间是一个往返时间的指示。于是拥塞窗口增加了2个报文段,且又发送了两个报文段。当收到报文段5的ACK后,拥塞窗口增加为3。此时尽管可发送多达3个报文段,可是在下一个ACK收到之前,只发送了2个报文段。

在21.6节中我们将再次讨论慢启动,并介绍怎样采用另一种被称为“拥塞避免”的技术来作为通常的实现。

218TCP/IP详解,卷1:协议

图20-9 时间0~15的成块数据吞吐量举例

在时间0,发送方发送了一个报文段。由于发送方处于慢启动中(其拥塞窗口为1个报文段),因此在继续发送以前它必须等待该数据段的确认。

在时间1,2和3,报文段从左向右移动一个时间单元。在时间4接收方读取这个报文段并产生确认。经过时间5、6和7,ACK移动到左边的发送方。我们有了一个8个时间单元的往返时间RTT(Round-Trip Time)。

我们有意把ACK报文段画得比数据报文段小,这是因为它通常只有一个IP首部和一个TCP首部。这里显示仅仅是一个单向的数据流动,并且假定ACK的移动速率与数据报文段的移动速率相等。实际上并不总是这样。

第20章 TCP的成块数据流219

图20-10 时间16~31的成块数据吞吐量举例

220TCP/IP详解,卷1:协议

在图20-12的下部,假定网络速率已经加倍,使得我们能够只使用上面一半的时间来发送4个报文段。这样,该管道的容量再次加倍(假定该图的上半部分与下半部分中的报文段具有同样大小,即具有相同的比特数)。

20.7.2 拥塞

当数据到达一个大的管道(如一个快速局域网)并向一个较小的管道(如一个较慢的广域网)发送时便会发生拥塞。当多个输入流到达一个路由器,而路由器的输出流小于这些输入流的总和时也会发生拥塞。

第20章 TCP的成块数据流221

图20-13 从较大管道向较小管道发送分组引起的拥塞

在该图中,我们已经标记路由器R1为“瓶颈”,因为它是拥塞发生的地方。它从左侧速率较高的局域网接收数据并向右侧速率较低的广域网发送(通常R1与R3是同样的路由器,如同R2与R4一样。但这并不是必需的,有时也会使用不对称的路径)。当路由器R2将所接收到的分组发送到右侧的局域网时,这些分组之间维持与其左侧广域网上同样的间隔,尽管局域网具有更高的带宽。类似地,返回的确认之间的间隔也与其在路径中最慢的链路上的间隔一致。

在图20-13中已经假定发送方不使用慢启动,它按照局域网的带宽尽可能快地发送编号为1~20的报文段(假定接收方的通告窗口至少为20个报文段)。正如我们看到的那样,ACK之间的间隔与在最慢链路上的一致。假定瓶颈路由器具有足够的容纳这20个分组的缓存。如果这个不能保证,就会引起路由器丢弃分组。在21.6节讨论避免拥塞时会看到怎样避免这种情况。

20.8 紧急方式

TCP提供了“紧急方式(urgent mode)”,它使一端可以告诉另一端有些具有某种方式的“紧急数据”已经放置在普通的数据流中。另一端被通知这个紧急数据已被放置在普通数据流中,由接收方决定如何处理。

可以通过设置TCP首部(图17-2)中的两个字段来发出这种从一端到另一端的紧急数据已经被放置在数据流中的通知。URG比特被置1,并且一个16bit的紧急指针被置为一个正的偏移量,该偏移量必须与TCP首部中的序号字段相加,以便得出紧急数据的最后一个字节的序号。

仍有许多关于紧急指针是指向紧急数据的最后一个字节还是指向紧急数据最后一个字节的下一个字节的争论。最初的TCP规范给出了两种解释,但Host RequirementsRFC确定指向最后一个字节是正确的。

然而,问题在于大多数的实现(包括源自伯克利的实现)继续使用错误的解释。所有符合Host Requirements RFC的实现都是可兼容的,但很有可能无法与其他大多数主机正确通信。

222TCP/IP详解,卷1:协议

我们设置发送缓存为8192个字节,以便让发送应用程序能够立即写所有的数据。图20-14显示了tcpdump输出的这个交换过程的结果(删去了连接建立的过程)。第1~5行表示发送方用4个1024字节的报文段去填充接收方的窗口。然后由于接收方的窗口被填满(第4行的ACK确认了数据,但并没有移动窗口的右边沿),所以发送方停止发送。

在写了第4个正常数据之后,应用进程写了1个字节并进入紧急方式。第6行是该应用进程写的结果,紧急指针被设置为4098。尽管发送方不能发送任何数据,但紧急指针和URG标志一起被发送。

5个这样的ACK在13 ms内被发送(第6~10行)。第1个ACK在应用进程写1个字节并进入紧急方式时被发送,后面两个在应用进程写最后两个1024字节的数据时被发送(尽管TCP不能发送这2048个字节的数据,可每次当应用程序执行写操作的时候,TCP的输出功能被调用。当TCP看到正处于紧急方式时,它会发送其他的紧急通知)。第4个ACK在应用进程关闭其TCP连接时被发送(TCP的输出功能再次被调用)。发送应用程序在启动几毫秒后终止—在接收方应用进程已经发出其第一个写操作之前。TCP将所有的数据进行排队,并在可能时发送出去(这就是为何指明发送缓存为8192字节的原因,因此只有这样才能够把所有的数据都放置在缓存中)。第5个ACK很可能是在接收第4行的ACK时产生的。发送TCP很可能在这个ACK到达前便已将其第4个报文段放入队列以便输出(第5行)。另一端接收到这个ACK也会引起TCP输出例程被调用。

图20-14 tcpdump对TCP紧急方式的输出结果

224TCP/IP详解,卷1:协议

图20-15 紧急方式例子中,应用进程的写操作和TCP的一些报文段

该图还可以让我们观察TCP是如何对应用进程写的数据进行重新分组化的。当进入紧急方式时待输出的1个字节是与在缓存中的后面1023个字节一同发送的。下一个报文段也包含1024字节的数据,而最后一个报文段则只包含一个字节。

20.9 小结

正如我们在本章一开始时讲的那样,没有一种单一的方法可以使用TCP进行成块数据的交换。这是一个依赖于许多因素的动态处理过程,有些因素我们可以控制(如发送和接收缓存的大小),而另一些我们则没有办法控制(如网络拥塞、与实现有关的特性等)。在本章,我们已经考察了许多TCP的传输过程,介绍了所有我们能够看到的特点和算法。

进行成块数据有效传输的最重要的方法是TCP的滑动窗口协议。我们考察了TCP为使发送方和接收方之间的管道充满来获得最可能快的传输速度而采用的方法。我们用带宽时延乘积衡量管道的容量,并分析了该乘积与窗口大小之间的关系。在24.8节介绍TCP性能的时候将再次涉及这个概念。

我们还介绍了TCP的PUSH标志,因为在跟踪结果中总是观察到它,但我们无法对它的设置与否进行控制。本章最后一个主题是TCP的紧急数据,人们常常错误地称其为“带外数据”。TCP的紧急方式只是一个从发送方到接收方的通知,该通知告诉接收方紧急数据已被发送,并提供该数据最后一个字节的序号。应用程序使用的有关紧急数据部分的编程接口常常都不是最佳的,从而导致更多的混乱。

第20章 TCP的成块数据流225

习题

  1. 在图20-6中,我们可以看到一个序号为0的字节和一个序号为8193的字节,试问这两个字节的含义是什么?
  2. 提前观察图22-1,并解释主机bsdi设置PUSH标志的含义。
  3. 在一个Usenet记录中,有人抱怨说美国和日本之间的一个128 ms时延、速率为256 000 b/s的链路吞吐量为120 000 b/s(利用率为47%),而当链路通过卫星时其吞吐量则为33 000 b/s(利用率为13%)。试问在这两种情况下窗口大小各为多少(假定卫星链路的时延为500 ms)?卫星链路的窗口大小应该如何调整?
  4. 如果API提供一种方法,使得发送方可以告诉其TCP打开PUSH标志,而接收方可以查询一个接收的报文段是否被设置了PUSH标志,试问该标志能否被用作一个记录标记?
  5. 在图20-3中为什么没有合并报文段15和16?
  6. 在图20-13中,我们假定对应数据报文段之间的间隔,返回的ACK之间的间隔被分隔得很好。如果在链路某处进行缓存并使许多ACK同时到达发送方,试问会发生什么情况?











内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: