0%

TCP可靠性传输

这篇文章记述了关于TCP可靠传输的知识点。

为了实现可靠性传输,需要考虑数据的丢包、重复、破坏及分片顺序混乱等问题。

TCP是通过序列号、确认应答机制、重传机制、窗口控制等机制保证可靠性传输的。

tcp 实现可靠传输的机制

  1. 应用数据被分割成 TCP 认为最适合发送的数据块。
  2. TCP 给发送的每一个包进行编号,接收方对数据包进行排序,把有序数据传送给应用层。
  3. 校验和:TCP 将保持它首部和数据的检验和。这是一个端到端的检验和,目的是检测数据在传输过程中的任何变化。如果收到段的检验和有差错,TCP 将丢弃这个报文段和不确认收到此报文段。
  4. TCP 的接收端会丢弃重复的数据。
  5. 流量控制:TCP 连接的每一方都有固定大小的缓冲空间,TCP的接收端只允许发送端发送接收端缓冲区能接纳的数据。当接收方来不及处理发送方的数据,能提示发送方降低发送的速率,防止包丢失。TCP 使用的流量控制协议是可变大小的滑动窗口协议。 (TCP 利用滑动窗口实现流量控制)
  6. 拥塞控制:当网络拥塞时,减少数据的发送。
  7. ARQ协议:也是为了实现可靠传输的,它的基本原理就是每发完一个分组就停止发送,等待对方确认。在收到确认后再发下一个分组。
  8. 超时重传:当 TCP 发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文段。

传输机制

超时重传

TCP会在数据包丢失和确认应答丢失两种情况下发生超时重传。

image-20210312095843833

超时时间的设置是以RTO表示(Retransmission Timeout 超时重传时间)

超时重传时间 RTO 的值应该略大于报文往返 RTT 的值

我们来看看 Linux 是如何计算 RTO 的呢?

估计往返时间,通常需要采样以下两个:

  • 需要 TCP 通过采样 RTT 的时间,然后进行加权平均,算出一个平滑 RTT 的值,而且这个值还是要不断变化的,因为网络状况不断地变化。
  • 除了采样 RTT,还要采样 RTT 的波动范围,这样就避免如果 RTT 有一个大的波动的话,很难被发现的情况。

快速重传

快速重传的工作方式是当收到三个相同的 ACK 报文时,会在定时器过期之前,重传丢失的报文段。

滑动窗口

滑动窗口协议比较复杂,也是 TCP 协议的精髓所在。

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

滑动窗口分为「接收窗口」和「发送窗口」
因为 TCP 协议是全双工的,会话的双方都可以同时接收和发送,那么就需要各自维护一个「发送窗口」和「接收窗口」。

发送窗口

大小取决于对端通告的接受窗口。
只有收到对端对于本端发送窗口内字节的 ACK 确认,才会移动发送窗口的左边界。

image-20210312100613062

流量控制

流量控制是一个端到端的问题。

拥塞控制

慢启动

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

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

这里假定拥塞窗口 cwnd 和发送窗口 swnd 相等,下面举个栗子:

  • 连接建立完成后,一开始初始化 cwnd = 1,表示可以传一个 MSS 大小的数据。

  • 当收到一个 ACK 确认应答后,cwnd 增加 1,于是一次能够发送 2 个

  • 当收到 2 个的 ACK 确认应答后, cwnd 增加 2,于是就可以比之前多发2 个,所以这一次能够发送 4 个

  • 当这 4 个的 ACK 确认到来的时候,每个确认 cwnd 增加 1, 4 个确认 cwnd 增加 4,于是就可以比之前多发 4 个,所以这一次能够发送 8 个。

  • image-20210312101316691

  • 可以看出慢启动算法,发包的个数是指数性的增长

    那慢启动涨到什么时候是个头呢?

    有一个叫慢启动门限 ssthresh (slow start threshold)状态变量。

    • cwnd < ssthresh 时,使用慢启动算法。

    • cwnd >= ssthresh 时,就会使用「拥塞避免算法」。

      拥塞避免算法

      前面说道,当拥塞窗口 cwnd 「超过」慢启动门限 ssthresh 就会进入拥塞避免算法。

      一般来说 ssthresh 的大小是 65535 字节。

      那么进入拥塞避免算法后,它的规则是:每当收到一个 ACK 时,cwnd 增加 1/cwnd。

      接上前面的慢启动的栗子,现假定 ssthresh8

    • 当 8 个 ACK 应答确认到来时,每个确认增加 1/8,8 个 ACK 确认 cwnd 一共增加 1,于是这一次能够发送 9 个 MSS 大小的数据,变成了线性增长。

      image-20210312101352992

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

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

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

      拥塞发生

      当网络出现拥塞,也就是会发生数据包重传,重传机制主要有两种:

    • 超时重传

    • 快速重传

      这两种使用的拥塞发送算法是不同的,接下来分别来说说。

      发生超时重传的拥塞发生算法

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

      这个时候,ssthresh 和 cwnd 的值会发生变化:

    • ssthresh 设为 cwnd/2

    • cwnd 重置为 1

      image-20210312101505353

      接着,就重新开始慢启动,慢启动是会突然减少数据流的。这真是一旦「超时重传」,马上回到解放前。但是这种方式太激进了,反应也很强烈,会造成网络卡顿。

      就好像本来在秋名山高速漂移着,突然来个紧急刹车,轮胎受得了吗。。。

      发生快速重传的拥塞发生算法

      还有更好的方式,前面我们讲过「快速重传算法」。当接收方发现丢了一个中间包的时候,发送三次前一个包的 ACK,于是发送端就会快速地重传,不必等待超时再重传。

      TCP 认为这种情况不严重,因为大部分没丢,只丢了一小部分,则 ssthreshcwnd 变化如下:

    • cwnd = cwnd/2 ,也就是设置为原来的一半;

    • ssthresh = cwnd;

    • 进入快速恢复算法

      快速恢复

      快速重传和快速恢复算法一般同时使用,快速恢复算法是认为,你还能收到 3 个重复 ACK 说明网络也不那么糟糕,所以没有必要像 RTO 超时那么强烈。

      正如前面所说,进入快速恢复之前,cwndssthresh 已被更新了:

    • cwnd = cwnd/2 ,也就是设置为原来的一半;

    • ssthresh = cwnd;

      然后,进入快速恢复算法如下:

    • 拥塞窗口 cwnd = ssthresh + 3 ( 3 的意思是确认有 3 个数据包被收到了);

    • 重传丢失的数据包;

    • 如果再收到重复的 ACK,那么 cwnd 增加 1;

    • 如果收到新数据的 ACK 后,把 cwnd 设置为第一步中的 ssthresh 的值,原因是该 ACK 确认了新的数据,说明从 duplicated ACK 时的数据都已收到,该恢复过程已经结束,可以回到恢复之前的状态了,也即再次进入拥塞避免状态;

      image-20210312101444359

image-20200415213302421

Welcome to my other publishing channels