TCP 为什么有时故意跑不快?拥塞控制与滑动窗口一次讲清

专栏: 《计算机网络基础》· 第三篇·传输层

承接: 《TCP 专章:三次握手之后,可靠管道里发生了什么?》

讲解维度: 概念 → 原理 → 应用 → 问题定位

读完你能: 分清 rwnd / cwnd;讲清慢启动与拥塞避免;理解 Reno / CUBIC / BBR 各自在怕什么;用 ss 等工具判断「慢在窗口还是慢在丢包」

导读:不是网卡坏了,是 TCP 在「踩刹车」

上一篇讲过:TCP 是可靠水管。但很多人会遇到这种体验------

复制代码
刚开始下载很快往上冲
    ↓
忽然变慢、一顿一顿
    ↓
过一会儿又慢慢加速

或者:

复制代码
Wi‑Fi 满格,网页却「半天才出来一块」
有线一插,世界清净了

第一反应常是:「带宽不够?服务器不行?」

有时确实如此;但还有一类非常常见的原因:

路径上出现了拥塞或丢包,TCP 的拥塞控制主动把发送速度降下来。

它不是在偷懒,而是在避免「大家一起把路堵死」。

本专题把 滑动窗口、流量控制、拥塞控制 放到一张图里讲透,再落到 Linux 上怎么观察。

一、概念:三扇「窗户」决定你能跑多欢

1.1 先分清两件怕的事

流量控制(Flow Control) 拥塞控制(Congestion Control)
怕谁忙不过来 接收方应用 / 接收缓冲 整条网络路径(路由器队列、瓶颈链路)
信号从哪来 对端通告的 rwnd(接收窗口) 丢包、重复 ACK、RTT 变化等(推断)
调什么 别把对方「灌满」 调本地的 cwnd(拥塞窗口)
类比 货架满了,别再送货 高速堵车了,大家一起减速

一句话:

复制代码
流量控制:护的是「对面那个人」
拥塞控制:护的是「中间那条路」

1.2 发送方真正能发多少?

概念层(忽略细节差异)可以记:

复制代码
发送窗口 ≈ min(rwnd, cwnd)

再加一个生活化量:

名词 人话
rwnd 对方说:我货架还剩这么大
cwnd 我认为:路上还能塞这么多「在途货」比较安全
飞行中的数据(bytes in flight) 已发出、还没被 ACK 确认的字节
BDP(带宽时延积) 理想情况下「管道里」大概能装多少:≈ 带宽 × RTT
复制代码
已确认          已发送未确认(in flight)        窗口内还可发
██████████░░░░░░░░░░░░░░░░░░░░│░░░░░░░░░░░░░░░░│
           ▲                  ▲
        左沿(SND.UNA)     右沿受 min(rwnd,cwnd) 限制

吞吐要上去,窗口通常得够大;但窗口乱开大,丢包和延迟会反噬。

拥塞控制,就是在这条钢丝上跳舞。

1.3 滑动窗口在干什么?

TCP 用 序号 + ACK + 窗口 实现「流水线发送」:不必一发一等,可以一口气发出一串,再根据 ACK 往前「滑」。

复制代码
时间 →

发: [1][2][3][4]......     (窗口允许范围内连续发)
收:     ACK2  ACK3  ACK4 ......
窗口右沿不断右移 → 可以继续发后面的

没有窗口,可靠传输会退化成「停等协议」,高速链路利用率极低。

有了窗口,才配得上「可靠水管」这个外号。

1.4 拥塞控制到底在优化什么?

目标可以记成两句矛盾的愿望:

  1. 尽量吃满可用带宽(别浪费)

  2. 尽量别把共享瓶颈挤爆(别害人害己)

互联网是共享的:你家路由、运营商汇聚、跨洋链路,都有队列。

队列一满 → 延迟升、开始丢包 → 大家重传 → 更堵。这就是经典的 拥塞崩溃 风险。

所以 TCP 选择:根据网络反馈,动态调整 cwnd。

二、原理:从「慢启动」到现代算法

2.1 总览:一条连接的速度曲线长什么样?

经典「丢包反馈」家族(Reno 一脉)的故事线:

复制代码
慢启动(指数涨)
    ↓  到门限 ssthresh
拥塞避免(大致线性涨)
    ↓  发生丢包 / 超时
猛踩刹车(窗口降下来)
    ↓
再爬坡......

cwnd
  ^
  │          /\      /\
  │         /  \    /  \
  │        /    \  /    \
  │   ____/      \/      \___
  │  /
  └──────────────────────────► 时间
     慢启动  拥塞避免  丢包后恢复

弱网网页「一顿一顿」,常常对应:丢包 → 降窗 → 重传 → 再慢启动/恢复

2.2 慢启动(Slow Start):名字叫慢,其实涨得很快

连接刚建立时,TCP 不知道 路径能扛多大。

策略:从较小的 cwnd 开始,每收到一轮成功的 ACK,大约把窗口翻倍(指数增长)------像试水温。

