欢迎光临
我们一直在努力

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 拥塞控制到底在优化什么?

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

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

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

  • 互联网是共享的:你家路由、运营商汇聚、跨洋链路,都有队列。
    队列一满 → 延迟升、开始丢包 → 大家重传 → 更堵。这就是经典的 拥塞崩溃 风险。

    所以 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

    赞(0)
    未经允许不得转载:171主机测评 » TCP 为什么有时故意跑不快?拥塞控制与滑动窗口一次讲清
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址