SunJun/关于TCP那些事(二)

Created Mon, 20 Aug 2018 01:20:00 +0800 Modified Tue, 27 Sep 2022 13:46:48 +0800
1164 Words

TCP重传机制

TCP确认机制属于累积确认,接收端给发送端的Ack确认只会确认最后一个连续的包,SeqNum和Ack是以字节数为单位,所以ack的时候,不能跳着确认,只能确认最大的连续收到的包, 不然发送端就以为之前的都收到了

超时重传

发送端发了1,2,3,4,5一共五份数据,接收端收到了1,2,于是回ack 3,然后收到了4(注意此时3没收到)。这种方式发送端会因为没有收到3的ACK重传,一旦收到3后会ACK4,意味着3、4都 收到了。
但是这种当时有很严重的问题,如果没收到3就会死等3,即使4、5都收到了发送端也不知道,因为没有收到回复。这可能到会方端悲观的认为4、5都没有收到而导致重传。

就会有两种选择:

  • 只重传timeout的包
  • 重传timeout后的所有的包

第一种节省带宽但是慢,第二种快但是浪费带宽,但是总体来说两种都不是最好的方式。

快速重传

TCP有Fast Retransmit快速重传机制,不以时间驱动,以数据驱动重传。包没有连续到达就ACK最后那个连续的包,发送方如果连续3次收到相同的ACK就重传该包,快速重传的好处 在于不用等待timeout。
但是快速重传只是解决一个问题那就是timeout问题,但是依然面临这个艰难的选择,就是只重传超时的那一个包还是重传所有包。

SACK方法

另一种选择是Selective Acknowledgment (SACK),这个需要在TCP头加上一个SACK,ACK还是快速重传,SACK则是汇报收到的数据碎版。

这样就知道哪些数据收到了哪些没有收到,这样就优化了快速重传算法,但是需要两边都支持在 Linux下,可以通过tcp_sack参数打开这个功能(Linux 2.4后默认打开)。

还需要考虑一个问题:

  • 接收方Reneging,所谓Reneging的意思就是接收方有权把已经报给发送端SACK里的数据给丢了。这样干是不被鼓励的,因为这个事会把问题复杂化了,但是,接收方这么 做可能会有些极端情况,比如要把内存给别的更重要的东西。
  • 发送方也不能完全依赖SACK,还是要依赖ACK,并维护Time-Out,如果后续的ACK没有增长,那么还是要把SACK的东西重传,另外,接收端这边永远不能把SACK的包标记为Ack

注意:SACK会消费发送方的资源,试想,如果一个攻击者给数据发送方发一堆SACK的选项,这会导致发送方开始要重传甚至遍历已经发出的数据,这会消耗很多发送端的资源。 详细的东西请参看《TCP ACK的性能权衡》

Duplicate SACK – 重复收到数据的问题

Duplicate SACK又称D-SACK,其主要使用了SACK来告诉发送方有哪些数据被重复接收了

D-SACK使用了SACK的第一个段来做标志:

  • 如果SACK的第一个段的范围被ACK所覆盖,那么就是D-SACK
  • 如果SACK的第一个段的范围被SACK的第二个段覆盖,那么就是D-SACK