引言
在 TCP 中,当发送端的数据到达接收主机时,接收端主机会返回一个确认应答消息,表示已收到消息。针对TCP数据包丢失情况,常用以下四种重传机制解决:
- 超时重传
- 快速重传
- SACK
- D-SACK
超时重传
在发送时设置一个定时器,当定时器溢出时还没收到ACK
确认,它就重传该数据。TCP会在以下两种情况发生超时重传
- 数据包丢失
- 确认应答丢失

那么怎样决定超时间隔和重传的频率?
RTT(Round-Trip Time 往返时延):表示数据往返两端一次所需要的时间
RTO(Retransmission Timeout 超时重传时间):表示数据包发送超过该时间还没有收到回复,则进行超时重传
-
当超时时间 RTO 较大时,重发就慢,丢了老半天才重发,没有效率,性能差;
-
当超时时间 RTO 较小时,导致可能并没有丢就重发,于是重发的就快,会增加网络拥塞,导致更多的超时,更多的超时导致更多的重发
所以 超时重传时间 RTO 的值应该略大于报文往返 RTT 的值
由于网络是波动的,报文往返 RTT 是经常波动变化的,那系统是怎么确定RTO的值的呢?
系统通过采样的方式来估计往返时间RTT:
-
采样 RTT 的时间进行加权平均,算出一个 SRTT(smoothed round-trip time)的值,而且这个值是不断变化的,因为网络状况不断地变化。
-
采样 RTT 的波动范围,这样就避免如果 RTT 有一个大的波动的话,很难被发现的情况
[RFC6298] 建议使用 Jacobaon/Karels 算法 计算 RTO
1 | 第一次计算RTO,其中R1为第一次测量的RTT值, SRTT是计算平滑的RTT, DevRTR 是计算平滑的 RTT 与最新 RTT 的差距。 |
在 Linux 下,α = 0.125,β = 0.25, μ = 1,∂ = 4 通过大量实验调试出来的
如果超时重发的数据,再次超时的时候,又需要重传的时候,TCP 的策略是超时间隔加倍:即每当遇到一次超时重传的时候,都会将下一次超时时间间隔设为先前值的两倍。
快速重传
超时触发重传存在的问题是,超时周期可能相对较长。 就有了快速重传机制来解决超时重发的时间等待
发送方发出了 1,2,3,4,5 份数据
- 第一份 Seq1 先送到了,于是就 Ack 回 2(表示2以前的都收到了,其他发送方接下来从2开始发送)
- 结果 Seq2 因为某些原因没收到,Seq3 到达了,于是还是 Ack 回 2;
- 后面的 Seq4 和 Seq5 都到了,但还是 Ack 回 2,因为 Seq2 还是没有收到;
- 发送端收到了三个 Ack = 2 的确认,知道了 Seq2 还没有收到,就会在定时器过期之前,重传丢失 的Seq2
- 最后,接收到收到了 Seq2,此时因为 Seq3,Seq4,Seq5 都收到了,于是 Ack 回 6 。
所以,快速重传的工作方式是当收到三个相同的 ACK 报文时,会在定时器过期之前,重传丢失的报文段
SACK
**快速重传( Selective Acknowledgment 选择性确认)**重传的时候,怎么知道是重传之前的一个,还是重传所有。发送端并不清楚这连续的三个 Ack 2 是由于哪个请求而传回来的。比如快速重传例子,是重传 Seq2 还是重传 Seq2、Seq3、Seq4、Seq5 。为了解决不知道重传哪些 TCP 报文,就有 SACK。
在 TCP 头部 Option
字段里加一个 SACK 的东西,将缓存的地图(几组不连续块的第一个序列号与不连续块的最后一个序列号)发送给发送方, 这样发送方就可以知道哪些数据收到了,哪些数据没收到,只重传丢失的数据。
如下图,发送方收到了三次同样的 ACK 确认报文,于是就会触发快速重发机制,通过 SACK 信息发现 只有 200~299 这段数据丢失,则重发时,就只选择了这个 TCP 段进行重复。
如果要支持 SACK ,必须双方都要支持。在 Linux 下,通过
net.ipv4.tcp_sack
参数打开这个功能(Linux 2.4 后默认打开)
Duplicate SACK
Duplicate SACK 又称 D-SACK ,使用 **SACK 来通知发送方有哪些数据被重复接收了 **
比如1,ACK丢包:
- 接收方发给发送方的两个 ACK 确认应答都丢失了,发送方超时后,重传第一个数据包(3000 ~ 3499)
- 接收方发现数据是重复收到的,于是回了一个
SACK = 3000~3500
,告诉发送方 3000~3500 的数据早已被接收了,因为 ACK 都到了 4000 了,已经意味着 4000 之前的所有数据 都已收到,所以这个 SACK 就代表着 D-SACK 。 这样发送方就知道了,数据没有丢,是接收方的 ACK 确认报文丢了
比如2,网络延时:
- 数据包(1000~1499) 被网络延迟了,导致发送方没有收到 Ack 1500 的确认报文。
- 而后面报文到达的三个相同的 ACK 确认报文,就触发了快速重传机制,但重传后,被延迟的数据包(1000~1499)又到了接收方;
- 所以接收方回了一个SACK=1000~1500,因为ACK已经到了3000,所以这个SACK是D- SACK,表示收到了重复的包。
- 这样发送方就知道快速重传触发的原因不是发出去的包丢了,也不是因为回应的 ACK 包丢了,而 是因为网络延迟了。
可见,D-SACK 有这么几个好处:
- 可以让发送方知道,是发出去的包丢了,还是接收方回应的 ACK 包丢了;
- 可以判断是不是发送方的数据包被网络延迟了;
- 可以判断网络中是不是把发送方的数据包给复制了;
在 Linux 下可以通过
net.ipv4.tcp_dsack
参数开启/关闭这个功能(Linux 2.4 后默认打开)