一 tcp协议详解
超时重传、快重传、sack、d-sack、滑动窗口、流量控制、拥塞控制
tcp协议如何实现可靠传输
1、序列号与确认应答,适用于网络环境正常
2、如果网络环境有延迟,会丢包,你将无法及时收到确认应答,干等着效率很低
于是有了超时重传
发一次包,收到一个确认,这个过程称之为RTT(Round-Trip Time 往返时延)
超时重传时间是以 RTO (Retransmission Timeout 超时重传时间)
超时重传时间 RTO 的值,不能过小,也不能过大,应该略大于报文往返 RTT 的值。
- 1、当超时时间 RTO 较大时,重发就慢,丢了老半天才重发,没有效率,性能差;
- 2、当超时时间 RTO 较小时,会导致可能并没有丢就重发,于是重发的就快,会增加网络拥塞,导致更多的超时,更多的超时导致更多的重发。
如果超时重发的数据,再次超时的时候,又需要重传的时候,TCP 的策略是超时间隔加倍。
也就是每当遇到一次超时重传的时候,都会将下一次超时时间间隔设为先前值的两倍。两次超时,就说明网络环境差,不宜频繁反复发送。
超时触发重传存在的问题是,超时周期可能相对较长。那是不是可以有更快的方式呢?
于是就可以用「快速重传」机制来解决超时重发的时间等待。
3、快重传:
所以,快速重传的工作方式是当收到三个相同的 ACK 报文时,会在定时器过期之前,重传丢失的报文段。
快速重传机制只解决了一个问题,就是超时时间的问题,但是它依然面临着另外一个问题。就是重传的时候,是重传之前的一个,还是重传所有的问题。
比如对于上面的例子,是重传 Seq2 呢?还是重传 Seq2、Seq3、Seq4、Seq5 呢?因为发送端可以发送了seq1、seq2、seq3、seq4、seq5、seq6、seq7、seq8,从seq2开始丢包了,然后假设seq3、seq4、seq5都丢了,从seq6往后才开始发送正常,但是也返回ack2,此时发送端并不清楚连续的三个 Ack 2 是谁传回来的,这时候仅重传seq2很明显是不合理的
根据 TCP 不同的实现,以上两种情况都是有可能的。可见,这是一把双刃剑。
为了解决不知道该重传哪些 TCP 报文,于是就有 SACK 方法。
4、快重传的另外一种实现机制-SACK方法
还有一种实现重传机制的方式叫:SACK( Selective Acknowledgment 选择性确认)。
这种方式需要在 TCP 头部「选项」字段里加一个 SACK 的东西,它可以将缓存的地图发送给发送方,这样发送方就可以知道哪些数据收到了,哪些数据没收到,知道了这些信息,就可以只重传丢失的数据。
如下图,发送方收到了三次同样的 ACK 确认报文,称之为Dup Ack,于是就会触发快速重发机制,通过 SACK 信息发现只有 200~299 这段数据丢失,则重发时,就只选择了这个 TCP 段进行重复。
综上,通常情况下,发送端是要在收到了3个Dup Ack之后才会重发数据包。
但在容器环境里,很多时候你会发现,发送方只收到一个重复的Ack就立马重发数据了,为什么呢???
因为在linux系统中,发送方收到sack信息之后,就知道了接收方对数据的接收情况,此时发送方会计算:接收方收到的数据与未收到的数据之间存在很大的差值(如上图所示,那些留空区域与蓝色块区域的差值),超过了reordering*mss_cache,就会立刻重传数据,并不会等到连续三次收到重复ack,所以有时候你会发现即便收到了一个SACK,linux内核也可能会重传
此时你来思考一个问题,我们发送的数据包,如果是乱序的,例如seq3早于seq2到达接收端,也就是说seq2会被正常接收,只是顺序上时错乱的(tcp收到乱序包后会组织好顺序),但是在seq2正常抵达前,返回给发送端的SACK信息里,关于seq2的包就是空白的,这块区间大,linux内核就会安排重发,它并不会管你seq2随后就会到达接收端,因为它发现接收方收到的数据与未收到的数据之间差值存在很大的空隙
而在容器环境里,因为容器veth对本身机制的问题,会加剧乱序包现象,因而会大幅提升上面这种问题出现的几率,也就是说在容器环境里:乱序包+SACK+因为计算接收方收到的数据与未收到的数据之间差值存在很大的空隙,而导致的重传要远高于包丢失带来的重传
# nsenter -t $pid -n netstat -s |grep reordering
# 输出
Detected reordering 933072 times using SACK
在容器环境里,出现乱序包+sack导致发生重传的概率要远大于因为网络丢包而产生的重传
强调: 并非只要乱序就会重传,而是差值过大导致的重传!!!