Skip to content

4.2 TCP 重传、滑动窗口、流量控制、拥塞控制

重传机制

通过序列号与确认应答。保证发送的包一定被接受,TCP 针对数据包丢失的情况,会用重传机制解决。常见的重传机制有

超时重传

超时重传:在发送数据时,设定一个定时器,当超过指定的时间后,没有收到对方的 ACK 确认应答报文,就会重发该数据。

TCP 会在以下两种情况发生超时重传:

  • 数据包丢失
  • 确认应答丢失

RTT(Round-Trip Time 往返时延):数据发送时刻到接收到确认的时刻的差值,也就是包的往返时间。

超时重传时间是以 RTO (Retransmission Timeout 超时重传时间)表示。

上图中有两种超时时间不同的情况:

  • RTO 较大时,丢了老半天才重发,效率差;
  • RTO 较小时,会导致可能并没有丢就重发,于是重发的就快,会增加网络拥塞,导致更多的超时,更多的超时导致更多的重发。

所以通常超时重传时间 RTO 的略大于报文往返 RTT 的值。

因为网络是时常变化的,所以「报文往返 RTT 的值」是经常变化的,所以「超时重传时间 RTO 的值」应该是一个动态变化的值

  • 通过采样 RTT 进行加权平均,算出一个平滑 RTT 的值,并不断更新。
  • 还要采样 RTT 的波动范围,这样就避免如果 RTT 有一个大的波动的话,很难被发现的情况。

如果超时重发的数据,再次超时的时候,又需要重传的时候,TCP 的策略是超时间隔加倍。

也就是每当遇到一次超时重传的时候,都会将下一次超时时间间隔设为先前值的两倍。两次超时,就说明网络环境差,不宜频繁反复发送。

超时触发重传存在的问题是,超时周期可能相对较长。

快速重传

快速重传:不以时间为驱动,而是以数据驱动重传。当收到三个相同的 ACK 报文时,会在定时器过期之前,重传丢失的报文段。

快速重传机制只解决了一个问题,就是超时时间的问题,但是它依然面临着另外一个问题。就是**重传的时候,是重传一个,还是重传所有的问题。**因为不确定可能丢了几个。

SACK 方法

SACK( Selective Acknowledgment 选择性确认):会在 TCP 头部「选项」字段里加一个 SACK 的东西,它可以将已收到的数据的信息发送给「发送方」,这样发送方就可以知道哪些数据收到了,哪些数据没收到,知道了这些信息,就可以只重传丢失的数据

Duplicate SACK

Duplicate SACK 又称 D-SACK:使用了 SACK 来告诉「发送方」有哪些数据被重复接收了(由于网络延时或者确认报文丢失导致的重复发送)。

好处:

  1. 可以让「发送方」知道,是发出去的包丢了,还是接收方回应的 ACK 包丢了;
  2. 可以知道是不是「发送方」的数据包被网络延迟了;

滑动窗口

滑动窗口:操作系统开辟的一个缓存空间,发送方主机在等到确认应答返回之前,必须在缓冲区中保留已发送的数据。如果按期收到确认应答,此时数据就可以从缓存区清除。大小就是指无需等待确认应答,而可以继续发送数据的最大值

累计确认(应答):只要后面序号的报文确认消息收到了,无论前面的确实报文是否收到,都可以表明接收端前面都收到了

TCP 头里有一个字段叫 Window表示窗口大小:是接收端告诉发送端自己还有多少缓冲区可以接收数据。发送端就可以根据这个接收端的处理能力来发送数据,而不会导致接收端处理不过来。

通常窗口的大小是由接收方的窗口大小来决定的。

发送方发送的数据大小不能超过接收方的窗口大小,否则接收方就无法正常接收到数据。

发送方缓存的数据:

  • 1、已发送并收到ACK的数据
  • 2、已发送但未收到ACK的数据(在滑窗)
    • 收到确认后会按需移动
  • 3、未发送但总大小在接收方处理范围内(在滑窗)
    • 如果为空,表示等待确认无法继续发送数据了
  • 4、未发送但总大小超过接收方处理范围
  • 用三个指针确定边界,3的左指针式相对偏移指针,12,34的边界指针是绝对指针

接收方的数据

  • 1、已成功接收并确认的数据(等待应用进程读取)(滑窗);

  • 2、是未收到数据但可以接收的数据;(滑窗)

    • 1、2构成滑窗大小,应用读取越快,窗口大小可以越大
  • 3、未收到数据并不可以接收的数据;

  • 两个指针确定边界

接收窗口的大小是约等于发送窗口的大小的,因为接收方的应用进程读取数据速度会发生变化,会通过 TCP 报文中的 Windows 字段来告诉发送方。而这个传输过程是存在时延的,所以接收窗口和发送窗口是约等于的关系。


流量控制

为了避免接收方处理不过来发送方发送的数据而导致丢包并触发重发机制,TCP 提供流量控制:让「发送方」根据「接收方」的实际接收能力控制发送的数据量。

操作系统会不断调整缓冲区大小,缓冲区直接决定了滑窗的大小

当应用进程没办法及时读取缓冲区的内容时,也会对我们的缓冲区造成影响。