复制代码
cwnd: 1 → 2 → 4 → 8 → 16 → ...
(单位常以 MSS 段数理解;实现细节因栈而异)

为什么叫「慢」?相对「一上来就按链路带宽狂发」而言,它是保守试探;

但对用户体感,前几百毫秒往往是 涨速最快 的阶段。

退出慢启动的常见条件:

  • cwnd 涨到 ssthresh(慢启动门限)→ 进入拥塞避免

  • 或发生拥塞信号(丢包等)→ 调整 ssthresh / cwnd

2.3 拥塞避免(Congestion Avoidance):改成「探一步」

进入拥塞避免后,不再指数猛涨,而大致变成:

复制代码
每个 RTT,cwnd 大约 +1 个 MSS   (加法增)

这就是经典 AIMD 思想里的 AI(Additive Increase) 部分:

慢慢试探还有没有余量,避免一下子把路踩爆。

2.4 丢包了怎么办?超时 vs 快速重传

(1)超时重传(RTO)

很久都等不到 ACK → 可能网络很糟糕。

经典处理更狠:窗口降得很低,常常重新走「谨慎爬升」。

体感:卡很久、吞吐掉崖。

(2)快速重传(Fast Retransmit)

若连续收到 多个重复 ACK(常见阈值是 3 个 dup ACK),更像「某段丢了,但后面的段还在到」:

复制代码
发: 1 2 3 4 5
收: ACK2 ACK2 ACK2 ACK2 ...   ← 说明 2 之后有东西到了,但缺 2?
         ↑ 重复 ACK
→ 立刻重传可能丢失的段,而不必死等超时
(3)快速恢复(Fast Recovery)

配合快速重传:不必把速度打回「新生儿」,而是 减半一类策略后继续发 (Reno 故事线)。

这对应 AIMD 的 MD(Multiplicative Decrease):乘法减,加法增------对共享链路相对公平。

2.5 一张表记住 Reno 家族的「情绪」

阶段 cwnd 大致行为 心情
慢启动 指数升 我在摸路有多宽
拥塞避免 线性升 路好像还行,我轻轻加速
快速重传/恢复 降一批再继续 丢了一包,先让让
超时 降得很凶 路可能塌了,重来

注意:真实内核实现(CUBIC 等)细节更复杂,但 「探测 → 反馈 → 加/减」 的骨架不变。

2.6 Linux 上你更常听到的:CUBIC 与 BBR

CUBIC(长期常见的默认之一)
  • 仍属 以丢包为主要拥塞信号 的大类

  • 用三次函数曲线调窗,在 高带宽高延迟(长肥管道) 上往往比老 Reno 更会「填满管子」

  • 体感:大文件传输、跨洋链路更「敢提速」

BBR(Bottleneck Bandwidth and RTT)
  • 更关心 瓶颈带宽与最小 RTT,而不是「等到丢了再认怂」

  • 试图把发送节奏调到:接近瓶颈能力,同时别把队列堆得老高

  • 缓冲膨胀(bufferbloat) 场景常有更好延迟表现

  • 但在某些「礼貌丢包 / 特殊策略」环境里,行为可能和丢包系算法「合不来」------工程上要实测

    丢包系(Reno/CUBIC...): 路堵到「掉包」才强烈反应
    BBR 系: 尽量用带宽与 RTT 模型主动调速

2.7 和「重传」的关系:可靠 ≠ 不减速

拥塞控制和可靠性机制是搭档:

复制代码
丢包
 ├─ 可靠性:重传丢掉的数据(保证最终交付)
 └─ 拥塞控制:降低 cwnd(保护路径,也保护自己后续吞吐)

所以你会看到一种「反直觉」:

TCP 为了长期更快、更稳,会在短期故意跑慢。

三、应用:这些现象其实是拥塞控制在说话

3.1 下载刚开始的「爬坡」

大文件 / 视频首片:

复制代码
握手完成 → 慢启动爬坡 → 接近瓶颈 → 进入更平稳的相

短连接、小对象特别多时(老 HTTP/1.1 一堆小图):

每条连接都在爬坡,还没爬满就结束了 → 平均吞吐难看。

这也是后来连接复用、HTTP/2、QUIC 要解决的体感问题之一(下篇队头阻塞会再碰到)。

3.2 弱网「一顿一顿」

复制代码
Wi‑Fi 干扰 → 突发丢包
    → 快速重传 / 降窗
    → 页面渲染卡住一下
    → 恢复后又继续

对比实验很有用:同一服务,有线 vs Wi‑Fi

若有线丝滑、Wi‑Fi 锯齿,优先怀疑接入质量触发拥塞控制,而不是业务代码写错。

3.3 数据中心与公网的差异

环境 常见特点 对拥塞控制的含义
同机房内网 RTT 极低、带宽高 窗口很小也能跑高吞吐;算法差异有时不显眼
跨城 / 跨境 RTT 大 需要更大窗口才能填满 BDP;算法选择更关键
移动网络 带宽跳变、抖动大 丢包与延迟信号都「吵」,体感波动大

