重传机制
重传机制是TCP实现可靠传输的方式之一,通过序列号与确认应答号实现
发送端发送的数据达到接收方后,接受方收到后会回应一个确认应答消息。
发送方发送1~1000的数据给接受方,接收方收完后,会返回一个应答消息和下一次期望收到的数据1001
发送方接着发送1001后面的消息
如果过程中发生了丢包,就会触发TCP的重传机制
重传机制包括:
-
超时重传
-
快速重传
-
SACK
-
D-SACk
超时重传
超时重传的两种情况
-
数据包丢失
-
ACK确认应答丢失
发送数据时设定一个定时器,设置一个超时重传时间RTO,超过时间没有收到对方的ACK确认应答报文,就会触发超时重传
RTO的计算
RTO的计算并不像是tstart - tend这么简单
RTT是动态变化的,所以RTO也是动态变化的
RTO计算公式
首次计算RTO
-
加权平均往返时间:RTTs = RTT
-
RTT的偏差加权平均值:RTTD= RTT / 2
-
重传时间:RTO = RTTs + 4RTTD
后续计算RTO
-
RTTs新 = RTTs旧 + α (RTT新 - RTTs旧)= (1-α)RTTs旧 + α RTT新
-
RTTD新 = (1 - β)* RTTD旧 + β * (| RTTs新 - RTT新 |)
-
RTO = RTTs + 4RTTD
在一般情况下 α = 0.125(1/8),β = 0.25(1/4)
当重传的数据再次丢失的时候,TCP就会启用超时间隔加倍
每次遇到超时重传时,都会将下一次时间设置为之前的时间间隔的两倍,两次重传,说明此时网络不好,不适宜频繁发送
快速重传
快速重传的重传判定标准看的不是时间,而是数据
大致过程
-
第一份的数据Seq1先到达了,返回ACK2
-
但是Seq2在传输过程中并未到达,Seq3到达了,返回ACK2
-
后面Seq4和Seq5再到达也只会返回ACK2,因为Seq2还未收到
-
发送端收到三个ACK = 2的确认后,就会知道Seq2没有收到,在定时器过期之前就会重传Seq2
-
最后收到了,Seq2,由于Seq3、Seq4和Seq5都收到了,所以接受方会返回一个ACK6
但是这种方法也有弊端,因为只要达到三个ACK2就会重传,但是发送方并不知道这三和ACK2是谁返回来的,是应该重传Seq2、Seq3、Seq4、Seq5还是只传Seq2呢。
由于快速重传不知道应该重传那些TCP报文,所以有了SACK方法
SACK(SelectIve Acknowledgment选择性确认)
这种方式需要在TCP选项头部内加入一个SACK字段,接收方将缓存地图发给对方,发送方就可以知道那些数据丢失了
如果要支持SACK,双方都必须支持。
D-SACK(Duplicate)
D-SACK其实就是利用SACK告诉对方那些数据被重复接收了
滑动窗口
如果是每发完一个数据包,都要回复一次,那么这种效率是非常低的,包往返的时间越长,网络的吞吐量就会越低
所以TCP就有了滑动窗口,滑动窗口是有大小的,窗口大小就是指无需等待确认应答,而可以继续发送数据的最大值
窗口实际上是操作系统开辟的一个缓存空间,如果收到应答,则可以在缓存中清除,如果没收到,则必须保留
TCP中有个window字段,表示窗口大小
这个字段时接收端告诉发送端字节还有多少缓冲区可以接受数据,发送端就可以根据这个来数据来发送数据
所以,窗口大小通常是由接收端的窗口大小决定的
滑动窗口示意图:
当可用窗口用完后,发送窗口就不能再发送数据了,只能等确认回来后向前移动才能发送
当发送窗口收到5个确认应答后,窗口向前移动5个字节,然后可以开始继续发送后面5字节
接收窗口
接收窗口的大小不是一直等于发送窗口的
流量控制
在发送过程中发送方不能一股脑的发送出去,需要考虑对方的接收能力,适当地控制流量
TCP就提供了一种机制可以让发送方根据接收方的接收能力来控制发送数据的量,这就是流量控制
窗口都是系统内的缓冲区,缓冲区是可以被操作系统调整的
如果接收窗口大小为0,就会阻止发送方传递数据,这就是所谓的窗口关闭
接收方的向发送方发送窗口大小时,是通过ACK报文通告的。
当窗口值为0时,接收方就会发送一个窗口大小为0的ACK报文给接收方,如果ACK报文在传输过程中丢失了,就会导致发送方接收到了0窗口报文后,在等待对方的下一个非0报文,但是接收方已经发送了非0报文,这个报文在传输过程中丢失了,造成发送方一直在等待非0窗口的消息,接收方一直在等待发送方的数据,会形成死锁现象。
为了解决这个问题,TCP每个连接都会设有一个定时器,只要TCP一方收到了对方的零窗口通知,就会开启一个持续计时器。
如果发送方收到0窗口报文后,停止发送数据,超时后,还没收到非0报文,则会发送一个窗口探测报文,告知自己的窗口大小。
-
如果窗口仍然为0,则重新开启下一轮计时
-
如果窗口为0,则发送方会收到非0的ACK报文,就可以继续发送数据了。
拥塞控制
流量控制只是为了告诉发送方,不要把接收方的缓存填满了,但是却不能知道网络传输过程中发生了什么
在网络拥堵出现时,如果继续发送大量的数据包,很有可能就会引起丢包,延迟等现象发生,这时TCP就会重发数据,重发大量数据,又会给网络造成更大的拥堵,又丢包延迟,又重发,又拥堵。。。。恶性循环
所以有了拥塞控制,避免网络过于拥挤,发送大量数据,形成恶性循环
拥塞窗口cwnd是一个由发送方维护的一个状态量,会根据网络的拥塞程度进行变化
发送窗口swnd = min(cwnd,rwnd),发送窗口就是接收窗口和拥塞窗口的最小值
-
网络没有拥堵时,拥塞窗口是会变大的
-
网络出现拥堵时,拥塞窗口会变小
只要出现了重传,就认为网络出现了拥堵
拥塞控制主要用到四种算法
-
慢启动算法
-
拥塞避免算法
-
拥塞发生算法
-
快恢复算法
慢启动算法
一开始cwnd拥塞窗口的大小为1
只要收到一个ACK,cwnd的大小就会翻倍
-
连接建立完成后,cwnd = 1,表示能传1个MSS大小的数据
-
收到上一个数据的ACK后,cwnd = 2 * 1 = 2,可以传2个MSS大小的数据
-
收到上两个数据的ACK后,cwnd = 2 * 2 = 4,可以传4个MSS大小的数据
-
以此类推
当拥塞窗口大小达到慢启动门ssthresh后,就会停用慢启动算法,开始使用拥塞避免算法
-
cwnd <= ssthresh,慢启动算法
-
cwnd > ssthresh,拥塞避免算法
拥塞避免算法
每次收到一个ACK时,cwnd就增加 1 / cwnd 的大小
随着这样增加,网络就会越来越拥堵,当出现丢包重传的时候,就会触发拥塞发生算法
拥塞发生算法
拥塞发生时,重传的机制主要有两种:
-
超时重传
-
快速重传
两者使用的拥塞发生算法是不同的
如果发生超时重传时
-
ssthresh就会设置为cwnd的一半
-
cwnd会置为1,重新开始慢启动算法
如果发送快速重传
TCP会认为当前情况并不那么严重,因为还能连续收到3个ACK,触发快速恢复算法
快恢复算法
-
cwnd会设置为原来的一半
-
ssthresh = cwnd
cwnd会首先加3,收到了快重传的3个ACK
然后重传丢失的数据包
如果再收到重传数据的ACK,cwnd增加1
如果收到新的数据的ACK后,把cwnd置为ssthresh的值,证明重传数据的ACK已经全部收到,于是再次进入拥塞避免算法