如果 TCP 客户端向 TCP 服务器发送一个序列号从 10000 到 20000 的数据包,TCP 将使用 seq_ack 20001 的 ACK 进行响应。
如果我截取来自客户端的 TCP 数据包,并将数据包拆分为 2 个 TCP 段,一个段的序列号从 10000 到 15000,另一个段的序列号从 15001 到 20000。然后将这两个 TCP 段发送到 TCP 服务器。假设第二个段在路径中丢失。TCP 服务器将响应一个 seq_ack 为 15001 的 ACK。
现在,由于 TCP 客户端发送了一个完整的数据包,其 seq 为 10000 到 20000,但它收到了一个 15001 的 ACK,从客户端的角度来看,这很奇怪。它会如何反应?理论上,客户端应该重传从 seq 15001 到 20000 的字节,即客户端将从 seq 15001 开始传输新的数据包。但实践情况如何?在 TCP 堆栈实现中,是否与理论上一样?
我认为在 TCP 发送缓冲区中,当发送一个 tcp 段时,该段仍然停留在那里直到收到 ACK。当收到 ACK 时,该段的这些字节将从缓冲区中清除。发送缓冲区中有一个指针,当收到 ACK 时,该指针指向 ack_seq 对应的位置。ack_seq 下面的字节被清除。这样,整个段就不需要重新传输了?
答案1
这就是所谓的选择性确认,并且已经包含在 TCP 规范中定义RFC 2018. 这将允许客户端确实重新发送字节 15001 到 20000(因为它们在不同的数据包/段中如果你已经按照你说的把它们分开了),但更有趣的是,它甚至允许无序确认。
来自 RFC 2018:
当收到包含 SACK 选项的 ACK 时,数据发送方应记录选择性确认以供将来参考。假设数据发送方有一个重传队列,其中包含已传输但尚未确认的段(按序列号顺序排列)。
支持SACK
是不是TCP 规范要求。如果客户端或者服务器不支持选择性确认,实际上所有 10000 到 20000 个字节都必须重新传输。
在TCP协议栈实现中,和理论上的一样吗?
通常SACK
是支持,因为性能、效率和延迟的提升非常显著——尤其是在互联网这样的网络中。
但实际上,即使你像你所说的那样手动操作数据包,这些假设也应该成立。根据RFC 793至少,整个数据窗口必须重新传输,但接收方做知道收到的数据至少有效的. 有关实施细节,3.3 序列号来自 RFC 793。
有关有和没有选择性确认支持的整个过程的概述,请参阅本文(其中包括一些非常有用的图表)。
答案2
段大小可以(并且确实)在连接的整个生命周期内发生变化。幸运的是,TCP 无需记录之前发送的各个数据包的段大小。因此,它将执行以下操作:
- 每当 ACK 到达时,将指针相应地前进到第一个未确认的字节并丢弃任何现在不需要的缓冲区。
- 当需要重传时(快速重传或超时;不是收到第一个 ACK 后立即发送,它将在当前有效的段大小从指针开始指向第一个未确认的字节。
这两个操作都独立于这些字节最初发送的段大小。所以该理论应该与大多数实现相匹配。
让我提供一些背景来解释:
TCP 使用字节还是段?对于应用程序,TCP 公开了一个字节流接口。此外,所有标头字段和内部变量都以字节为单位。但是,为了传输信息,TCP 将它们分成段,因为逐个发送字节会非常浪费 :-)。到处使用字节计数器的优点是段大小不需要在连接的整个生命周期内保持不变:
- 正在引入选项,例如在重传时搭载 SACK(实际实现很少会遇到这种情况,甚至根本不会遇到)
- 路径 MTU 发生变化,例如,路径上的一个链接变为较低的 MTU 或瓶颈 MTU 链接增加。当建立隧道(VPN、PPPoE)或路由协议选择不同的 MTU 链接时,就会发生这种情况。这种情况发生在设置了 Don't Fragment 的 IPv4 中(对于大多数现代 TCP 来说都是如此);在 TCPv6 中始终如此。
顺便说一句:SACK 不是这里的答案,因为接收方(通常)仅在识别出字节流中的漏洞(即,如果一个数据包丢失但随后的数据包到达)。