TCP的那些事(二)

TCP重传机制

TCP 要保证所有的数据包都到达,就必须要采取重传机制,注意:接收端给发送端的ACK只能确定最大连续的包,比如,发送端发送了1,2,3,4,5个数据包,但是接收端只收到了1,2,所以ACK = 3,然后收到了4(这时候3还没有收到),此时的TCP会怎么办呢?正如前面所说的,Sequence Number都是以字节数为单位的,所以ACK的时候不能跳着确认,只能确认最大的连续收到的包,不然发送端就认为之前的都收到了。

超时重传机制

一种是不回ACK死等3,当发送方收不到对方的ACK超时后,会重传3,一旦接收方收到了3,会ack4,表示3和4都收到了,但是这种方式有个严重的问题就是如果一味的死等3,这样4和5也相当于处于死等状态,所以这个时候如果4和5先收到了,那么发送方也完全不知道发生了什么。而且有可能会导致4和5的重传。

对此,有两种选择:

1.仅仅重传timeout的包,上面的数据包3.

2.重传timeout包后的所有数据包,也就是要重传3,4,5这三个数据包。

这两种方式各有优劣,第一种方式会节省宽带,但是慢;第二种方式会浪费宽带,也可能存在一些无用功,单数速度稍微快一点,但总体来说都不好,因为要等待timeout,这个timeout我们并不能具体确定,可能会很长。

快速重传机制

于是,TCP引入了一种叫做FAST Retransmit的方法,这种方法不以时间驱动,而是以数据驱动,如果发送方的数据包没有连续到达,就ACK那个可能丢失了的包,如果连续三次的ACK都是这个数据,就重传。FAST Retransmit的好处是不用等timeout了再重传。
比如:如果发送方发送了1,2,3,4,5五份数据包,第一份接收方接收到了ACK2,由于某些原因2没有收到,过了一会3收到了,还是ack2,一会4,5也受到了还是ack2,三次ack = 2以后,发送端知道2没有收到,于是就重发2,这一次数据包2收到了,由于3,4,5都已经收到了,于是ack6。可以参照下图:
TCP的那些事儿(2)
FAST Retransmit只解决了一个问题,就是timeout的问题,但是是仅仅重发ack的数据报,还是重发所有的数据报这个问题依然没有解决,因为2没有收到,3,4,5收到了之后ack2,发送端根本不知道这三个连续的ack是谁发回来的,也许发送端发了20个数据,而ack是10,16,20传回来的呢,测可能导致重传从2到20的所有数据包(这就是某些TCP实际的实现),所以快速重传机制是一把双刃剑。

SACK方法

另外一种更好的方式:(Selective Acknowledgment (SACK))这需要再TCP头里面加一个SACK的东西,ACK还是FAST Retransmit的ACK,SACK则是汇报收到的数据碎版。示意图:
TCP的那些事儿(2)
这样,发送端就可以根据SACK的数据知道那些数据到了,那些数据还没有到,所以这是对快速重传机制的一种优化,当然,这个协议需要两边都支持,在linux下,可以设置tcp_sack这个参数带开这个功能。(linux2.4以后默认打开)。
这里还要注意一个问题就是接收端Reneging,所谓Reneging就是接收端有权把已经报给发送端的sack里的数据丢掉,这样干是不被鼓励的,这个事会把问题复杂化,但是,接收方这么做可能会存在极端情况,比如,接收端要把内存给更重要的东西,所以发送方也不能完全依赖Sack,还是要依赖ACK,还要维护Timeout,如果后续ACK的值始终没有增长,就把SACK的东西重传,另外接收端永远不能把Sack的包标记为ack。
注意:sack消耗发送方的资源,如果攻击者给数据发送方一堆sack的选项,这可能导致发送方要重传甚至遍历已经发出去的所有数据,这会消耗很多发送方的资源。

Duplicate Sack(重复收到数据的问题)

又叫做D_Sack,其主要用sack来告诉发送方那些数据重复发送了,D_Sack用Sack第一段来做标记。
1.如果SACK的第一段被ACK的范围所覆盖,这种情况叫做D_Sack.
2.如果Sack的第一段的范围被Sack第二段的范围覆盖,这种情况叫做D_Sack。
示例一:
ACK丢包
TCP的那些事儿(2)
由上面的图可以看出来,丢了两个ACK所以发送端重传了第一段(3000-3499)于是第三行接收端发现重复收到,所以回了SACK = 3000 - 3500,因为第三行ACK = 4000,意味着收到了4000之前的所有数据,所以这个SACK就是D_SACK.主要告诉发送端接收端收到了重复的数据,而且这样发送端还知道数据吧并没有丢,是ACK丢了。
网络延时
TCP的那些事儿(2)
如上图所示:网络包(1000-1499)被延误了,经过下面三次ACK触发了FAST Retransmit,导致发送端重传(1000-1499),但是后面原来1000-1499的包又
传过来了,所以回了一个SACK = 1000-1500,但是ACK已经到了3000所以这个是D_Sack。
可见,引入D_SACK有下面几点好处:
1.可以让发送方知道,到底是数据包丢了,还是ACK包丢了。
2,是不是自己的TIMEOUT太小了,导致重传。
3,网络上出现了先发的包后到的情况(reordering)。
4,网络上是不是把我的数据包复制了。
知道这些东西可以很好的帮助TCP了解网络情况,从而很好的做网络上的流控。
这个功能可以通过设置linux下的tcp_dsack参数开启(linux2.4以后默认开启)。

相关文章: