飞翔飞翔
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
  • 数据库

    • SQL教程
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON教程
  • 工具

    • Markdown指南
  • Git

    • GitFlow
  • Quartz

    • Quartz教程
  • Java

    • Java设计模式
  • 缓存

    • Redis教程
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
  • 数据库

    • SQL教程
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON教程
  • 工具

    • Markdown指南
  • Git

    • GitFlow
  • Quartz

    • Quartz教程
  • Java

    • Java设计模式
  • 缓存

    • Redis教程
联系
阿里云
  • 学习路径
  • IP协议

    • 认识 IP 协议与网络层定位
    • IPv4 编址体系与分类地址
    • CIDR 与子网划分实战
    • IPv6 编址体系
    • ARP 协议详解
    • NDP 协议详解
    • IGMP 与 MLD 组播侦听发现
    • VRRP 与网关冗余
    • IPv4 数据报首部解析
    • IPv6 数据报与扩展首部
    • IPsec 安全扩展
    • 分片、MTU 与路径发现
    • ICMP 与 Traceroute 原理
    • 路由基础与转发流水线
    • 动态路由协议
    • NAT 网络地址转换
    • DHCP 与自动配置
    • Wireshark 与命令行工具
    • IPv6 过渡技术
    • IP 协议栈排障与攻击防御
  • TCP协议

    • 认识 TCP 协议与传输层定位
    • TCP 报文段首部解析
    • 三次握手与连接建立
    • 四次挥手与连接释放
    • TCP 有限状态机
    • 序列号与确认机制
    • 超时重传与 RTO 计算
    • 滑动窗口与流量控制
    • 拥塞控制基础
    • 现代拥塞控制算法
    • TCP 选项与扩展
    • TCP 性能调优与内核参数
    • Nagle 算法与糊涂窗口综合征
    • TCP 定时器与 Keep-Alive 机制
    • TCP 安全与攻击防御
    • TCP 与上层/下层交互
    • TCP 综合实践与排障

Nagle 算法与糊涂窗口综合征

小数据包问题

交互式应用(如 SSH、Telnet)每次按键只产生 1 字节数据。如果每个字节都单独封装成 TCP 报文:

IP 首部 20 字节 + TCP 首部 20 字节 + 数据 1 字节 = 41 字节
有效载荷占比 = 1 / 41 ≈ 2.4%

大量小报文会:

  • 消耗带宽(首部开销大)
  • 增加路由器/交换机处理负担
  • 引发 ACK 风暴(每个小报文触发一个 ACK)

Nagle 算法(RFC 896)

Nagle 算法于 1984 年提出,核心规则:

如果连接上有已发送但未确认的数据,则推迟发送小数据段,直到:

  1. 已发送的数据收到 ACK,或
  2. 累积的数据达到 MSS

效果:

  • 将多个小数据合并为一个大段发送
  • 保证任何时刻最多只有一个未确认的小段在途
  • 典型场景下减少 40% 的小报文数量

Nagle 算法的副作用:延迟

Nagle 算法与延迟 ACK 结合时,会产生显著的交互延迟。

延迟 ACK(Delayed ACK)

接收方不立即回复 ACK,而是:

  • 等待最多 40ms(Linux 典型值)
  • 或等待有数据要发送时"捎带"ACK
  • 目标:减少纯 ACK 报文数量

死锁场景

典型症状:

  • SSH 按键后明显卡顿
  • 游戏操作延迟
  • 实时通信(WebSocket)响应慢

解决方案:TCP_NODELAY

int on = 1;
setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on));

TCP_NODELAY 禁用 Nagle 算法,应用层数据立即发送,不等待 ACK。

何时禁用 Nagle:

  • 实时交互应用(游戏、SSH、交易终端)
  • 小数据频繁发送且对延迟敏感
  • 应用层已自行合并数据(如批量写入)

何时保留 Nagle:

  • 普通文件传输
  • 应用层写入频繁但数据量小
  • 带宽受限的网络

糊涂窗口综合征(Silly Window Syndrome, SWS)

SWS 是流量控制层面的问题:接收方通告极小的窗口(如 1 字节),发送方据此发送 1 字节数据,导致大量小报文。

产生原因

接收方应用层缓慢读取数据:

  1. 接收缓冲区满,通告窗口 = 0
  2. 应用层读取 1 字节,缓冲区空出 1 字节
  3. 接收方通告窗口 = 1
  4. 发送方发送 1 字节数据
  5. 重复步骤 2-4,每次只传 1 字节

解决方案

接收方策略(Clark 方案):

  • 窗口增加量 ≥ MSS 或缓冲区空出一半时,才通告新窗口
  • 避免通告微小窗口增量

发送方策略(Nagle 算法的延伸):

  • 发送方也不应发送小于 MSS 的数据段,除非:
    • 已有数据等待 ACK(Nagle 条件)
    • 或可以发送一个满段

Nagle 与 SWS 的关系

机制作用位置解决什么问题关键策略
Nagle 算法发送方小数据包过多合并小数据,最多一个未确认小段
延迟 ACK接收方ACK 报文过多延迟 40ms 或捎带 ACK
SWS 避免双方微小窗口导致小报文接收方不通告小窗口,发送方不发送小段

三者协同:

  • Nagle 减少发送方小报文
  • 延迟 ACK 减少 ACK 报文
  • SWS 避免避免窗口通告导致的小报文

冲突:Nagle + 延迟 ACK 导致交互延迟,需 TCP_NODELAY 解决。

本篇小结

  • 小数据包问题:1 字节数据 + 40 字节首部,效率极低
  • Nagle 算法:有未确认数据时缓冲小段,收到 ACK 或满 MSS 再发
  • 延迟 ACK:接收方等 40ms 或捎带 ACK,减少纯 ACK 数量
  • Nagle + 延迟 ACK 死锁:双方互相等待,导致交互延迟
  • TCP_NODELAY:禁用 Nagle,适合实时应用
  • 糊涂窗口综合征:接收方通告极小窗口,导致每次只传 1 字节
  • SWS 避免:接收方等窗口增到 MSS 或缓冲空一半才通告

动手实践

  1. 观察 Nagle 算法效果:

    # 服务端:nc -l 8080
    # 客户端:逐字符发送,抓包观察是否合并
    
  2. 用 Wireshark 观察延迟 ACK:

    • 过滤 tcp.analysis.ack_rtt
    • 观察 ACK 是否在数据到达后 40ms 才发出
  3. 对比启用/禁用 Nagle 的延迟:

    import socket
    
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)  # 禁用 Nagle
    
  4. 思考:为什么 HTTP/1.1 的 Keep-Alive 连接通常不禁用 Nagle,而 WebSocket 连接通常禁用?两者在数据发送模式上有什么区别?

上一页
TCP 性能调优与内核参数
下一页
TCP 定时器与 Keep-Alive 机制