UP | HOME

TCP:成块数据流

Table of Contents

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

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

滑动窗口协议

正常数据流

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

bsdi$ sock -i -s 7777

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

svr4$ sock -i -n8 bsdi 7777

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

tcp-sink-sock-sequences.png

  • 报文段4~6:发送方首先传送3个数据报文段
  • 报文段:仅确认了前两个数据报文段
    • 这可以从其确认序号为2048而不是3073看出:
      1. 当一个分组到达时,它首先被设备中断例程进行处理,然后放置到IP的输入队列中。三个报文段4、5和6依次到达并按接收顺序放到IP的输入队列。IP将按同样顺序将它们交给TCP
      2. 当TCP处理报文段4时,该连接被标记为产生一个经受时延的确认
      3. TCP处理下一报文段5,由于TCP现在有两个未完成的报文段需要确认,因此产生一个序号为2048的ACK(报文段7),并清除该连接产生经受时延的确认标志
      4. TCP处理下一个报文段(6),而连接又被标志为产生一个经受时延的确认
      5. 在报文段9到来之前,由于时延定时器溢出,因此产生一个序号为3073的ACK(报文段8)
      6. 报文段8中的窗口大小为3072,表明在TCP的接收缓存中还有1024个字节的数据等待被应用程序读取
  • 报文段11~16:说明了通常使用的 隔一个报文段确认 的策略
    • 报文段11、12和13到达并被放入IP的接收队列:
      • 当报文段11被处理时,连接被标记为产生一个经受时延的确认
      • 当报文段12被处理时,它们的ACK(报文段14)被产生且连接的经受时延的确认标志被清除
      • 报文段13使得连接再次被标记为产生经受时延
      • 但在时延定时器溢出之前,报文段15处理完毕,因此该确认立刻被发送
  报文段7、14和16中的ACK确认了两个收到的报文段是很重要的

  使用TCP的滑动窗口协议时,接收方不必确认每一个收到的分组

  在TCP中,ACK是累积的:它们表示接收方已经正确收到了一直到确认序号减1的所有字节

  在本例中,三个确认的数据为2048字节而两个确认的数据为1024字节

用tcpdump看到的是TCP的动态活动情况。在线路上看到的分组顺序依赖于许多无法控制的因素:

  • 发送方TCP的实现
  • 接收方TCP的实现
  • 接收进程读取数据:依赖于操作系统的调度
  • 网络的动态性:如以太网的冲突和退避等

对两个TCP而言: 没有一种单一的、正确的方法来交换给定数量的数据

快的接收方和慢的发送方

图20-2显示了在同样两个主机之间交换同样数据时的另一个时间系列,它们是在图20-1所示的几分钟之后截获的:

tcp-sink-sock-sequences-2.png

这一次接收方没有发送一个序号为3073的ACK,而是等待并发送序号为4097的ACK。接收方仅发送了4个ACK(报文段7、10、12和15):三个确认了2048字节,另一个确认了1024字节。最后1024字节数据的ACK出现在报文段17中,它与FIN的ACK一道发送

快的发送方和慢的接收方

图20-3显示了另外一个时间系列。这次是从一个快的发送方到一个慢的接收方。它的动态活动情况又有所不同:

tcp-sink-sock-sequences-3.png

  • 发送方发送4个背靠背的数据报文段去填充接收方的窗口,然后停下来等待一个ACK
  • 接收方发送ACK(报文段8),但通告其窗口大小为0,这说明接收方已收到所有数据,但这些数据都在接收方的TCP缓冲区,因为应用程序还没有机会读取这些数据
  • 另一个ACK在17.4ms后发送,表明接收方现在可以接收另外的4096个字节的数据。虽然这看起来像一个ACK,但由于它并不确认任何新数据,只是用来增加窗口的右边沿,因此被称为 窗口更新
  • 发送方发送最后4个报文段(10~13),再次填充了接收方的窗口。注意到报文段13中包括两个比特标志: PUSHFIN
  • 随后从接收方传来另外两个ACK,它们确认了最后的4096字节的数据(从4097到8192字节)和FIN(标号为8192)

滑动窗口

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

tcp-windows-protocol.png

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

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

  1. 窗口合拢 :窗口左边沿向右边沿靠近,这种现象发生在数据被发送和确认时
  2. 窗口张开 :当窗口右边沿向右移动时将允许发送更多的数据,这种现象发生在另一端的接收进程读取已经确认的数据并释放了TCP的接收缓存时
  3. 窗口收缩 :当右边沿向左移动时,RFC强烈建议不要使用这种方式,但TCP必须能够在某一端产生这种情况时进行处理

