滑动窗口打满是连接未断但数据停滞,表现为ss -i中rcv_wnd长期≤4096或归零、snd_wnd接近0且retrans不涨、rtt正常;根因是应用读取慢致接收缓冲区积压,触发内核缩窗保命机制。

滑动窗口打满不是“连接断了”,而是连接还在,但数据几乎不往前走——ss -i 里 rcv_wnd 长期卡在几KB、snd_wnd 接近0,且 retrans 不涨、rtt 正常,基本就能锁定是接收方窗口没开,发送方被堵死。
怎么一眼看出窗口是不是真打满了
别靠猜,用 ss 直接看实时窗口值:
-
ss -i state established '( dport = :8080 )'(替换成你的端口),重点盯rcv_wnd和snd_wnd字段 - 如果
rcv_wnd长期 ≤ 4096 或反复归零,说明接收方应用读得太慢,内核缓冲区堆满,被迫缩窗 - 如果
snd_wnd长期为 0 且retrans几乎不增加,大概率是发不出去,不是丢包,是对方不给窗口 - 对比
rtt:若 RTT 稳定在毫秒级(比如 0.2ms),但吞吐只有 MB/s 级,就是典型的“管道空转”——窗口太小,填不满带宽
为什么应用没崩溃,窗口却缩到几百字节
根本原因不是内核 bug,而是应用层没及时调用 recv() 或读缓冲区太小,导致内核 TCP 接收队列持续积压,触发自动缩窗保命机制:
- Linux 默认
net.ipv4.tcp_rmem中间值是 262144(256KB),但如果应用每次只recv(1024),又不批量读,队列就一直满着 - 内核看到接收缓冲区长期 ≥ 90%,会主动把通告窗口缩到最小(甚至 1 MSS),防止丢包
- 容器环境更危险:若容器内存受限,
sk_buff分配失败,也会强制压窗,此时dmesg | grep -i "tcp"可能出现Out of socket memory
快速验证和临时缓解手段
先不动配置,用最轻量方式确认问题归属:
- 在接收端执行
cat /proc/net/softnet_stat,第 1 列持续增长说明软中断处理不过来,可能是网卡收包太快、应用读太慢 - 用
strace -p $(pidof your_app) -e trace=recv,read看应用是否真的在读、每次读多少、有没有阻塞在recv() - 临时绕过:在发送端加
TCP_NODELAY(禁 Nagle)没用,但可尝试setsockopt(fd, IPPROTO_TCP, TCP_QUICKACK, &on, sizeof(on))加速 ACK 回复,缓解窗口更新延迟 - 终极验证:停掉接收端应用,立刻再发数据——如果发送端马上报
Connection reset by peer,说明之前不是网络问题,是对方应用卡住
调参前必须检查的三个硬条件
窗口调大 ≠ 自动变快,以下三点缺一不可,否则改了也白改:
-
net.ipv4.tcp_window_scaling必须为 1:用sysctl net.ipv4.tcp_window_scaling检查,为 0 就永远卡死在 64KB - 接收缓冲区上限
net.core.rmem_max必须 ≥ 你期望的最大窗口(比如想撑到 2MB,这里至少设成2097152) - 三次握手必须协商成功:抓包看 SYN 包里是否有
wscale选项,没有说明对端不支持或防火墙截断了选项字段
真正卡点往往不在参数本身,而在应用读取逻辑——窗口是“水池”,应用才是“抽水泵”。池子挖再大,泵不转,水照样不动。


