如果发生了先减少缓存,再收缩窗口,后续接收到的包可能会丢失(因为发送方收到窗口大小有时延,可能先发送了大量包,但是接收方缓存存放不下)。

为了防止这种情况发生,TCP 规定是不允许同时减少缓存又收缩窗口的,而是采用先收缩窗口,过段时间再减少缓存,这样就可以避免了丢包情况。

窗口关闭

窗口关闭:当接受方窗口大小为 0 时,就会阻止发送方给接收方传递数据,直到窗口变为非 0 为止。

当发生窗口关闭时,接收方处理完数据后,会向发送方通告一个窗口非 0 的 ACK 报文,如果这个通告窗口的 ACK 报文在网络中丢失了,会导致发送方一直等待接收方的非 0 窗口通知,接收方也一直等待发送方的数据,如不采取措施,这种相互等待的过程,会造成了死锁的现象。

为了解决这个问题,TCP 为每个连接设有一个持续定时器,只要 TCP 连接一方收到对方的零窗口通知,就启动持续计时器。

如果持续计时器超时,就会发送窗口探测 ( Window probe ) 报文,而对方在确认这个探测报文时,给出自己现在的接收窗口大小。

  • 如果接收窗口仍然为 0,那么收到这个报文的一方就会重新启动持续计时器;
  • 如果接收窗口不是 0,那么死锁的局面就可以被打破了。

窗口探测的次数一般为 3 次,每次大约 30-60 秒(不同的实现可能会不一样)。如果 3 次过后接收窗口还是 0 的话,有的 TCP 实现就会发 RST 报文来中断连接。

糊涂窗口综合症

糊涂窗口综合症:接收方处理数据很慢,导致窗口很小,每次处理了几个字节就告诉发送方现在可以发送几个字节,发送方也会义无反顾地发送这几个字节

TCP + IP 头有 40 个字节,为了传输那几个字节的数据,要搭上这么大的开销,这太不经济了。

要解决糊涂窗口综合症,就要同时解决两个问题就可以了:

  • 让接收方不通告小窗口给发送方

    • 当窗口过小,直接告诉发送方窗口为0
  • 让发送方避免发送小数据

    • 窗口太小不发送

拥塞控制

在网络出现拥堵时,如果继续发送大量数据包,可能会导致数据包时延、丢失等,这时 TCP 就会重传数据,但是一重传就会导致网络的负担更重,于是会导致更大的延迟以及更多的丢包,这个情况就会进入恶性循环被不断地放大……

拥塞控制:目的是避免「发送方」的数据填满整个网络。

拥塞窗口:发送方维护的一个的状态变量,它会根据网络的拥塞程度动态变化的。网络越通畅,窗口越大。

发送窗口的值是= min(拥塞窗口, 接收窗口)

其实只要「发送方」没有在规定时间内接收到 ACK 应答报文,也就是发生了超时重传,就会认为网络出现了拥塞。

拥塞控制主要是四个算法:

慢启动

TCP 在刚建立连接完成后,首先是有个慢启动的过程,这个慢启动的意思就是一点一点的提高发送数据包的数量,如果一上来就发大量的数据,这不是给网络添堵吗?

慢启动:当发送方每收到一个 ACK,拥塞窗口 cwnd 的大小就会加 1。

  • 第一次发送收到一个ack,增加到2
  • 第二次发送收到两个ack,增加到4。。。
  • 变化其实是两倍递增,ack的数量其实也是两倍递增,所以增加量和ack相同
  • 发包的个数是指数性的增长
  • 当到达慢启动门限值ssthresh,就会启动拥塞避免算法,一般来说阈值的大小是 65535 字节。(两个字节的最大值个包)

拥塞避免算法

拥塞避免:每当收到一个 ACK 时,cwnd 增加 1/cwnd。

拥塞避免算法就是将原本慢启动算法的指数增长变成了线性增长,还是增长阶段,但是增长速度缓慢了一些。

一直增长到网络进入了拥塞的状况,于是就会出现丢包现象,这时就需要对丢失的数据包进行重传。

当触发了重传机制,也就进入了「拥塞发生算法」。

拥塞发生

发生了「超时重传」,则就会使用拥塞发生算法。

  • 将慢启动门限值设置为当前拥塞窗口的一半
  • 拥塞窗口重置为初始值(Linux的初始值为10)

快速恢复

发生了「快速重传」(能收到3个重复的ACK)的拥塞发生算法

  • 拥塞窗口减少到原来的一半;

  • 慢启动门限值设置为当前拥塞窗口值(也就是发生重传时的一半

  • 进入快速恢复算法:

    • 拥塞窗口 = 慢启动门限值 + 重复收到ack的数量

    • 重传丢失的数据包;

    • 如果再收到重复的 ACK,那么拥塞窗口每次增加 1;

    • 如果收到新数据的 ACK 后,把拥塞窗口设置为第一步中的慢启动门限值;

      • 这里拥塞窗口又回到慢启动门限值,是因为快速恢复是为了尽快将丢失的数据包发给目标
      • 快速恢复是拥塞发生后慢启动的优化,最后还是要降低拥塞窗口进入拥塞避免,只是没有那么剧烈

正在精进