1. 校验和
  2. 序列号
  3. 确认应答
  4. 超时重传
  5. 流量控制
  6. 拥塞控制

    校验和

    序列号 + 确认应答

    TCP 将每个字节的数据都进行了编号,这个编号就叫序列号。根据序列号可以将收到的数据进行排序,还可以去掉重复收到的数据,并且在确认应答上也有很大的作用
    接收方收到数据后,会发送 ACK 报文回应自己的接收情况,通过确认应答号告诉发送方下一个要发送的数据包
    例:
  • 发送方 -> 接收方:数据(1 ~ 2000)
  • 接收方 -> 发送方:ack = 1,ack num = 2001(表示前面的数据都收到了,并没有丢失,下一次可以发送序列号为 2001 的数据了)
  • 发送方 -> 接收方:数据(2001 ~ 3000)
  • 接收方 -> 发送方:ack = 1, ack num = 2111(表示只收到了序列号为 2001 ~ 2110 的数据,发生了数据丢失,下一次得重发序列号 2111 开始的数据)

除了接收方没有收到相应数据时发送方会重发数据,当发送方没有收到应答包时也会重发数据

超时重传

发生超时重传的情况:数据包丢失、确认应答包丢失
数据包从发送方到接收方的时间 + 应答包从接收方到发送方的时间 = 往返时间(RTTRound-trip Time
当超过了这个时间(RTT)还没有收到应答包,那么就会触发超时重传,故而超时重传时间(RTORetransmission Timeout)> RTT

超时重传时间的确定

  1. 一般来说,RTT 大体上不会相差太多,但是由于网络的原因,RTT 会波动,时间会有偏差,这一偏差成为抖动,即方差,所以说 RTO 会略大于 RTT 的平均值 + 抖动值,并且是一个动态变化的值
  2. 每当遇到一次超时重传,会将下一次超时重传时间设为之前的 2 倍,如果经历了两次超时重传则表示网络拥堵,此时不适合频繁发送数据。在重发的过程中,假如一个包经过多次的重传也没有收到对方的确认应答包,那么就会认为接收端异常,强制关闭连接,并且通知应用,通信异常强行终止

    流量控制

    滑动窗口的概念

    滑动窗口本身是为了提高通信效率而引入的(原本传输数据时发送方需要接收到接收方发来的应答包之后才能继续发送数据,这样通信效率低下),滑动窗口的大小就是在无需等待确认应答包的情况下,能够发送数据的最大值,在 TCP 头中有一个 Window 字段用来表示滑动窗口大小

    窗口大小是如何决定的

    窗口大小一般由接收方的窗口大小来决定,接收方通过 Window 字段告诉发送方自己还有多少缓冲区可以接收数据,然后发送方就可以根据接收方的处理能力来调整发送速度。发送方的窗口大小不能大于接收方的窗口大小,否则会造成有部分数据接收方无法正常接收,可能还会导致网络拥堵
    流量控制机制:根据接收方的处理能力来调整发送速度,具体实现如上所说,接收方通过 TCP 头部的 Window(窗口大小) 字段发送 ACK 报文告诉发送方自己还有多少缓冲区可以接收数据。并且通过确认应答包可以判断接收方是否已经接收到了数据,如果已经接收了就从缓冲区里面删除数据,这样发送方的窗口的可用空间又变大了

    拥塞控制

    流量控制和拥塞控制的主要区别

  • 流量控制:避免发送方发送的数据填满 接收方的缓存区
  • 拥塞控制:避免发送的数据填满 网络

    拥塞窗口cwnd

    拥塞窗口是发送方维护的一个状态变量,发送窗口swnd约等于接收窗口rwnd,由于又引入了拥塞窗口,所以swnd = min(cwnd, rwnd),拥塞窗口会随着网络情况而变化,会因为网络拥塞而减小,而在没有网络拥塞的时候又会增大

    如何判断是否发生网络拥塞

    发送方在发送数据时,需要根据接收方的缓存区剩余空间来调整发送速率,所以需要有一定的感知能力。发送方一般是基于 丢包 来判断当前网络是否发生拥塞,丢包可以由 超时重传 重复确认 来做判断

拥塞控制基本策略分为四个算法:慢启动、拥塞避免、拥塞发生阶段的算法、快速恢复

慢启动(SSSlow Start

算法规则:发送方每收到一个 ACK,那么cwnd就 + 1
例(这里的cwnd的初始化值只是假设为 1):

  • 一开始初始化cwnd为 1,发送方只能发送 1 个数据包
  • 收到一个 ACK 后,cwnd+ 1 = 2,这时可以发送 2 个数据包
  • 收到两个 ACK 后,cwnd + 2 = 4,这时可以发送 4 个数据包
  • 收到四个 ACK 后,cwnd + 4 = 8,这时可以发送 8 个数据包

从以上过程可以看出,慢启动阶段cwnd指数增长
指数增长得很快,自然不能一直呈指数增长,那么什么时候才会结束慢启动算法?
慢启动阶段维护了一个变量慢启动门限ssthreshSlow Start Threshold),当拥塞窗口cwnd<ssthresh时,使用慢启动算法,当cwnd>=ssthresh时,那么就采用拥塞避免算法
一般来说,ssthresh的大小为 65535 字节

拥塞避免(CACongestion Avoidance

算法规则:发送方每收到一个 ACK 确认应答,那么cwnd就 + 1/cwnd
假设ssthresh = 8,那么刚开始时cwnd= 8,接下来的过程如下:

  • 收到 8 个 ACK,cwnd + (1 / 8)* 8 = 9,这时可以发送 9 个数据包
  • 收到 9 个 ACK,cwnd + (1 / 9)* 9 = 10,这时可以发送 10 个数据包
  • 收到 10 个 ACK,cwnd + (1 / 10) * 10 = 11,这时可以发送 11 个数据包

从以上过程可以看出,拥塞避免阶段cwnd线性 增长,依然还是在增长,只是增长速度变慢了
随着cwnd的继续增长,网络状况就会慢慢进入堵塞状态

拥塞发生时的算法

当拥塞发生时,有 2 种算法来进行处理:超时重传、快速重传

超时重传的拥塞状态算法

算法规则:ssthresh->cwnd / 2cwnd->系统里设置的cwnd初始化值
在 Linux 中,可以通过ss -nli | grep cwnd命令查看每一个 TCP 连接的 cwnd 初始化值
假设系统设置的cwnd初始化值为 1,且当cwnd = 14,ssthresh= 8 时,网络发生了堵塞,那么根据算法规则,ssthresh= 14 / 2 = 7,cwnd = 1,接着cwnd就继续从 1 开始增长了
超时重传的拥塞状态算法会突然减少大量的数据流,一切从头开始,继续根据慢启动算法来增长,这样突然的情况很可能会导致网络卡顿,所以并不是特别好的方法

快速重传的拥塞状态算法

快速重传:当接收端发现丢包时,就会发送前一个包的 ACK,当发送方收到三次重复的 ACK 后,就会认为这个数据包已丢,就会立刻重传,而不会再等待超时重传
快速重传的拥塞状态算法相对超时重传的拥塞状态算法来说较好
算法规则:cwnd=cwnd / 2ssthresh=cwnd,接着就进入快速恢复算法

快速恢复(FRFast Recovery

快速恢复算法一般与快速重传一起使用,快速恢复算法认为,快速重传时既然还可以收到三个相同的 ACK,那么网络情况还不是很严重,没有必要像超时重传那样直接从慢启动开始
算法规则:

  1. cwnd = cwnd + 3+ 3是因为收到 3 个重复的 ACK)
  2. 重发丢失的数据包
  3. 每当收到一个重复的 ACK,cwnd = cwnd + 1
  4. 收到新数据包的 ACK 后,cwnd = ssthresh(原因:收到新数据的 ACK 意味着丢失的数据已经收到了,那么恢复过程可以结束了,可以恢复到之前的状态,就是拥塞避免的状态,继续线性增长cwnd

    具体过程图示