万字详文:TCP 拥塞控制详解

本文主要介绍 TCP 拥塞控制算法,内容多来自网上各个大佬的博客及《TCP/IP 详解》一书,在此基础上进行梳理总结,与大家分享。因水平有限,内容多有不足之处, 敬请谅解。

一、TCP 首部格式

在了解 TCP 的拥塞控制之前,先来看看 TCP 的首部格式和一些基本概念。

TCP 头部标准长度是 20 字节。包含源端口、目的端口、序列号、确认号、数据偏移、保留位、控制位、窗口大小、校验和、紧急指针、选项等。

1.1 数据偏移(Data Offset)

该字段长 4 位,单位为 4 字节。表示为 TCP 首部的长度。所以 TCP 首部长度最多为 60 字节。

1.2 控制位

目前的 TCP 控制位如下,其中 CWR 和 ECE 用于拥塞控制,ACK、RST、SYN、FIN 用于连接管理及数据传输。

CWR:用于 IP 首部的 ECN 字段。ECE 为 1 时,则通知对方已将拥塞窗口缩小。
ECE:在收到数据包的 IP 首部中 ECN 为 1 时将 TCP 首部中的 ECE 设置为 1,表示从对方到这边的网络有拥塞。
URG:紧急模式
ACK:确认
PSH:推送,接收方应尽快给应用程序传送这个数据。没用到
RST:该位为 1 表示 TCP 连接中出现异常必须强制断开连接。
SYN:初始化一个连接的同步序列号
FIN:该位为 1 表示今后不会有数据发送,希望断开连接。

1.3 窗口大小(Window)

该字段长度位 16 位,即 TCP 数据包长度位 64KB。可以通过 Options 字段的 WSOPT 选项扩展到 1GB。

1.4 选项(Options)

受 Data Offset 控制,长度最大为 40 字节。一般 Option 的格式为 TLV 结构:

常见的 TCP Options 有,SACK 字段就位于该选项中。:

1.5 SACK 选项

SACK 包括了两个 TCP 选项,一个选项用于标识是否支持 SACK,是在 TCP 连接建立时发送;另一种选项则包含了具体的 SACK 信息。

 

资料领取直通车:大厂面试题锦集+视频教程

Linux服务器学习网站:C/C++Linux服务器开发/后台架构师

  1. SACK_Permitted 选项,该选项只允许在 TCP 连接建立时,有 SYN 标志的包中设置,也即 TCP 握手的前两个包中,分别表示通信的两方各自是否支持 SACK。
TCP SACK-Permitted Option:
Kind: 4
Length: Variable
+----------+----------+
| Kind=4   | Length=2 |
+----------+----------+
  1. SACK(选择性确认) 选项位于 Options 中。该选项参数告诉对方已经接收到并缓存的不连续的数据块,发送方可根据此信息检查究竟是哪些块丢失,从而发送相应的数据块。受 TCP 包长度限制,TCP 包头最多包含四组 SACK 字段。
TCP SACK Option:
Kind: 5
Length: Variable
                +--------+--------+
                                | Kind=5 | Length |
              +--------+--------+--------+--------+
              |      Left Edge Of lst Block       |
              +--------+--------+--------+--------+
              |     Right Edge Of lst Block       |
              +--------+--------+--------+--------+
              |                   .  .  .         |
              +--------+--------+--------+--------+
              |      Left Edge Of nth Block       |
              +--------+--------+--------+--------+
              |     Right Edge Of nth Block       |
          +--------+--------+--------+--------+
  1. SACK 的工作原理
    如下图所示, 接收方收到 500-699 的数据包,但没有收到 300-499 的数据包就会回 SACK(500-700) 给发送端,表示收到 500-699 的数据。

二、滑动窗口和包守恒原则

2.1 滑动窗口

为了解决可靠传输以及包乱序的问题,TCP 引入滑动窗口的概念。在传输过程中,client 和 server 协商接收窗口 rwnd,再结合拥塞控制窗口 cwnd 计算滑动窗口 swnd。在 Linux 内核实现中,滑动窗口 cwnd 是以包为单位,所以在计算 swnd 时需要乘上 mss(最大分段大小)。

swnd=min(rwnd,cwnd∗mss)

如下图所示滑动窗口包含 4 部分:

  • 已收到 ack 确认的数据;
  • 已发还没收到 ack 的;
  • 在窗口中还没有发出的(接收方还有空间);
  • 窗口以外的数据(接收方没空间)。

滑动后的示意图如下(收到 36 的 ack,并发出了 46-51 的数据):

2.2 包守恒原则

TCP 维护一个发送窗口,估计当前网络链路上能容纳的数据包数量,希望在有数据可发的情况下,回来一个确认包就发出一个数据包,总是保持发送窗口那么多包在网络中流动。