图20-5表示了这三种情况。因为窗口的左边沿受另一端发送的确认序号的控制,因此不可能向左边移动。如果接收到一个指示窗口左边沿向左移动的ACK,则它被认为是一个重复ACK,窗口边沿的移动并被丢弃:

tcp-window-move.png

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

实例

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

tcp-sink-sock-windows.png

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

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

窗口大小

由接收方提供的窗口的大小通常可以由接收进程控制,这将影响TCP的性能:

4.2BSD默认设置发送和接受缓冲区的大小为2048个字节。在4.3BSD中双方被增加为4096个字节

SunOS 4.1.3、BSD/386和SVR4仍然使用4096字节的默认大小

其他的系统,如Solaris 2.2、4.4BSD和AIX3.2则使用更大的默认缓存大小,如8192或16384等。

插口API允许进程设置发送和接收缓存的大小。接收缓存的大小是该连接上所能够通告的最大窗口大小

有一些应用程序通过修改插口缓存大小来增加性能。

研究显示在改变发送和接收缓存大小(在单向数据流的应用中,如文件传输,只需改变发送方的发送缓存和接收方的接收缓存大小)的情况下,例如位于以太网上的两个工作站之间进行文件传输时,对以太网而言,默认的4096字节并不是最理想的大小,将两个缓存增加到16384个字节可以增加约40%左右的吞吐量

实例

可以使用sock程序来控制这些缓存的大小。以如下方式调用服务器程序:

bsdi$ sock -i -s -R6144 5555

该命令使用 -R 选项设置接收缓存为6144个字节。接着在主机sun上启动客户程序并 -w 使之发送8192个字节的数据:

sun$ sock -i -n1 -w8192 bsdi 5555

图20-7显示了结果:

tcp-sink-sock-change-window-size.png

  • 报文段2中提供的窗口大小为6144字节
  • 报文段4~9:由于这是一个较大的窗口,因此客户立即连续发送了6个报文段,然后停止
  • 报文段10:确认了所有的数据(从第1到6144字节),但 提供的窗口大小却为2048 ,这很可能是接收程序没有机会读取多于2048字节的数据
  • 报文段11和12:完成了客户的数据传输,且最后一个报文段带有FIN标志
  • 报文段13:包含与报文段10相同的确认序号,但 通告了一个更大的窗口大小
  • 报文段14:确认了最后的2048字节的数据和FIN
  • 报文段15和16:仅用于 通告一个更大的窗口大小
  • 报文段17和18:完成通常的关闭过程

PUSH标志

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

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

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

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

   代码中的注释表明该算法对那些只有在缓存被填满或收到一个PUSH标志时才向应用程序提交数据的TCP实现有效
   
   使用socket API通知TCP设置正在接收数据的PUSH标志或得到该数据是否被设置PUSH标志的信息是不可能的

由于源于伯克利的实现一般从不将接收到的数据推迟交付给应用程序,因此它们忽略所接收的PUSH标志

实例

  • 在图20-1中观察到所有8个数据报文段(4~6、9、11~13和15)的PUSH标志均被置1,这是因为 客户进行了8次1024字节数据的写操作,并且每次写操作均清空了发送缓存
  • 观察图20-7,预计报文段12中的PUSH标志被置1,因为它是最后一个报文段

为什么发送方知道有更多的数据需要发送还设置报文段7中的PUSH标志呢?这是因为 虽然指定写的是8192个字节的数据,但发送方的发送缓存却是4096个字节

  • 在图20-7中的第14、15和16这三个连续的确认报文段。在图20-3中也观察到了两个连续的ACK,但那是因为接收方已经通告其窗口为0(使发送方停止)。 当窗口张开时,需要发送另一个窗口非0的ACK来使发送方重新启动 。可是在图20-7中,窗口的大小从来没有达到过0。然而,当窗口大小增加了2048个字节的时候,另一个ACK(报文段15和16)被发送以通知对方窗口被更新(在报文段15和16中,这两个窗口更新是不需要的,因为已经收到了对方的FIN,表明它不会再发送任何数据)
  许多TCP实现在窗口大小增加了两个最大报文段长度(本例中为2048字节,因为MSS为1024字节)

  或者最大可能窗口的50%(本例中为2048字节,因为最大窗口大小为4096字节)时发送这个窗口更新
  • 作为PUSH标志的另一个例子,再次回到图20-3。之所以看到前4个报文段(4~7)的标志被设置,是因为它们每一个均使TCP产生了一个报文段并提交给IP层。但是随后,TCP停下来等待一个确认来移动4096字节的窗口。在此期间,TCP又得到了应用程序的最后4096个字节的数据。当窗口张开时(报文段9),发送方TCP知道它有4个可立即发送的报文段,因此它 只设置了最后一个报文段(13)的PUSH标志