3.4 应用层能做什么?(边界意识)

应用通常 不直接改 cwnd,但可以:

  • 控制并发连接数与请求大小(别自己制造微突发洪水)

  • 选合适协议与库(HTTP/2、QUIC)

  • 做好超时与重试退避(避免重试风暴二次拥塞)

  • 服务端限流、队列、过载保护(保护的是「业务拥塞」)

    传输层拥塞控制:管「这条 TCP 连接别把网挤爆」
    应用过载保护: 管「这个服务别被请求挤爆」
    两者都叫「别堵死」,层级不同

四、问题定位:慢,是窗口小、丢包,还是对端不读?

4.1 先画决策树

复制代码
「传数据很慢 / 吞吐上不去」
        │
        ├─ 对端应用不 read? → 流量控制:rwnd 变小甚至 0(窗口关门)
        │
        ├─ 路径丢包 / 重传高? → 拥塞控制降速 + 重传耗时
        │
        ├─ RTT 很大且窗口不够? → 填不满 BDP(长肥管道)
        │
        └─ 本机 CPU / 磁盘 / 应用逻辑慢? → 根本不是网

4.2 推荐观察手段(Linux)

(1)看连接与重传相关信息
复制代码
# 概览
ss -s

# 看 TCP 连接细节(不同内核字段略有差异)
ss -ti dst <对端IP>

# 系统级重传统计(名称因发行版而异,可 man nstat)
nstat -az | grep -i retrans

直觉:

线索 可能含义
重传计数持续涨 路径质量差或拥塞
发送队列堆着、吞吐低 窗口/拥塞/对端处理
rwnd 很小 对端读得慢或缓冲压力
(2)抓包看 dup ACK / 重传
复制代码
sudo tcpdump -ni any host <对端IP> and tcp -v
# 或用 Wireshark:Expert 里看 Retransmission / Dup ACK

看到成串 Duplicate ACK 再出现重传,很符合「快速重传」剧本。

(3)路径质量
复制代码
ping <对端>
mtr -rwzbc 50 <对端>    # 若已安装

丢包、RTT 剧烈抖动,会直接驱动拥塞控制「情绪化」。

4.3 两个经典误判

误判 1:把流量控制当成拥塞

复制代码
接收方进程卡死 / 处理太慢
  → 通告 rwnd ↓
  → 发送方变慢
这是「对面货架满了」,不是「高速堵车」

误判 2:无限重试把路踩得更堵

复制代码
客户端超时重试风暴
  → 更多包进入已拥塞路径
  → 更丢、更重传、更超时
要退避(backoff),不要无脑重试

4.4 最小实验(建立体感)

在可控环境用 tc 制造延迟/丢包(需权限,注意别在生产乱来):

复制代码
# 示例:对某网卡加 50ms 延迟 + 1% 丢包(演示后记得删)
sudo tc qdisc add dev eth0 root netem delay 50ms loss 1%
# 用 iperf3 / 大文件下载观察吞吐锯齿
sudo tc qdisc del dev eth0 root

你会更直观地感到:同样「有带宽」,反馈一差,TCP 就会主动降速。

本章小结

复制代码
滑动窗口让可靠传输能「流水线」跑起来
发送受限 ≈ min(rwnd, cwnd)
流量控制护接收方;拥塞控制护路径
慢启动指数试探,拥塞避免加法爬升
丢包 → 快重传/恢复或超时猛降
CUBIC 等擅长大带宽;BBR 更看带宽与 RTT
弱网一顿一顿,常常是降窗与重传的合奏
排障:重传、RTT、rwnd、有线对比、抓包 dup ACK
相关推荐
杨某不才2 小时前
如何能让Linux服务器对shell 终端 + sftp 文件传输长期保活
linux·运维·服务器
三言老师3 小时前
文本工具组合统计服务器日志数据
linux·运维·服务器
m0_743697593 小时前
DNS服务器
运维·服务器
aixingkong9215 小时前
AI超节点Scale Up域总线各层优化设计
linux·服务器·网络
happymagic7 小时前
vmware workstation Pro 虚拟机设定启停的时间段
运维·服务器·windows
组合缺一7 小时前
Solon 的 10 种 HTTP 服务器:改一行依赖,换一个引擎
java·服务器·网络协议·http·solon
Zhang~Ling8 小时前
手写进程池:基于匿名管道的 Master-Slave 架构实战
linux·服务器·架构
coward918 小时前
千兆以太网卡(Ethernet)驱动初始化完成,但是无法udhcpc获取ip
服务器·网络·嵌入式硬件·tcp/ip
AI人工智能+电脑小能手8 小时前
【大白话说Java面试题 第211题】【10_网络协议篇】第2题:说一下什么是 HTTP 协议?
java·网络协议·http·https·web通信