Nagle 算法、ACK 延迟和 Rlogin 回显

Nagle's Algorithm, ACK Delay and Rlogin echo

我也被建议在这里问,因为关于协议的具体问题是主题,但如果有人感兴趣,这个问题也有 ServerFault.

的小赏金

我正在阅读 TCP 数据流、延迟 ACKNagle 算法

到目前为止我的理解是:

  1. TCP 上的 Delayed ACK 实现对接收到的段的确认产生延迟,从而使应用程序有机会在确认的同时写入一些数据,从而避免发送空 ACK 数据包并导致网络拥塞。
  2. Nagle 的算法 实现声明您不能发送一个小的 TCP 段,而另一个小的段仍然没有被确认。这避免了流量加载多个 tinygrams.

在某些交互式应用程序中,例如 Rlogin,Nagle 算法延迟 ACK 可以 "conflict":

Rlogin 在我们键入时将键盘输入发送到服务器,一些键(如 F1)会生成一个以上的字节 (F1 = 转义 + 左括号 + M)。如果这些字节一个一个地传送到 TCP,那么它们可以在不同的段中发送。

服务器在拥有整个序列之前不会使用回显进行回复,因此所有 ACK 都会被延迟(期望来自应用程序的一些数据)。另一方面,客户端会在发送下一个字节之前等待第一个字节的确认(遵循 Nagle 算法)。这种组合最终导致 "laggy" Rlogin.

在 Rlogin 上发送的 F1F2 键的 tcpdump 表示如下:

    type Fl key
1   0.0                 slip.1023 > vangogh. login: P 1:2(1) ack 2
2   0.250520 (0.2505)   vangogh.login > slip.1023: P 2:4(2) ack 2
3   0.251709 (0.0012)   slip.1023 > vangogh.login: P 2:4(2) ack 4
4   0.490344 (0.2386)   vangogh.login > slip.1023: P 4:6(2) ack 4
5   0.588694 (0.0984)   slip.1023 > vangogh.login: . ack 6
    type F2 key
6   2.836830 (2.2481)   slip.1023 > vangogh.login: P 4:5(1) ack 6
7   3.132388 (0.2956)   vangogh.login > slip.1023: P 6:8(2) ack 5
8   3.133573 (0.0012)   slip.1023 > vangogh.login: P 5:7(2) ack 8
9   3.370346 (0.2368)   vangogh.login > slip.1023: P 8:10(2) ack 7
10  3.388692 (0.0183)   slip.1023 > vangogh.login: . ack 10

现在有疑问:即使我阅读的页面指出服务器在拥有完整的键序列之前没有回复回显,但通过 tcpdump 捕获的数据包显示正在回显键在它们各自的 ACK 上(第一个回复是 2 个字节长,因为来自 ESC 的回显是两个字符 - 插入符号 + 左括号)。

如果数据正在从应用程序发送到 TCP(回显响应),为什么 ACK 会延迟? 根据说明,关于服务器 在回显它之前等待完整序列,难道 ACK 不应该包含直到最后一个 ACK​​ 的回显,它会包含整个序列回显吗?

编辑: 这是修改后的 Rlogin 的 tcpdump 输出,没有 Nagle 算法(TCP_NODELAY 标志):

        type Fl key
1   0.0     slip.1023 > vangogh.login: P 1:2(1) ack 2
2   0.002163 (0.0022)   slip.1023 > vangogh.login: P 2:3(1) ack 2
3   0.004218 (0.0021)   slip.1023 > vangogh.login: P 3:4(1) ack 2
4   0.280621 (0.2764)   vangogh.login > slip.1023: P 5:6(1) ack 4
5   0.281738 (0.0011)   slip.1023 > vangogh.login: . ack 2
6   2.477561 (2.1958)   vangogh.login > slip.1023: P 2:6(4) ack 4
7   2.478735 (0.0012)   slip.1023 > vangogh.login: . ack 6
        type F2 key
8   3.217023 (0.7383)   slip.1023 > vangogh.login: P 4:5(1) ack 6
9   3.219165 (0.0021)   slip.1023 > vangogh.login: P 5:6(1) ack 6
10  3.221688 (0.0025)   slip.1023 > vangogh.login: P 6:7(1) ack 6
11  3.460626 (0.2389)   vangogh.login > slip.1023: P 6:8(2) ack 5
12  3.489414 (0.0288)   vangogh.login > slip.1023: P 8:10(2) ack 1
13  3.640356 (0.1509)   slip.1023 > vangogh.login: . ack 10

在段 4 上可以注意到,延迟约 0.2 毫秒,即使关闭了 Nagle 算法(因此所有特殊密钥字节一起到达并可以一起处理)。在段 6 上,我们无法识别 ~0.2ms 的延迟,因为服务器需要一段时间来重新处理和重新打包一些丢失的段的重传,但这个延迟在 2s 以上。

F2 键示例中,在第 11 段我们还可以注意到约 0.2 毫秒的延迟。我们无法在段 12 上识别它,因为它可能是在段 11 之后发送的。

这确实表明@MattTimmersans 的回答是正确的,这可能是这本书对段延迟的误解。它们确实可能是网络介质的特性,而不是由于数据未向下发送到 TCP 堆栈而延迟的 ACK。

参考: http://people.na.infn.it/~garufi/didattica/CorsoAcq/Trasp/Lezione9/tcpip_ill/tcp_int.htm

由于段 2、4、5、7 中的 ACK 带有数据,因此它们不是延迟的 ACK(不是由 ACK 计时器超时启动的)。

我相信与这些相关的 0.25 秒延迟只是主机之间的往返时间。我注意到这个数据是在 1993 年记录的。如果我没记错的话,我认为那个时候 250 毫秒的 ping 并不少见。

如果 tcpdump 在 vangogh 而不是 slip 上是 运行,它看起来会在 ESC 和 [=13= 之后很快看到回声] 数据包。

这个例子表明,即使没有 ACK 延迟,Nagle 的算法也很糟糕,因为它给交换增加了额外的往返时间。