TCP 缓冲区与窗口大小调优:发送缓冲、接收缓冲、滑动窗口
说实话,TCP 优化里最容易被忽视的就是缓冲区。很多人调了超时、改了拥塞算法,但数据还是跑不上去。我猜问题出在哪?缓冲区太小。
我自己在负责一个视频推流项目时遇到过:服务器明明带宽 1Gbps,CPU 也没瓶颈,但吞吐死活只有 300Mbps。抓包一看,窗口一直在 64KB 附近徘徊。原来是接收缓冲区默认值太小,把对端的发送窗口给掐住了。
所以这一章,咱们就彻底把发送缓冲、接收缓冲、滑动窗口的事聊透。你搞清楚这仨,TCP 性能至少可以翻倍。
发送缓冲区:数据上路的“蓄水池”
发送缓冲区就是内核里给每个 TCP 连接分配的一块内存。应用程序调用 send() 把数据丢进去,然后内核负责按 TCP 协议拆成段发出去。
- 作用:暂存待发送数据,让应用不必等网络。
- 满了会怎样?
send()阻塞或返回EAGAIN(非阻塞模式)。 - 太小:应用频繁进入等待,上下文切换暴增。
- 太大:内存浪费,且丢包时重传的数据量更多。
我习惯看一个指标:发送缓冲区里积压的数据量。如果积压长期接近缓冲区上限,就该考虑扩容了。
接收缓冲区:对端在等你收
接收缓冲区是对方数据抵达后的第一站。应用没及时读取数据,它们就躺在里面。接收缓冲区的大小直接影响本端通告的接收窗口 wscale。
为什么?因为 TCP 滑动窗口的右边界就是“接收缓冲区剩余空间”。
滑动窗口:流量控制的握手
滑动窗口不是物理缓冲区,它是一个“承诺”——“我还能收多少字节”。发送方根据这个承诺决定发多少,不能超过窗口大小。
这玩意有三层含义:
- 接收窗口 (rwnd):接收缓冲区剩余,告诉对端别发太快。
- 拥塞窗口 (cwnd):发送方自己估算的链路承载能力。
- 实际发送窗口 = min(rwnd, cwnd)。
你调发送缓冲/接收缓冲,最终就反映到 rwnd 上。而 rwnd 又通过 TCP 头里的 Window 字段传递。如果接收缓冲太小,那就算网络再好,也跑不起来。
一张图看透数据流
下面这张 SVG 图把发送缓冲、接收缓冲和滑动窗口的关系画清楚了。你看一眼就能记住。
从图里你能看到:数据从左到右经过发送缓冲区、TCP 分段(受滑动窗口约束)、网络、接收缓冲区,最终到应用。接收缓冲区剩余空间通过 ACK 包里的 Window 字段反馈回来,形成闭环。
实战参数调优:改多大才合适?
直接上干货。我总结了几个常见场景的缓冲区建议值,你直接拿去用。
| 场景 | 发送缓冲 | 接收缓冲 | 说明 |
|---|---|---|---|
| 通用 Web 服务 | 64KB | 64KB | 够用,兼顾内存 |
| 文件传输(局域网) | 256KB | 256KB | 配合大窗口减少 ACK 数量 |
| 视频直播(公网) | 512KB ~ 1MB | 512KB ~ 1MB | 按 BDP 算,延迟可忍受 |
| 数据库同步(低延迟) | 128KB | 128KB | 平衡内存与延迟 |
| 小包高频(如游戏) | 16KB ~ 32KB | 16KB ~ 32KB | 避免数据积压,降低延迟 |
动手改设置:代码来了
Linux 下有两种改法:系统级和 socket 级。系统级用 sysctl,socket 级用 setsockopt。
系统级调优(影响所有连接)
# 查看当前值
$ sysctl net.ipv4.tcp_rmem
net.ipv4.tcp_rmem = 4096 87380 6291456
$ sysctl net.ipv4.tcp_wmem
net.ipv4.tcp_wmem = 4096 16384 4194304
三个值分别是:最小值、默认值、最大值。单位是字节。我建议直接调整默认值和最大值:
# 设置接收缓冲(最小值 4KB,默认 128KB,最大 16MB)
$ sysctl -w net.ipv4.tcp_rmem="4096 131072 16777216"
# 设置发送缓冲(最小值 4KB,默认 64KB,最大 16MB)
$ sysctl -w net.ipv4.tcp_wmem="4096 65536 16777216"
Socket 级调优(针对特定连接)
int rcvbuf = 262144; // 256KB
setsockopt(sock, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf));
int sndbuf = 262144;
setsockopt(sock, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf));
SO_RCVBUF 设置值翻倍(因为内部也需要开销)。你设 256KB,实际会分配 512KB 的内核内存。别设太猛,否则内存撑爆。
避坑指南
- 别信默认值:Linux 默认接收缓冲只有 87KB(新版可能 128KB),对千兆网卡根本不够。
- 调了后看实际效果:用
ss -ti看wscale和rto,窗口要是没变大说明你设的没生效(可能没重启服务)。 - 动态调整 vs 固定值:Linux 支持自动调整窗口(tcp_moderate_rcvbuf),但我个人偏向根据业务固定,避免波动。
- 调发送缓冲要考虑重传:缓冲区太大,丢包后重传队列也大,可能加剧拥塞。通常设一次 RTT 内能发完的量就够了。
嗯,到这里你应该能明白:TCP 调优不是靠蒙。你理解了发送缓冲、接收缓冲和滑动窗口的关系,再配合合理的参数,性能提升是立竿见影的。我当年那个视频项目,调完缓冲区后从 300Mbps 直接干到 850Mbps,CPU 占用反而降了。这就是调对地方的效果。