网络-TCP(2)

152 阅读9分钟

TCP 重传

在 TCP 中,当发送端的数据到达接收主机时,接收端主机会返回一个确认应答消息,表示已收到消息

常见的重传机制:

  • 超时重传
  • 快速重传
  • SACK
  • D-SACK

超时重传

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

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

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

RTT 指的是数据发送时刻到接收到确认的时刻的差值,也就是包的往返时间

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

Linux 是如何计算 RTO 的呢?

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

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

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

快速重传

不以时间为驱动,而是以数据驱动重传

发送方发出了 1,2,3,4,5 份数据:

  • 第一份 Seq1 先送到了,于是就 Ack 回 2;
  • 结果 Seq2 因为某些原因没收到,Seq3 到达了,于是还是 Ack 回 2;
  • 后面的 Seq4 和 Seq5 都到了,但还是 Ack 回 2,因为 Seq2 还是没有收到;
  • 发送端收到了三个 Ack = 2 的确认,知道了 Seq2 还没有收到,就会在定时器过期之前,重传丢失的 Seq2。
  • 最后,收到了 Seq2,此时因为 Seq3,Seq4,Seq5 都收到了,于是 Ack 回 6

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

快速重传存在的问题:重传的时候,是重传一个,还是重传所有的问题

SACK 方法

SACK( Selective Acknowledgment), 选择性确认

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

Duplicate SACK

使用了 SACK 来告诉「发送方」有哪些数据被重复接收

滑动窗口

TCP 是每发送一个数据,都要进行一次确认应答。当上一个数据包收到了应答了, 再发送下一个,这样的传输方式有一个缺点:数据包的往返时间越长,通信的效率就越低

窗口大小就是指无需等待确认应答,而可以继续发送数据的最大值

窗口的实现实际上是操作系统开辟的一个缓存空间,发送方主机在等到确认应答返回之前,必须在缓冲区中保留已发送的数据,如果按期收到确认应答,此时数据就可以从缓存区清除

假设窗口大小为 3 个 TCP 段,那么发送方就可以「连续发送」 3 个 TCP 段

窗口大小由哪一方决定

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

发送方的滑动窗口 image.png

  • #1 是已发送并收到 ACK确认的数据:1~31 字节
  • #2 是已发送但未收到 ACK确认的数据:32~45 字节
  • #3 是未发送但总大小在接收方处理范围内(接收方还有空间):46~51字节
  • #4 是未发送但总大小超过接收方处理范围(接收方没有空间):52字节以后

流量控制

发送方不能无脑的发数据给接收方,要考虑接收方处理能力,如果一直无脑的发数据给对方,但对方处理不过来,那么就会导致触发重发机制,为了解决这种现象发生,TCP 提供一种机制可以让「发送方」根据「接收方」的实际接收能力控制发送的数据量,这就是所谓的流量控制

TCP 通过让接收方指明希望从发送方接收的数据大小(窗口大小)来进行流量控制

如果窗口大小为 0 时,就会阻止发送方给接收方传递数据,直到窗口变为非 0 为止,这就是窗口关闭

窗口关闭潜在的危险:

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

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

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

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

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

拥塞控制

流量控制是避免「发送方」的数据填满「接收方」的缓存,但是并不知道网络的中发生了什么

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

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

拥塞窗口 cwnd是发送方维护的一个的状态变量,它会根据网络的拥塞程度动态变化的

发送窗口 swnd 和接收窗口 rwnd 是约等于的关系,那么由于加入了拥塞窗口的概念后,此时发送窗口的值是swnd = min(cwnd, rwnd),也就是拥塞窗口和接收窗口中的最小值

拥塞窗口 cwnd 变化的规则:

  • 只要网络中没有出现拥塞,cwnd 就会增大;
  • 但网络中出现了拥塞,cwnd 就减少

拥塞的判断:

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

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

  • 慢启动
  • 拥塞避免
  • 拥塞发生
  • 快速恢复

慢启动

慢启动的意思就是一点一点的提高发送数据包的数量,当发送方每收到一个 ACK,拥塞窗口 cwnd 的大小就会加 1

举例:

  • 连接建立完成后,一开始初始化 cwnd = 1,表示可以传一个 MSS 大小的数据。
  • 当收到一个 ACK 确认应答后,cwnd 增加 1,于是一次能够发送 2 个
  • 当收到 2 个的 ACK 确认应答后, cwnd 增加 2,于是就可以比之前多发2 个,所以这一次能够发送 4 个
  • 当这 4 个的 ACK 确认到来的时候,每个确认 cwnd 增加 1, 4 个确认 cwnd 增加 4,于是就可以比之前多发 4 个,所以这一次能够发送 8 个

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

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

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

拥塞避免

每当收到一个 ACK 时,cwnd 增加 1/cwnd

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

拥塞发生

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

  • 超时重传
  • 快速重传

超时重传的现象:

ssthresh 和 cwnd 的值会发生变化:

  • ssthresh 设为 cwnd/2
  • cwnd 重置为 10 (是恢复为 cwnd 初始化值)

这种方式太激进了,反应也很强烈,会造成网络卡顿

快速重传的现象:

  • cwnd = cwnd/2 ,也就是设置为原来的一半;
  • ssthresh = cwnd;
  • 进入快速恢复算法

快速恢复

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

进入快速恢复算法如下:

  • 拥塞窗口 cwnd = ssthresh + 3 ( 3 的意思是确认有 3 个数据包被收到了);
  • 重传丢失的数据包;
  • 如果再收到重复的 ACK,那么 cwnd 增加 1;
  • 如果收到新数据的 ACK 后,把 cwnd 设置为第一步中的 ssthresh 的值,原因是该 ACK 确认了新的数据,说明从 duplicated ACK 时的数据都已收到,该恢复过程已经结束,可以回到恢复之前的状态了,也即再次进入拥塞避免状态