慢启动

迄今为止,在本章所有的例子中,发送方一开始便向网络发送多个报文段,直至达到接收方通告的窗口大小为止。当发送方和接收方处于同一个局域网时,这种方式是可以的。但是如果在发送方和接收方之间存在多个路由器和速率较慢的链路时,就有可能出现一些问题。 一些中间路由器必须缓存分组,并有可能耗尽存储器的空间 。研究证明了这种连接方式是 严重降低了TCP连接的吞吐量的

现在TCP支持一种被称为 慢启动 的算法。该算法通过观察到新分组进入网络的速率应该与另一端返回确认的速率相同而进行工作

慢启动为发送方的TCP增加了另一个窗口: 拥塞窗口 ,记为cwnd。当与另一个网络的主机建立TCP连接时,拥塞窗口被初始化为1个报文段(即另一端通告的报文段大小)。每收到一个ACK,拥塞窗口就增加一个报文段。发送方取拥塞窗口与通告窗口中的最小值作为发送上限。拥塞窗口是 发送方使用的流量控制 ,而通告窗口则是 接收方使用的流量控制

   拥塞窗口以字节为单位,但是慢启动以报文段大小为单位进行增加

发送方开始时发送一个报文段,然后等待ACK。当收到该ACK时,拥塞窗口从1增加为2,即可以发送两个报文段。当收到这两个报文段的ACK时,拥塞窗口就增加为4。这是一种指数增加的关系。

在某些点上可能达到了互联网的容量,于是中间路由器开始丢弃分组。这就通知发送方它的拥塞窗口开得过大

实例

现在,来观察一个实际中的慢启动。图20-8表示的是将从主机sun发送到主机vangogh.cs.berkeley.edu的数据。这些数据将通过一个慢的SLIP链路,该链路是TCP连接上的瓶颈:

tcp-congestion-window.png

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

成块数据的吞吐量

实例

首先看一看窗口大小、窗口流量控制以及慢启动对传输成块数据的TCP连接的吞吐量的相互作用

图20-9显示了左边的发送方和右边的接收方之间的一个TCP连接上的时间系列,共显示了16个时间单元。为简单起见,本图只显示离散的时间单元。每个粗箭头线的上半部分显示的是从左到右的携带数据的报文段,标记为1,2,3等等。在粗线箭头下面表示的是反向传输的ACK。把ACK用细箭头线表示,并标注了被确认的报文段号:

tcp-window-throughout-sample.png

  • 时间0:发送方发送了一个报文段。由于发送方处于慢启动中(其拥塞窗口为1个报文段),因此在继续发送以前它必须等待该数据段的确认
  • 时间1,2和3:报文段从左向右移动一个时间单元
  • 时间4:接收方读取这个报文段并产生确认
  • 时间5、6和7:ACK移动到左边的发送方
    • 有了一个8个时间单元的往返时间RTT
有意把ACK报文段画得比数据报文段小,这是因为它通常只有一个IP首部和一个TCP首部

这里显示仅仅是一个单向的数据流动,并且假定ACK的移动速率与数据报文段的移动速率相等。实际上并不总是这样,通常发送一个分组的时间取决于两个因素:
1. 传播时延:由光的有限速率、传输设备的等待时间等引起
2. 媒体速率:(即媒体每秒可传输的比特数)的发送时延

对于一个给定的两个接点之间的通路,传播时延一般是固定的,而发送时延则取决于分组的大小。在速率较慢的情况下发送时延起主要作用,而在千兆比特速率下传播时延则占主要地位
  • 时间8和9:当发送方收到ACK后,发送两个报文段(标记为2和3)
    • 此时它的拥塞窗口为2个报文段。这两个报文段向右传送到接收方
  • 在时间12和13:接收方产生两个ACK。这两个返回到发送方的ACK之间的间隔与报文段之间的间隔一致,被称为TCP的自计时行为
  由于接收方只有在数据到达时才产生ACK,因此发送方接收到的ACK之间的间隔与数据到达接收方的间隔是一致的
 
  然而在实际中,返回路径上的排队会改变ACK的到达率

图20-10表示的是后面16个时间单位:

tcp-window-throughout-sample-cont.png

2个ACK的到达使得拥塞窗口从2个报文段增加为4个,而这4个报文段在时间16~19时被发送。第1个ACK在时间23到达。4个ACK的到达使得拥塞窗口从4个报文段增加为8个,并在时间24~31发送8个报文段

