在网络上,TCP 针对数据包丢失的情况,会用重传机制解决。常见的重传机制包括:超时重传、快速重传、SACK、D-SACK。
1. 超时重传
在发送数据时,设定一个定时器,当超过指定的时间后,没有收到对方的 ACK 确认应答报文,就会重发该数据。这就是超时重传。一般有两种情况:数据包丢失、确认应答丢失。
那么超时时间应该设置为多少呢?
- RTT(
Round-Trip Time
)往返时延。指的是数据发送时刻到接收到确认的时刻的差值。也就是报文的往返时间。 - RTO(
Retransmission Timeout
)超时重传时间。
RTO 的值非常重要,当 RTO 较大时,重传就会很慢,也就是丢了很久之后才重传,效率低,性能差。当 RTO 较小时,有可能数据包并没有丢就重传了,重传变快了,但会增加网络拥塞,导致更多的超时,更多的超时可能会导致更多的重传。
因此,我们可以知道,RTO 的值应该略大于 RTT 的值。实际上,由于我们的网络是时常变化的,于是 RTT 值是经常变化的,那么 RTO 的值应该是一个动态变化的值。
Linux 中计算 RTO 的方式,需要进行采样。
- TCP 通过采样 RTT 的时间,然后进行加权平均,算出一个相对稳定的 RTT 值。而且这个值由于网路状况的变化,会是不断变化的。
- 除了采样 RTT 之外,还要采样 RTT 的波动范围,这样就避免了如果 RTT 有一个大的波动的话,很难被发现的情况。
最后通过一组公式,结合采样的 RTT 时间,计算出 RTO 的值。
我们超时重传的数据,再次超时,又需要重传的时候,TCP 的策略是:将超时时间间隔加倍。也就是每当遇到一次超时重传的时候,都会将下一次超时时间间隔设为先前值的两倍。因为超时重传的数据包都超时了,就说明网络环境比较差,不宜频繁反复发送数据包。
超时重传存在的问题是,超时周期可能相对较长。于是可以使用 “快速重传” 机制来解决超时重传的时间等待。
2. 快速重传
TCP 的快速重传机制,他不以时间为驱动,而是以数据为驱动进行重传。

如上图,举个例子。
- 发送方发送了
seq1
到seq5
,这 5 个数据报文。假如 seq1 发送到了,于是接收端回复一个 ACK2。 - 接下来发送 seq2 的时候,由于某些原因,接收端没有收到。
- 但是 seq3 到达了,接收端还是返回 ACK2。同样的,seq4 和 seq5 都到了,接收端还是返回 ACK2,因为 seq2 还没有收到。
- 发送端收到了三个
ACK=2
的确认,由此得知接收端没有收到 seq2,就会在定时器过期之前,重传丢失的 seq2。 - 最后,收到了 seq2,此时因为接收端 seq3、seq4、seq5 都收到了,于是 ACK 返回 6。
因此,快速重传的工作方式是当收到三个相同的 ACK 报文时,会在定时器过期之前,重传丢失的报文段。
新问题出现:快速重传其实只解决了超时时间的问题,还有另外一种问题,就是重传的时候,是重传一个报文,还是重传所有的报文。
比如:假设发送方发送了 6 个数据包,编号分别为 seq1 - seq6
。但是 seq2、seq3 丢失了,那么接收方在收到 seq4、seq5、seq6
时,都是回复 ACK=2
给发送方,但是发送方并不清楚这连续的 ACK=2
是接收方收到那个报文而回复的。因此发送方是选择重传 seq2 一个报文?还是重传 seq2 之后已发送的所有报文呢?
- 如果只选择重传 seq2 这一个报文,那么重传的效率很低。因为对于丢失的 seq3 报文,还得在后续收到三个重复的
ACK=3
才能触发重传。 - 如果选择重传 seq2 之后已发送的所有报文,虽然能同时重传已丢失的 seq2 和 seq3 报文。但是 seq4、seq5、seq6 的报文是已经接收过的,重传他们相当于资源浪费。
因此,为了解决不知道该重传那些 TCP 报文,于是就有了 SACK 方法。
3. SACK 方法
SACK 也用来实现重传,SACK(Selective Acknowledgment
)称为 “选择性确认”。
这种方式需要在 TCP 头部选项字段里加一个 SACK 的字段,他可以将已收到的数据的信息发送给“发送方”,这样发送方就可以知道那些数据收到了,那些数据没有收到。知道了此信息之后,发送方只需要重传丢失的数据即可。
如果要使用 SACK,必须双方都支持 SACK 功能。在 Linux 下,可以通过 net.ipv4.tcp_sack
参数打开这个功能。
4. Duplicate SACK
Duplicate SACK
又称为 D-SACK
,其主要使用了 SACK 来告诉 发送方 有哪些数据被重复接收了。
举一个例子:ACK 丢包

- 发送方发送
3000 - 3499
这个区间的数据。发送方再发送3500 - 3900
这个区间的数据。但是这两个 ACK 都丢失了。所以发送方等待超时后,会重传第一个数据包3000 - 3499
这个区间的数据。 - 此时,接收方收到了,并且发现数据是重复收到的,于是回了一个
(ACK=4000, SACK=3000-3500)
这样的一个包,告诉发送方,3000-3500
的数据早已被接收了,因为 ACK 都到了 4000 了,已经意味着 4000 之前的所有数据都已经收到,所以这个 SACK 就代表着D-SACK
。 - 这样,发送方就知道是数据没有丢,而是接收方的 ACK 确认报文丢了。
再举一个例子:网络延时

- 发送方发送了
500-999
这个报文,接收端回复ACK=1000
。然后发送方发送1000-1499
这个报文,因为网络延时,接收端没有收到。 - 发送方继续发送
1500-1999
这个报文,接收端回复ACK=1000, SACK=1500-2000
。发送方继续发送2000-2499
,接收端回复ACK=1000, SACK=1500-2500
。继续发送方发送2500-2999
这个报文,接收端回复ACK=1000, SACK=1500-3000
。 - 此时收到三个相同的 ACK 确认报文,就触发了快速重传机制,但是在重传后,被延迟的数据包
1000 - 1499
又回到了接收端。 - 此时,接收方回了一个
ACK=3000, SACK=1000-1500
,这个 SACK 包是D-SACK
包,表示收到了重复的包 - 这样发送方就知道快速重传触发的原因不是发出去的包丢了,也不是因为回应的 ACK 包丢了,而是因为网络延时了。
因此,D-SACK
有如下好处:
- 可以让 发送方 知道,是发出去的包丢了,还是接收方回应的 ACK 包丢了
- 可以知道是不是 发送方 的数据包 网络时延比较大
- 可以知道网络中是不是把发送方 的数据包给复制了
在 Linux 下可以通过 net.ipv4/tcp_dsack
参数开启或关闭这个功能。