tcp SACK选择确认位

时间:2024-02-24 16:36:21

场景

考虑一个场景,tcp发动端连续发送了4个包1-200,201-300,301-400,401-500. 接收端接收了1-200, 201-300,401-500。由于301-400没有收到,所以接收端只能发送一个ack 301给发送端,以确认1-300都收到,而401-500无法给发送端确认。这时发送端不知道301-400和401-500这两个包是否到达接收端。

解决办法

处理这种情况有两种可能的方式:

仅重传超时片段:这是一种更加保守的方式,仅重传超时的片段,希望其他片段都能够成功接收。但因为发送端没法确认200后到底有多少片段没被接收,情况就比较复杂。如果该片段之后的其他片段实际上接收到了,这一方式是最佳的。如果没接收到,就无法正常执行,这时后面的每一个片段需要单独计时并重传。

重传所有片段:这是一种更激进或者说更悲观的方式。无论何时一个片段超时了,不仅重传该片段,还有所有其他尚未确认的片段(301-400和401-500都会重传)。这一方式确保了任何时间都有一个等待确认的停顿时间,在所有未确认片段丢失的情况下,会刷新全部未确认片段,以使对端设备多一次接收机会。这种方式的问题在于可能这些重传是不必要的。如果第一个片段丢失而后面其他片段都接收到了,也得重传所有片段。

由于TCP不知道其他片段是否接收到,所以它也无法确认哪种方法更好,但只能选择一种方式

最优解决方式

tcpdump抓包时,在sync协商阶段可以看到一个“sackOK”的字段。
10:37:02.939789 IP 10.138.226.167.17198 > 10.139.252.110.9360: Flags S, seq 986305541, win 14600, options mss 1460,nop,nop,sackOK, length 0

这个位叫做选择确认(selective acknowledgment, SACK)。

它就用来处理上面的问题,即在TCP确认机制中,无法有效处理非连续TCP片段的问题。

但前提是连接的两方设备必须同时支持这一功能,通过连接时使用的SYN片段来协商是否允许SACK(即抓包中显示的sackOK)。这一过程完成之后,任一设备都可以在常规TCP片段中使用SACK选项。这一选项包含一个关于已接收但未确认片段数据sequence number范围的列表,如果某个片段已被选择确认过,则该片段中的SACK比特位置为1。该方式使用激进方式的改进版本,一个片段重传之后,之后所有片段也会重传,除非SACK比特位为1

例如,在示例中4个片段的情况下,当接收端发回ack为301(1-200,201-300)的确认信息,其中包含一个SACK选项指明:“已接收到字节401至500,但尚未确认”。如果片段4在片段1和2之后到达,上述信息也可以通过一个单独的ack确认片段来完成。服务器确认片段4的字节范围,并为片段4打开SACK位。当片段3重传时,服务器看到片段4的SACK位为1,就不会对其重传。

在片段3重传之后,片段4的SACK位被清除。这是为了防止客户端出于某种原因改变片段4已接收的想法。客户端应当发送确认号为501或更高的确认信息,正式确认片段3和4接收到。如果这一情况没有发生,服务器必须接收到片段4的另一条选择确认信息才能将它的SACK位打开,否则,在片段3重传时或计时器超时的情况下会对其自动重传。