在时间31及其后续时间,发送方和接收方之间的管道被填满。此时不论拥塞窗口和通告窗口是多少,它都不能再容纳更多的数据。每当接收方在某一个时间单位从网络上移去一个报文段,发送方就再发送一个报文段到网络上。但是不管有多少报文段填充了这个管道,返回路径上总是具有相同数目的ACK。这就是连接的理想稳定状态

带宽时延乘积

现在来回答窗口应该设置为多大的问题。在例子中,作为最大的吞吐量,发送方在任何时候有8个已发送的报文段未被确认。 接收方的通告窗口必须不小于这个数目,因为通告窗口限制了发送方能够发送的段的数目

可以计算通道的容量为:

capacity (bit) = bandwidth (b/s) × round-trip-time (s)

一般称之为 带宽时延乘积 。这个值依赖于网络速度和两端的RTT,可以有很大的变动。例如,一条穿越美国(RTT约为60ms)的T1的电话线路(1544000b/s)的带宽时延乘积为11580字节。对于20.4节中讨论的缓存大小而言,这个结果是合理的。但是一条穿越美国的T3电话线路(45000000b/s)的带宽时延乘积则为337500字节,这个数值超过了最大所允许的TCP通告窗口的大小(65535字节)。以后将讨论能够避免当前TCP限制的新的TCP窗口大小选项

T1电话线的1544000b/s是原始比特率。由于每193个bit使用1个作为帧同步,因此实际数据率为1536000b/s

一个T3电话线的原始比特率实际上是44736000b/s,其数据率可达到44210000b/s

在讨论中一般使用1.544Mb/s和45Mb/s

不论是带宽还是时延均会影响发送方和接收方之间通路的容量。在图20-11中显示了一个增加了一倍的RTT会使通路容量也增加一倍。底下的说明部分,通过使用一个较长的RTT,这个管道能够容纳8个报文段而不是4个.类似地图20-12表示了增加一倍的带宽也可使该管道的容量增加一倍:

tcp-throughout-large-bandwidth.png

拥塞

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

图20-13显示了一个典型的大管道向小管道发送报文的情况:

tcp-throughout-congestion.png

之所以说它典型,是因为 大多数的主机都连接在局域网上,并通过一个路由器与速率相对较低的广域网相连

在该图中,已经标记路由器R1为 瓶颈 ,因为它是拥塞发生的地方。它从左侧速率较高的局域网接收数据并向右侧速率较低的广域网发送

   通常R1与R3是同样的路由器,如同R2与R4一样。但这并不是必需的,有时也会使用不对称的路径

当路由器R2将所接收到的分组发送到右侧的局域网时,这些分组之间维持与其左侧广域网上同样的间隔,尽管局域网具有更高的带宽。类似地,返回的确认之间的间隔也与其在路径中最慢的链路上的间隔一致

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

紧急方式

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

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

  仍有许多关于紧急指针是指向紧急数据的最后一个字节还是指向紧急数据最后一个字节的下一个字节的争论
  
  最初的TCP规范给出了两种解释,但RFC确定指向最后一个字节是正确的
  
  问题在于大多数的实现继续使用错误的解释,所有符合FC的实现都是可兼容的,但很有可能无法与其他大多数主机正确通信

过程

  1. TCP必须通知接收进程:
    • 何时已接收到一个紧急数据指针
    • 何时某个紧急数据指针还不在此连接上
    • 紧急指针是否在数据流中向前移动
  2. 接收进程可以读取数据流,并必须能够被告知何时碰到了紧急数据指针
  3. 只要从接收方当前读取位置到紧急数据指针之间有数据存在,就认为应用程序处于 紧急方式
  4. 在紧急指针通过之后,应用程序便转回到正常方式

TCP本身对紧急数据知之甚少。没有办法指明紧急数据从数据流的何处开始。TCP通过连接传送的唯一信息就是:

  • 紧急方式已经开始:TCP首部中的URG比特
  • 指向紧急数据最后一个字节的指针

其他的事情留给应用程序去处理

带外数据

不幸的是,许多实现不正确地称TCP的紧急方式为 带外数据 。如果一个应用程序确实需要一个独立的带外信道,第二个TCP连接是达到这个目的的最简单的方法

许多运输层确实提供许多人认为的那种真正的带外数据:使用同一个连接的独立的逻辑数据通道作为正常的数据通道,但TCP并没有提供

TCP的紧急方式与带外数据之间的混淆,也是因为主要的编程接口(socket API)将TCP的紧急方式映射为称为带外数据的插口

