第19章 TCP的交互数据流
19.4 Nagle算法
在前一节我们看到 , 在一个R l o g i n连接上客户一般每次发送一个字节到服务器,这就产生了一些4 1字节长的分组:2 0字节的I P首部、2 0字节的T C P首部和1个字节的数据。在局域网上,这些小分组(被称为微小分组( t i n y g r a m))通常不会引起麻烦,因为局域网一般不会出现拥塞。但在广域网上,这些小分组则会增加拥塞出现的可能。一种简单和好的方法就是采用RFC 896 [Nagle 1984]中所建议的N a g l e算法。
该算法要求一个 T C P连接上最多只能有一个未被确认的未完成的小分组,在该分组的确认到达之前不能发送其他的小分组。相反, T C P收集这些少量的分组,并在确认到来时以一个分组的方式发出去。该算法的优越之处在于它是自适应的:确认到达得越快,数据也就发送得越快。而在希望减少微小分组数目的低速广域网上,则会发送更少的分组(我们将在2 2 . 3节看到“小”的含义是小于报文段的大小)。
在图1 9 - 3中可以看到,在以太网上一个字节被发送、确认和回显的平均往返时间约为 1 6m s。为了产生比这个速度更快的数据,我们每秒键入的字符必须多于 6 0个。这表明在局域网环境下两个主机之间发送数据时很少使用这个算法。
但是,当往返时间( RT T)增加时,如通过一个广域网,情况就会发生变化。看一下在主机s l i p和主机v a n g o g h . c s . b e r k e l e y . e d u之间的R l o g i n连接工作的情况。为了从我们的网络中出去(参看原书封面内侧),需要使用两个 S L I P链路和I n t e r n e t。我们希望获得更长的往返时间。图 1 9 - 4显示了当在客户端快速键入字符(像一个快速打字员一样)时一些数据流的时间系列(去掉了服务类型信息,但保留了窗口通告)。
比较图1 9 - 4与图1 9 - 3,我们首先注意到从s l i p到v a n g o g h不存在经受时延的A C K。这是因为在时延定时器溢出之前总是有数据等待发送。
其次,注意到从左到右待发数据的长度是不同的,分别为: 1、1、2、1、2、2、3、1和3个字节。这是因为客户只有收到前一个数据的确认后才发送已经收集的数据。通过使用 N a g l e算法,为发送1 6个字节的数据客户只需要使用 9个报文段,而不再是1 6个。
报文段1 4和1 5看起来似乎是与N a g l e算法相违背的,但我们需要通过检查序号来观察其中的真相。因为确认序号是 5 4,因此报文段1 4是报文段1 2中确认的应答。但客户在发送该报文段之前,接收到了来自服务器的报文段 1 3,报文段1 5中包含了对序号为5 6的报文段1 3的确认。
因此即使我们看到从客户到服务器有两个连续返回的报文段,客户也是遵守了 N a g l e算法的。在图1 9 - 4中可以看到存在一个经受时延的 A C K,但该A C K是从服务器到客户的(报文段1 2),因为它不包含任何数据,因此我们可以假定这是经受时延的 A C K。服务器当时一定非常忙,因此无法在服务器的定时器溢出前及时处理所收到的字符。
最后看一下最后两个报文段中数据的数量以及相应的序号。客户发送 3个字节的数据(1 8 ,1 9和2 0),然后服务器确认这 3个字节(最后的报文段中的 ACK 21),但是只返回了一个字节(标号为5 9)。这是因为当服务器的T C P一旦正确收到这3个字节的数据,就会返回对该数据的确认,但只有当R l o g i n服务器发送回显数据时,它才能够发送这些数据的回显。这表明 T C P可以
在应用读取并处理数据前发送所接收数据的确认。T C P确认仅仅表明T C P已经正确接收了数据。最后一个报文段的窗口大小为8 1 8 9而非8 1 9 2,表明服务器进程尚未读取这三个收到的数据。
19.4.1 关闭Nagle算法
有时我们也需要关闭 N a g l e算法。一个典型的例子是 X窗口系统服务器(见 3 0 . 5节):小消息(鼠标移动)必须无时延地发送,以便为进行某种操作的交互用户提供实时的反馈。
这里将举另外一个更容易说明的例子—在一个交互注册过程中键入终端的一个特殊功能键。这个功能键通常可以产生多个字符序列,经常从 A S C I I码的转义( e s c a p e )字符开始。如果T C P每次得到一个字符,它很可能会发送序列中的第一个字符( A S C I I码的E S C),然后缓存其他字符并等待对该字符的确认。但当服务器接收到该字符后,它并不发送确认,而是继续等待接收序列中的其他字符。这就会经常触发服务器的经受时延的确认算法,表示剩下的字符没有在200 ms内发送。对交互用户而言,这将产生明显的时延。
插口API用户可以使用T C P _ N O D E L A Y选项来关闭Nagle算法。Host Requirements RFC声明T C P必须实现N a g l e算法,但必须为应用提供一种方法来关闭该算法在某个连接上执行。
19.4.2 一个例子
可以在N a g l e算法和产生多个字符的按键之间看到这种交互的情况。在主机 s l i p和主机v a n g o g h . c s . b e r k e l e y . e d u之间建立一个R l o g i n连接,然后按下F 1功能键,这将产生3个字节:一个e s c a p e、一个左括号和一个 M。然后再按下F 2功能键,这将产生另外 3个字节。图1 9 - 5表示的是t c p d u m p的输出结果(我们去掉了其中的服务类型和窗口通告)。
图1 9 - 6表示了这个交互过程的时间系列。在该图的下面部分我们给出了从客户发送到服务器的6个字节和它们的序号以及将要返回的 8个字节的回显。
当r l o g i n客户读取到输入的第1个字节并向T C P写入时,该字节作为报文段 1被发送。这是F 1键所产生的3个字节中的第1个。它的回显在报文段 2中被返回,此时剩余的 2个字节才被发送(报文段3)。这两个字节的回显在报文段 4被接收,而报文段5则是对它们的确认。
第1个字节的回显为2个字节(报文段2)的原因是因为在A S C I I码中转义符的回显是2个字节:插入记号和一个左括号。剩下的两个输入字节:一个左括号和一个 M,分别以自身作为回显内容。
当按下下一个特殊功能键(报文段 6 ~ 1 0)时,也会发生同样的过程。正如我们希望的那样,在报文段 5和1 0(s l i p发送回显的确认)之间的时间差是 200 ms的整数倍,因为这两个A C K被进行时延。
现在我们使用一个修改后关闭了 N a g l e算法的r l o g i n版本重复同样的实验。图 1 9 - 7显示了t c p d u m p的输出结果(同样去掉了其中的服务类型和窗口通告)。
在已知某些报文段在网络上形成交叉的情况下,以该结果构造时间系列则更具有启发性和指导意义。这个例子同样也需要随着数据流对序号进行仔细的检查。在图 1 9 - 8中显示这个结果。用图1 9 - 7中t c p d u m p输出的号码对报文段进行了相应的编号。
我们注意到的第 1个变化是当3个字节准备好时它们全部被发送(报文段 1、2和3)。没有时延发生—N a g l e算法被禁止。
在t c p d u m p输出中的下一个分组(报文段 4)中带有来自服务器的第 5个字节及一个确认序号为4的A C K。这是不正确的,因为客户并不希望接收到第 5个字节,因此它立即发送一个确认序号为2而不是6的响应(没有被延迟)。看起来一个报文段丢失了,在图 1 9 - 8中我们用虚线表示。
如何知道这个丢失的报文段中包含第 2、3和4个字节,且其确认序号为 3呢?这是因为正如在报文段 5中声明的那样,我们希望的下一个字节是第 2个字节(每当 T C P接收到一个超出期望序号的失序数据时,它总是发送一个确认序号为其期望序号的确认)。也正是因为丢失的分组中包含第 2、3和4个字节,表明服务器必定已经接收到报文段 2,因此丢失的报文段中的确认序号一定为3(服务器期望接收的下一个字节号)。最后,注意到重传的报文段 6中包含有丢失的报文段中的数据和报文段 4,这被称为重新分组化。我们将在 2 2 . 11节对其进行更多的介绍。
现在回到禁止 N a g l e算法的讨论中来。可以观察到键入的下一个特殊功能键所产生的 3个字节分别作为单独的报文段(报文段 8、9和1 0)被发送。这一次服务器首先回显了报文段 8中的字节(报文段11),然后回显了报文段9和1 0中的字节(报文段1 2)。
在这个例子中,我们能够观察到的是在跨广域网运行一个交互应用的环境下,当进行多字节的按键输入时,默认使用 N a g l e算法会引起额外的时延。
在第2 1章我们将进行有关时延和重传方面的讨论。