相关博客:https://blog.csdn.net/knowledgebao/article/details/84626184
目录
![](/qrcode.jpg)
第20章 TCP的成块数据流
20.1 引言
在第1 5章我们看到T F T P使用了停止等待协议。数据发送方在发送下一个数据块之前需要 等待接收对已发送数据的确认。本章我们将介绍 T C P所使用的被称为滑动窗口协议的另一种 形式的流量控制方法。该协议允许发送方在停止并等待确认前可以连续发送多个分组。由于 发送方不必每发一个分组就停下来等待确认,因此该协议可以加速数据的传输。
我们还将介绍T C P的P U S H标志,该标志在前面的许多例子中都出现过。此外,我们还要 介绍慢启动,T C P使用该技术在一个连接上建立数据流,最后介绍成块数据流的吞吐量。
20.2 正常数据流
我们以从主机s v r 4单向传输8 1 9 2个字节到主机b s d i开始。在b s d i上运行s o c k程序作 为服务器:
bsdi % sock -i -s 7777
其中,标志- i和- s指示程序作为一个“吸收(s i n k)”服务器运行(从网络上读取并丢弃 数据),服务器端口指明为7 7 7 7。相应的客户程序运行为:
svr4 % sock -i -n8 bsdi 7777
该命令指示客户向网络发送 8个1 0 2 4字节的数据。图2 0 - 1显示了这个过程的时间系列。我 们在输出的前3个报文段中显示了每一端M S S的值。
发送方首先传送3个数据报文段(4 ~ 6)。下一个报文段(7)仅确认了前两个数据报文段, 这可以从其确认序号为2 0 4 8而不是3 0 7 3看出来。
报文段7的A C K的序号之所以是2 0 4 8而不是3 0 7 3是由以下原因造成的:当一个分组到达时, 它首先被设备中断例程进行处理,然后放置到 I P的输入队列中。三个报文段 4、5和6依次到达 并按接收顺序放到 I P的输入队列。I P将按同样顺序将它们交给 T C P。当T C P处理报文段4时, 该连接被标记为产生一个经受时延的确认。 T C P处理下一报文段(5),由于T C P现在有两个未 完成的报文段需要确认,因此产生一个序号为 2 0 4 8的A C K(报文段7),并清除该连接产生经 受时延的确认标志。 T C P处理下一个报文段( 6),而连接又被标志为产生一个经受时延的确 认。在报文段9到来之前,由于时延定时器溢出,因此产生一个序号为 3 0 7 3的A C K(报文段8)。 报文段8中的窗口大小为3 0 7 2,表明在T C P的接收缓存中还有1 0 2 4个字节的数据等待被应用程 序读取。
报文段11 ~ 1 6说明了通常使用的“隔一个报文段确认”的策略。报文段 11、1 2和1 3到达并 被放入I P的接收队列。当报文段 11被处理时,连接被标记为产生一个经受时延的确认。当报 文段1 2被处理时,它们的A C K(报文段1 4)被产生且连接的经受时延的确认标志被清除。报 文段1 3使得连接再次被标记为产生经受时延。但在时延定时器溢出之前,报文段 1 5处理完毕, 因此该确认立刻被发送。
注意到报文段7、1 4和1 6中的A C K确认了两个收到的报文段是很重要的。使用 T C P的滑动 窗口协议时,接收方不必确认每一个收到的分组。在 T C P中,A C K是累积的—它们表示接 收方已经正确收到了一直到确认序号减 1的所有字节。在本例中,三个确认的数据为 2 0 4 8字节 而两个确认的数据为1 0 2 4字节(忽略了连接建立和终止中的确认)。
用t c p d u m p看到的是T C P的动态活动情况。我们在线路上看到的分组顺序依赖于许多无 法控制的因素:发送方T C P的实现、接收方T C P的实现、接收进程读取数据(依赖于操作系统 的调度)和网络的动态性(如以太网的冲突和退避等)。对这两个T C P而言,没有一种单一的、 正确的方法来交换给定数量的数据。
为显示情况可能怎样变化,图 2 0 - 2显示了在同样两个主机之间交换同样数据时的另一个 时间系列,它们是在图2 0 - 1所示的几分钟之后截获的。
一些情况发生了变化。这一次接收方没有发送一个序号为 3 0 7 3的A C K,而是等待并发送 序号为4 0 9 7的A C K。接收方仅发送了4个A C K(报文段7、1 0、1 2和1 5):三个确认了2 0 4 8字
节,另一个确认了1 0 2 4字节。最后1 0 2 4字节数据的A C K出现在报文段1 7中,它与F I N的A C K 一道发送(比较该图中的报文段1 7与图2 0 - 1中的报文段1 6和1 8)。
快的发送方和慢的接收方
图2 0 - 3显示了另外一个时间系列。这次是从一个快的发送方(一个 S p a r c工作站)到一个 慢的接收方(配有慢速以太网卡的 8 0 3 8 6机器)。它的动态活动情况又有所不同。
发送方发送4个背靠背(b a c k - t o - b a c k)的数据报文段去填充接收方的窗口,然后停下来 等待一个A C K。接收方发送A C K(报文段8),但通告其窗口大小为 0,这说明接收方已收到 所有数据,但这些数据都在接收方的 T C P缓冲区,因为应用程序还没有机会读取这些数据。 另一个A C K(称为窗口更新)在17.4 ms后发送,表明接收方现在可以接收另外的 4 0 9 6个字节 的数据。虽然这看起来像一个 A C K,但由于它并不确认任何新数据,只是用来增加窗口的右 边沿,因此被称为窗口更新。
发送方发送最后4个报文段(1 0 ~ 1 3),再次填充了接收方的窗口。注意到报文段 1 3中包括 两个比特标志:P U S H和F I N。随后从接收方传来另外两个 A C K,它们确认了最后的4 0 9 6字节 的数据(从4 0 9 7到8 1 9 2字节)和F I N(标号为8 1 9 2)。
20.3 滑动窗口
图2 0 - 4用可视化的方法显示了我们在前一节观察到的滑动窗口协议。
在这个图中,我们将字节从 1至11进行标号。接收方通告的窗口称为提出的窗口( o ff e r e d w i n d o w),它覆盖了从第4字节到第9字节的区域,表明接收方已经确认了包括第 3字节在内的 数据,且通告窗口大小为6。回顾第1 7章,我们知道窗口大小是与确认序号相对应的。发送方 计算它的可用窗口,该窗口表明多少数据可以立即被发送。
当接收方确认数据后,这个滑动窗口不时地向右移动。窗口两个边沿的相对运动增加或 减少了窗口的大小。我们使用三个术语来描述窗口左右边沿的运动:
1) 称窗口左边沿向右边沿靠近为窗口合拢。这种现象发生在数据被发送和确认时。
2) 当窗口右边沿向右移动时将允许发送更多的数据,我们称之为窗口张开。这种现象发 生在另一端的接收进程读取已经确认的数据并释放了 T C P的接收缓存时。
3) 当右边沿向左移动时,我们称之为窗口收缩。 Host Requirements RFC强烈建议不要使 用这种方式。但T C P必须能够在某一端产生这种情况时进行处理。第 2 2 . 3节给出了这样的一个 例子,一端希望向左移动右边沿来收缩窗口,但没能够这样做。
图2 0 - 5表示了这三种情况。因为窗口的左边 沿受另一端发送的确认序号的控制,因此不可能 向左边移动。如果接收到一个指示窗口左边沿向 左移动的 A C K,则它被认为是一个重复 A C K, 并被丢弃。
如果左边沿到达右边沿,则称其为一个零窗口,此时发送方不能够发送任何数据。
一个例子
图2 0 - 6显示了在图2 0 - 1所示的数据传输过程中滑动窗口协议的动态性。
以该图为例可以总结如下几点:
- 发送方不必发送一个全窗口大小的数据。
- 来自接收方的一个报文段确认数据并把窗口向右边滑动。这是因为窗口的大小是相对 于确认序号的。
- 正如从报文段7到报文段8中变化的那样,窗口的大小可以减小,但是窗口的右边沿却 不能够向左移动。
- 接收方在发送一个A C K前不必等待窗口被填满。在前面我们看到许多实现每收到两个 报文段就会发送一个A C K。 下面我们可以看到更多的滑动窗口协议动态变化的例子。
20.4 窗口大小
由接收方提供的窗口的大小通常可以由接收进程控制,这将影响 T C P的性能。
4 . 2 B S D默认设置发送和接受缓冲区的大小为2 0 4 8个字节。在4 . 3 B S D中双方被增加 为4 0 9 6个字节。正如我们在本书中迄今为止所看到的例子一样, SunOS 4.1.3、 B S D / 3 8 6和S V R 4仍然使用4 0 9 6字节的默认大小。其他的系统,如Solaris 2.2、4 . 4 B S D和 AIX3.2则使用更大的默认缓存大小,如8192或16384等。
插口A P I允许进程设置发送和接收缓存的大小。接收缓存的大小是该连接上所能 够通告的最大窗口大小。有一些应用程序通过修改插口缓存大小来增加性能。
[Mogul 1993]显示了在改变发送和接收缓存大小(在单向数据流的应用中,如文件传输, 只需改变发送方的发送缓存和接收方的接收缓存大小)的情况下,位于以太网上的两个工作 站之间进行文件传输时的一些结果。它表明对以太网而言,默认的 4 0 9 6字节并不是最理想的 大小,将两个缓存增加到 1 6 3 8 4个字节可以增加约 4 0 %左右的吞吐量。在 [ P a p a d o p o u l o s和 Parulkar 1993]中也有相似的结果。
在2 0 . 7节中,我们将看到在给定通信媒体带宽和两端往返时间的情况下,如何计算最小的 缓存大小。
一个例子
可以使用s o c k程序来控制这些缓存的大小。我们以如下方式调用服务器程序:
bsdi % sock -i -s -R6144 5555
该命令设置接收缓存为6 1 4 4个字节(- R选项)。接着我们在主机s u n上启动客户程序并使 之发送8 1 9 2个字节的数据:
sun % sock -i -n1 -w8192 bsdi 5555
图2 0 - 7显示了结果。
首先注意到的是在报文段 2中提供的窗口大小为6 1 4 4字节。由于这是一个较大的窗口,因 此客户立即连续发送了 6个报文段(4 ~ 9),然后停止。报文段1 0确认了所有的数据(从第 1到 6 1 4 4字节),但提供的窗口大小却为2 0 4 8,这很可能是接收程序没有机会读取多于 2 0 4 8字节的 数据。报文段11和1 2完成了客户的数据传输,且最后一个报文段带有 F I N标志。
报文段1 3包含与报文段1 0相同的确认序号,但通告了一个更大的窗口大小。报文段 1 4确 认了最后的2 0 4 8字节的数据和F I N,报文段1 5和1 6仅用于通告一个更大的窗口大小。报文段 1 7和1 8完成通常的关闭过程。
20.5 PUSH标志
在每一个T C P例子中,我们都看到了P U S H标志,但一直没有介绍它的用途。发送方使用 该标志通知接收方将所收到的数据全部提交给接收进程。这里的数据包括与 P U S H一起传送的 数据以及接收方T C P已经为接收进程收到的其他数据。
在最初的T C P规范中,一般假定编程接口允许发送进程告诉它的 T C P何时设置P U S H标志。 例如,在一个交互程序中,当客户发送一个命令给服务器时,它设置 P U S H标志并停下来等待 服务器的响应(在习题 1 9 . 1中我们假定当发送 1 2字节的请求时客户设置 P U S H标志)。通过允 许客户应用程序通知其T C P设置P U S H标志,客户进程通知T C P在向服务器发送一个报文段时 不要因等待额外数据而使已提交数据在缓存中滞留。类似地,当服务器的 T C P接收到一个设 置了P U S H标志的报文段时,它需要立即将这些数据递交给服务器进程而不能等待判断是否还 会有额外的数据到达。
然而,目前大多数的 A P I没有向应用程序提供通知其 T C P设置P U S H标志的方法。的确, 许多实现程序认为P U S H标志已经过时,一个好的T C P实现能够自行决定何时设置这个标志。
如果待发送数据将清空发送缓存,则大多数的源于伯克利的实现能够自动设置 P U S H标志。 这意味着我们能够观察到每个应用程序写的数据均被设置了 P U S H标志,因为数据在写的时候就立即被发送。
代码中的注释表明该算法对那些只有在缓存被填满或收到一个P U S H标志时才向应 用程序提交数据的TCP实现有效。
使用插口A P I通知T C P设置正在接收数据的 P U S H标志或得到该数据是否被设置 PUSH标志的信息是不可能的。
由于源于伯克利的实现一般从不将接收到的数据推迟交付给应用程序,因此它们忽略所 接收的P U S H标志。
举例
在图2 0 - 1中我们观察到所有8个数据报文段(4 ~ 6、9、11 ~ 1 3和1 5)的P U S H标志均被置1, 这是因为客户进行了8次1 0 2 4字节数据的写操作,并且每次写操作均清空了发送缓存。
再次观察图2 0 - 7,我们预计报文段 1 2中的P U S H标志被置1,因为它是最后一个报文段。 为什么发送方知道有更多的数据需要发送还设置报文段 7中的P U S H标志呢?这是因为虽然我 们指定写的是8 1 9 2个字节的数据,但发送方的发送缓存却是 4 0 9 6个字节。
值得注意的另外一点是在图 2 0 - 7中的第1 4、1 5和1 6这三个连续的确认报文段。在图 2 0 - 3 中我们也观察到了两个连续的 A C K,但那是因为接收方已经通告其窗口为 0(使发送方停止)。 当窗口张开时,需要发送另一个窗口非 0的A C K来使发送方重新启动。可是,在图 2 0 - 7中,窗 口的大小从来没有达到过 0。然而,当窗口大小增加了 2 0 4 8个字节的时候,另一个 A C K (报文 段1 5和1 6 )被发送以通知对方窗口被更新(在报文段 1 5和1 6中,这两个窗口更新是不需要的, 因为已经收到了对方的 F I N,表明它不会再发送任何数据)。许多T C P实现在窗口大小增加了 两个最大报文段长度(本例中为 2 0 4 8字节,因为M S S为1 0 2 4字节)或者最大可能窗口的 5 0 % (本例中为2 0 4 8字节,因为最大窗口大小为 4 0 9 6字节)时发送这个窗口更新。在第 2 2 . 3节详细 考察糊涂窗口综合症的时候,我们还会看到这种现象。
作为P U S H标志的另一个例子,再次回到图 2 0 - 3。我们之所以看到前4个报文段(4 ~ 7)的 标志被设置,是因为它们每一个均使 T C P产生了一个报文段并提交给I P层。但是随后,T C P停 下来等待一个确认来移动 4 0 9 6字节的窗口。在此期间, T C P又得到了应用程序的最后 4 0 9 6个 字节的数据。当窗口张开时(报文段 9),发送方T C P知道它有4个可立即发送的报文段,因此 它只设置了最后一个报文段(1 3)的P U S H标志。
20.6 慢启动
迄今为止,在本章所有的例子中,发送方一开始便向网络发送多个报文段,直至达到接 收方通告的窗口大小为止。当发送方和接收方处于同一个局域网时,这种方式是可以的。但 是如果在发送方和接收方之间存在多个路由器和速率较慢的链路时,就有可能出现一些问题。 一些中间路由器必须缓存分组,并有可能耗尽存储器的空间。 [Jacobson 1988]证明了这种连 接方式是如何严重降低了T C P连接的吞吐量的。
现在,T C P需要支持一种被称为“慢启动(slow start)”的算法。该算法通过观察到新分组 进入网络的速率应该与另一端返回确认的速率相同而进行工作。
慢启动为发送方的T C P增加了另一个窗口:拥塞窗口(congestion window),记为c w n d。当与另一个网络的主机建立 T C P连接时,拥塞窗口被初始化为 1个报文段(即另一端通告的报文 段大小)。每收到一个A C K,拥塞窗口就增加一个报文段( c w n d以字节为单位,但是慢启动 以报文段大小为单位进行增加)。
发送方取拥塞窗口与通告窗口中的最小值作为发送上限。拥 塞窗口是发送方使用的流量控制,而通告窗口则是接收方使用的流量控制。 发送方开始时发送一个报文段,然后等待 A C K。当收到该A C K时,拥塞窗口从1增加为2, 即可以发送两个报文段。当收到这两个报文段的 A C K时,拥塞窗口就增加为4。这是一种指数 增加的关系。
在某些点上可能达到了互联网的容量,于是中间路由器开始丢弃分组。这就通知发送方 它的拥塞窗口开得过大。当我们在下一章讨论 T C P的超时和重传机制时,将会看到它们是怎 样对拥塞窗口起作用的。现在,我们来观察一个实际中的慢启动。
一个例子
图2 0 - 8表示的是将从主机s u n发送到主机v a n g o g h . c s . b e r k e l e y . e d u的数据。这些数据 将通过一个慢的SL I P 链路,该链路是TC P 连接上的瓶颈(我们已经在时间系列上去掉了连接建立 的过程)
我们观察到发送方发送一个长度为 5 1 2字节的报文段,然后等待A C K。该A C K在716 ms后 收到。这个时间是一个往返时间的指示。于是拥塞窗口增加了 2个报文段,且又发送了两个报 文段。当收到报文段5的A C K后,拥塞窗口增加为3。此时尽管可发送多达3个报文段,可是在 下一个A C K收到之前,只发送了2个报文段。
在2 1 . 6节中我们将再次讨论慢启动,并介绍怎样采用另一种被称为“拥塞避免”的技术来作为通常的实现。
20.7 成块数据的吞吐量
让我们看一看窗口大小、窗口流量控制以及慢启动对传输成块数据的 T C P连接的吞吐量 的相互作用。
图2 0 - 9显示了左边的发送方和右边的接收方之间的一个 T C P连接上的时间系列,共显示了 1 6个时间单元。为简单起见,本图只显示离散的时间单元。每个粗箭头线的上半部分显示的 是从左到右的携带数据的报文段,标记为 1, 2, 3, 等等。在粗线箭头下面表示的是反向传输的 A C K。我们把A C K用细箭头线表示,并标注了被确认的报文段号。
在时间0,发送方发送了一个报文段。由于发送方处于慢启动中(其拥塞窗口为 1个报文 段),因此在继续发送以前它必须等待该数据段的确认。
在时间1, 2和3,报文段从左向右移动一个时间单元。在时间 4接收方读取这个报文段并产 生确认。经过时间5、6和7,A C K移动到左边的发送方。我们有了一个 8个时间单元的往返时 间RT T(R o u n d - Trip Ti m e)。
我们有意把 A C K报文段画得比数据报文段小,这是因为它通常只有一个 I P首部和一个 T C P首部。这里显示仅仅是一个单向的数据流动,并且假定 A C K的移动速率与数据报文段的 移动速率相等。实际上并不总是这样。
通常发送一个分组的时间取决于两个因素:传播时延(由光的有限速率、传输设 备的等待时间等引起)和一个取决于媒体速率(即媒体每秒可传输的比特数)的发送 时延。对于一个给定的两个接点之间的通路,传播时延一般是固定的,而发送时延则 取决于分组的大小。在速率较慢的情况下发送时延起主要作用(例如,在习题 7 . 2中我 们甚至没有考虑传播时延),而在千兆比特速率下传播时延则占主要地位(见图24-6)。
当发送方收到A C K后,在时间8和9发送两个报文段(我们标记为2和3)。此时它的拥塞窗口 为2个报文段。这两个报文段向右传送到接收方,在时间1 2和1 3接收方产生两个A C K。这两个返 回到发送方的A C K之间的间隔与报文段之间的间隔一致,被称为 T C P的自计时( s e l f - c l o c k i n g )行 为。由于接收方只有在数据到达时才产生A C K,因此发送方接收到的A C K之间的间隔与数据到 达接收方的间隔是一致的(然而在实际中,返回路径上的排队会改变ACK的到达率)。
图2 0 - 1 0表示的是后面1 6个时间单位。2个A C K的到达使得拥塞窗口从 2个报文段增加为4 个,而这4个报文段在时间1 6 ~ 1 9时被发送。第1个A C K在时间2 3到达。4个A C K的到达使得拥 塞窗口从4个报文段增加为8个,并在时间2 4 ~ 3 1发送8个报文段。
在时间3 1及其后续时间,发送方和接收方之间的管道 ( p i p e )被填满。此时不论拥塞窗口和 通告窗口是多少,它都不能再容纳更多的数据。每当接收方在某一个时间单位从网络上移去 一个报文段,发送方就再发送一个报文段到网络上。但是不管有多少报文段填充了这个管道, 返回路径上总是具有相同数目的A C K。这就是连接的理想稳定状态。
20.7.1 带宽时延乘积
现在来回答窗口应该设置为多大的问题。在我们的例子中,作为最大的吞吐量,发送方 在任何时候有8个已发送的报文段未被确认。接收方的通告窗口必须不小于这个数目,因为通 告窗口限制了发送方能够发送的段的数目。
可以计算通道的容量为: c a p a c i t y (bit) = b a n d w i d t h (b/s) × ro u n d-trip time ( s )
一般称之为带宽时延乘积。这个值依赖于网络速度和两端的 RT T,可以有很大的变动。例如, 一条穿越美国(RT T约为60 ms)的T 1的电话线路(1 544 000 b/s)的带宽时延乘积为11 580字 节。对于2 0 . 4节中讨论的缓存大小而言,这个结果是合理的。但是一条穿越美国的 T 3电话线路 (45 000 000 b/s)的带宽时延乘积则为337 500字节,这个数值超过了最大所允许的T C P通告窗 口的大小(6 5 5 3 5字节)。在2 4 . 4节我们将讨论能够避免当前T C P限制的新的T C P窗口大小选项。
T1电话线的1 544 000 b/s是原始比特率。由于每193个bit使用1个作为帧同步,因此 实际数据率为1 536 000 b/s。一个T3电话线的原始比特率实际上是44 736 000 b/s,其数 据率可达到44 210 000 b/s。在讨论中我们使用1.544 Mb/s和45 Mb/s。
不论是带宽还是时延均会影响发送方和接收方之间通路的容量。在图 2 0 - 11中我们显示了 一个增加了一倍的RT T会使通路容量也增加一倍。
在图2 0 - 11底下的说明部分,通过使用一个较长的 RT T,这个管道能够容纳8个报文段而不 是4个。
类似地,图2 0 - 1 2表示了增加一倍的带宽也可使该管道的容量增加一倍。
在图2 0 - 1 2的下部,假定网络速率已经加倍,使得我们能够只使用上面一半的时间来发送 4个报文段。这样,该管道的容量再次加倍(假定该图的上半部分与下半部分中的报文段具有 同样大小,即具有相同的比特数)。
20.7.2 拥塞
当数据到达一个大的管道(如一个快速局域网)并向一个较小的管道(如一个较慢的广域网)发送时便会发生拥塞。当多个输入流到达一个路由器,而路由器的输出流小于这些输 入流的总和时也会发生拥塞。
图2 0 - 1 3显示了一个典型的大管道向小管道发送报文的情况。之所以说它典型,是因为大 多数的主机都连接在局域网上,并通过一个路由器与速率相对较低的广域网相连(我们再次 假定图中上半部分的报文段(9 ~ 2 0)都是相同的,而图中下半部分的 A C K也都是相同的)。
在该图中,我们已经标记路由器 R 1为“瓶颈”,因为它是拥塞发生的地方。它从左侧速 率较高的局域网接收数据并向右侧速率较低的广域网发送(通常 R 1与R 3是同样的路由器, 如同R 2与R 4一样。但这并不是必需的,有时也会使用不对称的路径)。当路由器R 2将所接收 到的分组发送到右侧的局域网时,这些分组之间维持与其左侧广域网上同样的间隔,尽管局 域网具有更高的带宽。类似地,返回的确认之间的间隔也与其在路径中最慢的链路上的间隔 一致。
在图2 0 - 1 3中已经假定发送方不使用慢启动,它按照局域网的带宽尽可能快地发送编号 为1 ~ 2 0的报文段(假定接收方的通告窗口至少为 2 0个报文段)。正如我们看到的那样, A C K 之间的间隔与在最慢链路上的一致。假定瓶颈路由器具有足够的容纳这 2 0个分组的缓存。 如果这个不能保证,就会引起路由器丢弃分组。在 2 1 . 6节讨论避免拥塞时会看到怎样避免这 种情况。
20.8 紧急方式
T C P提供了“紧急方式 ( u rgent mode)”,它使一端可以告诉另一端有些具有某种方式的 “紧急数据”已经放置在普通的数据流中。另一端被通知这个紧急数据已被放置在普通数据流 中,由接收方决定如何处理。
可以通过设置T C P首部(图1 7 - 2)中的两个字段来发出这种从一端到另一端的紧急数据 已经被放置在数据流中的通知。 U R G比特被置1,并且一个1 6 b i t的紧急指针被置为一个正的 偏移量,该偏移量必须与 T C P首部中的序号字段相加,以便得出紧急数据的最后一个字节的 序号。
仍有许多关于紧急指针是指向紧急数据的最后一个字节还是指向紧急数据最后一 个字节的下一个字节的争论。最初的 T C P规范给出了两种解释,但Host Requirements RFC确定指向最后一个字节是正确的。
然而,问题在于大多数的实现(包括源自伯克利的实现)继续使用错误的解释。所有符合Host Requirements RFC的实现都是可兼容的,但很有可能无法与其他大多数 主机正确通信。
T C P必须通知接收进程,何时已接收到一个紧急数据指针以及何时某个紧急数据指针还不 在此连接上,或者紧急指针是否在数据流中向前移动。接着接收进程可以读取数据流,并必 须能够被告知何时碰到了紧急数据指针。只要从接收方当前读取位置到紧急数据指针之间有 数据存在,就认为应用程序处于“紧急方式”。在紧急指针通过之后,应用程序便转回到正常 方式。
T C P本身对紧急数据知之甚少。没有办法指明紧急数据从数据流的何处开始。 T C P通过连 接传送的唯一信息就是紧急方式已经开始( T C P首部中的U R G比特)和指向紧急数据最后一 个字节的指针。其他的事情留给应用程序去处理。
不幸的是,许多实现不正确地称 T C P的紧急方式为带外数据(out-of-band data)。如果一个 应用程序确实需要一个独立的带外信道,第二个 T C P连接是达到这个目的的最简单的方法 (许多运输层确实提供许多人认为的那种真正的带外数据:使用同一个连接的独立的逻辑数据 通道作为正常的数据通道。这是T C P所没有提供的)。
T C P的紧急方式与带外数据之间的混淆,也是因为主要的编程接口(插口A P I)将 TCP的紧急方式映射为称为带外数据的插口。
紧急方式有什么作用呢?两个最常见的例子是 Te l n e t和R l o g i n。当交互用户键入中断键时, 我们在第2 6章将看到使用紧急方式来完成这个功能的例子。另一个例子是 F T P,当交互用户 放弃一个文件的传输时,我们将在第 2 7章看到这样的一个例子。
Te l n e t和R l o g i n从服务器到客户使用紧急方式是因为在这个方向上的数据流很可能要被客户 的T C P停止(也即,它通告了一个大小为0的窗口)。但是如果服务器进程进入了紧急方式,尽 管它不能够发送任何数据,服务器T C P也会立即发送紧急指针和U R G标志。当客户T C P接收到 这个通知时就会通知客户进程,于是客户可以从服务器读取其输入、打开窗口并使数据流动。
如果在接收方处理第一个紧急指针之前,发送方多次进入紧急方式会发生什么情况呢? 在数据流中的紧急指针会向前移动,而其在接收方的前一个位置将丢失。接收方只有一个紧 急指针,每当对方有新的值到达时它将被覆盖。这意味着如果发送方进入紧急方式时所写的 内容对接收方非常重要,那么这些字节数据必须被发送方用某种方式特别标记。我们将看到 Te l n e t通过在数据流中加入一个值为2 5 5的字节作为前缀来标记它所有的命令。
一个例子
让我们观察一下即使是在接收方窗口关闭的情况下, T C P是如何发送紧急数据的。在主 机b s d i上启动s o c k程序,并使之在连接建立后和从网络读取前暂停 1 0秒种(通过使用- P选 项),这将使另一端填满发送窗口:
bsdi % sock -i -s -P10 5555
接着我们在主机 s u n上启动客户,使之使用一个 8 1 9 2字节的发送缓存(使用 - S选项) 并进行6个向网络写 1 0 2 4字节数据的操作(使用 - n选项)。还指明- U 5选项,告知它向网络 写第5个缓存之前要写 1个字节的数据,并进入紧急数据方式。我们指明详细标志来观察写 的顺序:
我们设置发送缓存为8 1 9 2个字节,以便让发送应用程序能够立即写所有的数据。图 2 0 - 1 4 显示了t c p d u m p输出的这个交换过程的结果(删去了连接建立的过程)。第1 ~ 5行表示发送方 用4个1 0 2 4字节的报文段去填充接收方的窗口。然后由于接收方的窗口被填满(第 4行的A C K 确认了数据,但并没有移动窗口的右边沿),所以发送方停止发送。
在写了第4个正常数据之后,应用进程写了 1个字节并进入紧急方式。第 6行是该应用进程写的结 果,紧急指针被设置为40 9 8 。尽管发送方不能发送任何数据,但紧急指针和UR G 标志一起被发送。
5个这样的A C K在13 ms内被发送(第6 ~ 1 0行)。第1个A C K在应用进程写1个字节并进入 紧急方式时被发送,后面两个在应用进程写最后两个 1 0 2 4字节的数据时被发送(尽管 T C P不 能发送这2 0 4 8个字节的数据,可每次当应用程序执行写操作的时候, T C P的输出功能被调用。 当T C P看到正处于紧急方式时,它会发送其他的紧急通知)。第4个A C K在应用进程关闭其 T C P连接时被发送(T C P的输出功能再次被调用)。发送应用程序在启动几毫秒后终止—在 接收方应用进程已经发出其第一个写操作之前。 T C P将所有的数据进行排队,并在可能时发 送出去(这就是为何指明发送缓存为 8 1 9 2字节的原因,因此只有这样才能够把所有的数据都 放置在缓存中)。第5个A C K很可能是在接收第 4行的A C K时产生的。发送 T C P很可能在这个 A C K到达前便已将其第 4个报文段放入队列以便输出(第 5行)。另一端接收到这个 A C K也会 引起T C P输出例程被调用。
接着,接收方确认最后的 1 0 2 4字节的数据(第11行),但同时通告窗口为 0。发送方用一 个包含紧急通知的报文段进行了响应。
在第1 3行,当应用进程被唤醒、并从接收缓存读取一些数据时,接收方通告窗口为 2 0 4 8 字节。于是后面又发送了两个 1 0 2 4字节的报文段(第1 4和1 5行)。其中,由于紧急指针在第 1 个报文段的范围内,因此这个报文段被设置了紧急通知标志,而第 2个报文段则关闭了该标 志。
当接收方再次打开窗口(第 1 6行)时,发送方传输最后的数据(序号为 6 1 4 5)并发起正 常的连接关闭。
图2 0 - 1 5显示了发送的6 1 4 5个字节数据的序号。可以看到当进入紧急方式时所发送的字节 的序号是4 0 9 7,但在图2 0 - 1 4中紧急指针指向4 0 9 8,这证明了该实现(SunOS 4.1.3)将紧急指 针设置为紧急数据最后字节的下一个字节。
该图还可以让我们观察 T C P是如何对应用进程写的数据进行重新分组化的。当进入紧急 方式时待输出的 1个字节是与在缓存中的后面 1 0 2 3个字节一同发送的。下一个报文段也包含 1 0 2 4字节的数据,而最后一个报文段则只包含一个字节。
20.9 小结
正如我们在本章一开始时讲的那样,没有一种单一的方法可以使用 T C P进行成块数据的 交换。这是一个依赖于许多因素的动态处理过程,有些因素我们可以控制(如发送和接收缓 存的大小),而另一些我们则没有办法控制(如网络拥塞、与实现有关的特性等)。在本章, 我们已经考察了许多T C P的传输过程,介绍了所有我们能够看到的特点和算法。
进行成块数据有效传输的最重要的方法是 T C P的滑动窗口协议。我们考察了 T C P为使发送 方和接收方之间的管道充满来获得最可能快的传输速度而采用的方法。我们用带宽时延乘积 衡量管道的容量,并分析了该乘积与窗口大小之间的关系。在 2 4 . 8节介绍T C P性能的时候将再 次涉及这个概念。
我们还介绍了T C P的P U S H标志,因为在跟踪结果中总是观察到它,但我们无法对它的设 置与否进行控制。本章最后一个主题是 T C P的紧急数据,人们常常错误地称其为“带外数据”。 T C P的紧急方式只是一个从发送方到接收方的通知,该通知告诉接收方紧急数据已被发送, 并提供该数据最后一个字节的序号。应用程序使用的有关紧急数据部分的编程接口常常都不 是最佳的,从而导致更多的混乱。
参考资料:
1,链接:pdf文档,TCP的成块数据流 提取码:hs49
有任何问题,请联系:[email protected]