作用

两个最常见的例子是telnet和rlogin。当交互用户键入中断键时,使用紧急方式来完成这个功能的例子。另一个例子是ftp,当交互用户放弃一个文件的传输时

telnet和rlogin从服务器到客户使用紧急方式是因为 在这个方向上的数据流很可能要被客户的TCP停止 (它通告了一个大小为0的窗口)。但是如果服务器进程进入了紧急方式,尽管它不能够发送任何数据,服务器TCP也会立即发送紧急指针和URG标志。当客户TCP接收到这个通知时就会通知客户进程,于是 客户可以从服务器读取其输入、打开窗口并使数据流动

如果在接收方处理第一个紧急指针之前,发送方多次进入紧急方式会发生什么情况呢?在数据流中的紧急指针会向前移动,而其在接收方的前一个位置将丢失。 接收方只有一个紧急指针,每当对方有新的值到达时它将被覆盖 。这意味着如果发送方进入紧急方式时所写的内容对接收方非常重要,那么 这些字节数据必须被发送方用某种方式特别标记 。将看到telnet通过 在数据流中加入一个值为255的字节作为前缀 来标记它所有的命令

实例

观察一下即使是在接收方窗口关闭的情况下,TCP是如何发送紧急数据的。在主机bsdi上启动sock程序,并使之在连接建立后和从网络读取前暂停10秒种(通过使用 -P 选项),这将使另一端填满发送窗口:

bsdi$ sock -i -s -P10 5555

接着在主机sun上启动客户,使之使用一个8192字节的发送缓存(使用 -S 选项)并进行6个向网络写1024字节数据的操作(使用 -n 选项)。还指明 -U5 选项,告知它向网络写第5个缓存之前要写1个字节的数据,并进入紧急数据方式。通过指明详细标志来观察写的顺序:

tcp-sock-urgent.png

设置发送缓存为8192个字节,以便让发送应用程序能够立即写所有的数据。图20-14显示了tcpdump输出的这个交换过程的结果:

tcp-sock-urgent-dump.png

  • 第1~5行:发送方用4个1024字节的报文段去填充接收方的窗口
    • 第4行:由于接收方的窗口被填满(ACK确认了数据,但并没有移动窗口的右边沿),所以发送方停止发送
  • 第6~10行:在写了第4个正常数据之后,应用进程写了1个字节并进入紧急方式
    • 紧急指针被设置为4098。尽管发送方不能发送任何数据,但 紧急指针和URG标志一起被发送
    • 5个这样的ACK在13ms内被发送
      • 第1个ACK:在应用进程 写1个字节并进入紧急方式时被发送
      • 第2~3个ACK:在应用进程写最后两个1024字节的数据时分别被发送 尽管TCP不能发送这2048个字节的数据,可每次当应用程序执行写操作的时候,TCP的输出功能被调用。当TCP看到正处于紧急方式时,它会发送其他的紧急通知
      • 第4个ACK:在应用进程 关闭其TCP连接时被发送,TCP的输出功能再次被调用
        • 发送应用程序在启动几毫秒后终止,在接收方应用进程已经发出其第一个写操作之前
        • TCP将所有的数据进行排队,并在可能时发送出去,这就是为何指明发送缓存为8192字节的原因,因此只有这样才能够把所有的数据都放置在缓存中
      • 第5个ACK:很可能是在接收第4行的ACK时产生的。发送TCP很可能在这个ACK到达前便已将其第4个报文段放入队列以便输出(第5行) 另一端接收到这个ACK也会引起TCP输出例程被调用
  • 第11行:接收方确认最后的1024字节的数据,但同时通告窗口为0
  • 第12行:发送方用一个包含紧急通知的报文段进行了响应
  • 第13行:当应用进程被唤醒、并从接收缓存读取一些数据时,接收方通告窗口为2048字节
  • 第14~15行:后面又发送了两个1024字节的报文段
    • 由于紧急指针在第1个报文段的范围内,因此这个报文段被设置了紧急通知标志
    • 而第2个报文段则关闭了该标志
  • 第16行:当接收方再次打开窗口时,发送方传输最后的数据(序号为6145)并发起正常的连接关闭

图20-15显示了发送的6145个字节数据的序号。可以看到当进入紧急方式时所发送的字节的序号是4097,但在图20-14中紧急指针指向4098,这证明了该实现(SunOS 4.1.3)将紧急指针设置为紧急数据最后字节的下一个字节:

tcp-urgent-point.png

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

Next:TCP 超时和重传

Previous:TCP 交互数据流

Home:目录