传输的理想情况是要同时达到最大的吞吐量和最小的往返延迟,要达到这个目的,连接必须同时满足两个条件:

  • 以链路瓶颈带宽 BtlBw 发包 (带宽利用率最高)
  • 保证链路中没有缓存队列(延迟最低)

包守恒原则是拥塞控制的基础。

三、TCP 重传机制

本文重点介绍 TCP 拥塞控制相关,传输流程不在该范围之内,有兴趣的同学可以查阅相关文档。不过 TCP 重传逻辑和拥塞控制中的快速重传有关,所以在真正介绍拥塞控制算法之前,先来了解下 TCP 重传逻辑。

3.1 超时重传 [RFC2988]

RTT(Round Trip Time)由三部分组成:链路的传播时间(propagation delay)、末端系统的处理时间、路由器缓存中的排队和处理时间(queuing delay)。TCP 发送端得到了基于时间变化的 RTT 测量值,就能据此设置 RTO。

RTO=γ∗RTO

当一个重传报文段被再次重传时,则增大 RTO 的退避因子 γ 。通常情况下 γ 值为 1,多次重传 γ 加倍增长为 2,4,8 等。通常 γ 不能超过最大退避因子,Linux 下 RTO 不能超过 TCP_RTO_MAX(默认为 120s)。一旦收到相应的 ACK, γ 重置为 1。

下面介绍几种常用的 RTT 算法。

3.1.1 rtt 经典算法 [RFC793]

1)首先,先采样 RTT,记下最近几次的 RTT 值。 2)然后做平滑计算 SRTT( Smoothed RTT)。公式为:(其中的 α 取值在 0.8 到 0.9 之间,这个算法英文叫 Exponential weighted moving average,中文叫:加权移动平均)

SRTT=(α∗SRTT)+((1−α)∗RTT)

3)开始计算 RTO。公式如下:

RTO=min[UBOUND,max[LBOUND,(β∗SRTT)]]

其中:

  • UBOUND 是最大的 timeout 时间,上限值;
  • LBOUND 是最小的 timeout 时间,下限值;
  • β 值一般在 1.3 到 2.0 之间。

该算法的问题在于重传时,是用重传的时间还是第一次发数据的时间和 ACK 回来的时间计算 RTT 样本值,另外,delay ack 的存在也让 rtt 不能精确测量

3.1.2 rtt 标准算法(Jacobson / Karels 算法)

该算法 [RFC6298] 特点是引入了最新的 RTT 的采样rtts和平滑过的srtt的差值做参数来计算。 公式如下: 1.计算平滑 RTT

srtt=srtt+α(rttssrtt)

2.计算平滑 RTT 和真实的差距(加权移动平均)

rttvar=(1−β)∗rttvar+β∗(|rtts−srtt|)

3.计算 RTO

rto=u∗srtt+∂∗rttvar

4.考虑到时钟粒度,给 RTO 设置一个下界。

rto=max(u∗srtt+max(G,∂∗rttvar),1000)

这里G为计时器粒度,1000ms 为整个 RTO 的下届值。因此 RTO 至少为 1s。在 Linux 下,α = 0.125,β = 0.25, μ = 1,∂ = 4 ——这就是算法中的“调得一手好参数”,nobody knows why, it just works…)(Linux 的源代码在:tcp_rtt_estimator)。

5.在首个 SYN 交换前,TCP 无法设置 RTO 初始值。根据 [RFC6298],RTO 初始值为 1s,而初始 SYN 报文段采用的超时间隔为 3s。当计算出首个 RTT 测量结果 rtts,则按如下方法进行初始化:

srtt=rttsrttvar=rtts/2

3.1.3 Karn 算法

在 RTT 采样测量过程中,如果一个数据包初传后,RTO 超时重传,接着收到这个数据包的 ACK 报文,那么这个 ACK 报文是对应初传 TCP 报文还是对应重传 TCP 报文呢?这个问题就是重传二义性的问题。当没有使用 TSOPT 选项,单纯的 ACK 报文并不会指示对应初传包还是重传包,因此就会发生这个问题。 TCP Karn 算法是对经典算法在重传二义性问题上的一种改进。该算法分两部分。 1) 当出现超时重传,接收到重传数据的确认信息时不更新 RTT。即忽略了重传部分,避免重传二义性问题。 2) 对该数据之后的包采取退避策略,仅当接收到未经重传的数据时,该 SRTT 才用于计算 RTO。

因为单纯忽略重传数据时,如果在某一时间,网络闪动,突然变慢了,产生了比较大的延时,这个延时导致要重转所有的包(因为之前的 RTO 很小),但因为重转的不算,RTO 就不会被更新,这是个灾难。而且一发生重传就对现有的 RTO 值翻倍的方式也很难估算出准确的 RTT。

3.1.4 T

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值