📜  拥塞控制的慢启动重启算法

📅  最后修改于: 2022-05-13 01:57:02.327000             🧑  作者: Mango

拥塞控制的慢启动重启算法

一旦客户端和服务器之间建立连接,发送方将数据以数据包的形式发送给接收方。如果网络拥塞,则数据包将被丢弃。当路由器等中间设备的缓冲区被填满时,就会发生这种情况。 TCP 发送者必须检测它是否已经填满了“中间设备的缓冲区”,如果是,则降低传输速率,以便网络可以摆脱“拥塞状态”。但是有一个问题是没有人将中间设备的“缓冲区”状态通知 TCP 发送方。

TCP 发送方自己“估计”中间设备缓冲区的状态,并为自己维护一个变量,称为拥塞窗口 (cwnd)。发送方永远不会发送价值超过其拥塞窗口大小的数据。假设 cwnd = 15 个数据包,那么发送方无法将 16 个数据包发送到网络中。在 TCP 发送方完成的这个“估计”说明了从 TCP 发送方到 TCP 接收方的“整个路径”。它不是对 TCP 发送方和 TCP 接收方之间“每个中间设备”的缓冲区占用率的估计。因为发送方无法知道中间设备的数量。此外,对于同一连接,数据包的路由可能会发生变化。不同的数据包可以选择不同的路由从发送方到达接收方。

那么,“估计过程”是如何从 TCP 发送方开始的呢? cwnd 的初始值是多少?答案是慢启动算法。

慢启动算法:

目的:开始“估计”cwnd 的过程,并迅速得出一个“体面的估计”。

算法

插图

最初,cwnd = initcwnd = 10 个段。发件人将 10 个段发送到航班中,而无需等待单个确认到达。
在每个 ACK 到达时,发送者将 cwnd 增加 1 个段并在网络中发送 2 个新段。

cwnd = 10 + 1 = 11,新的 cwnd 是 11,但现在只有 9 个段在飞行。发件人被允许在飞行中发送价值 cwnd 值的段。所以发件人在飞行中再发送 2 个段。

inflight = inflight – 1 + 2 // -1 因为我们收到了 ACK,+2 因为我们发送了 2 个新段
机上 = 10 – 1 = 9 + 2 = 11

因此,如果所有段都被成功确认,cwnd 将在一个 RTT 中“最终”加倍。所有 10 个 ACK 将在一个 RTT 时间内到达发送者,到那时 cwnd 将变为 20。现在有 20 个段在飞行中。

关键要点

每次 ACK 到达时,cwnd 都会以连续的方式增加。发送者在一个 RTT 之后不会将 cwnd 加倍,而是在每次新的 ACK 到达时逐渐增加 1。 TCP 发送方不等待“所有 10 个段”的 ACK 到达以增加 cwnd。这种行为会浪费网络带宽,因为在所有 ACK 和带宽都没有被利用之前不会发送新的段。

慢启动重启:

当 cwnd 达到 ssthresh 的最大值或检测到数据包丢失时,慢启动将终止,另一个算法将在此后处理。

但是,当 TCP 连接空闲一段时间(等于 RTO)时,发送方会使其对 cwnd 的估计无效,因为它有一段时间没有被更新。当发送方在空闲后的定义时间段后发送更多段时,它会重新启动慢启动算法以正确估计其拥塞窗口大小。

AIMD(加法增加乘法减少):

当发生丢包(即 TCP 发送方没有收到特定数据包的 ACK)或当算法达到“慢启动阈值”时,慢启动终止,AIMD 开始。在 TCP 连接开始时,ssthresh = ∞。发生丢包时更新 ssthresh。它被更新为 ssthresh = cwnd ÷ 2。当检测到数据包丢失时,AIMD 将 cwnd 减少 50%,并且每个 RTT 增加 1(不是每个 ACK,不像慢启动)。

如果重置 TCP 连接会出现什么问题?

TCP 发送方必须获得 cwnd 的“新估计”,这需要几个 RTT。示例:假设 TCP 连接由于空闲期而重置时 cwnd = 1024 个段。现在 TCP 发送方将 cwnd 初始化为 10 个段,并使用慢启动算法来增加 cwnd。 TCP 发送方需要 10 个 RTT(如果一个 RTT 为 100ms,则为 1000ms)将其 cwnd 变为 1024 个段!

Linux 命令:

借助以下命令在 Linux 内核中使用 SSR:

检查 Linux 内核中 SSR 的默认设置

$ sysctl net.ipv4.tcp_slow_start_after_idle

预期输出(这意味着启用了 SSR):

$ net.ipv4.tcp_slow_start_after_idle = 1

禁用慢启动重启(对服务器很重要,对客户端不重要)

$ sudo sysctl -w net.ipv4.tcp_slow_start_after_idle=0

预期输出:

$ net.ipv4.tcp_slow_start_after_idle = 0