再多来点 TCP 吧:Delay ACK 和 Nagle 算法
内容提要
教科书介绍的TCP内容通常比较基础,但实际应用中发现TCP要复杂一些。TCP结束连接不一定要4次挥手,可以在同一个Segment中设置FIN和ACK。Delay ACK可以降低纯ACK包的数量,但会造成延迟。Nagle's Algorithm解决了发送小数据包的问题。同时使用Delay ACK和Nagle's Algorithm会导致问题。关闭Nagle's Algorithm和Delay ACK的方法是设置TCP_NODELAY和TCP_QUICKACK。
延伸解读
Delay ACK 与 Nagle 算法为何冲突
Delay ACK 让接收方推迟发送 ACK,希望将 ACK 与后续数据合并;Nagle 算法让发送方在未收到 ACK 前不发送小包。两者同时启用时,发送方等待 ACK 才发下一段数据,而接收方等待应用层响应才发 ACK,形成类似死锁的等待,导致 HTTP 请求等场景出现不合理延迟。
关闭机制的适用场景
关闭 Nagle 算法可设置 TCP_NODELAY,使 write 直接发送数据,适合交互式或低延迟应用。关闭 Delay ACK 可设置 TCP_QUICKACK,使收到 TCP segment 后立即 ACK。但 TCP_QUICKACK 并非永久生效,且现代 Linux 默认关闭 Delay ACK,调整前需确认系统行为。
从抓包看 TCP 实现的灵活性
文章通过 tcpdump 示例显示,TCP 结束连接时 FIN 和 ACK 可合并在同一 Segment 中,因此不一定是教科书式的四次挥手。这提醒读者,实际 TCP 行为可能因实现和场景而异,理解协议字段的独立组合有助于准确分析网络交互。
Q&A
TCP 关闭连接时,为什么有时只看到 3 次挥手而不是 4 次?
因为 FIN 和 ACK 是不同的标志位,可以在同一个 TCP 段中同时设置。当一端发送 FIN 后,另一端如果也没有数据要发送,就可以在 ACK 的同时也发送 FIN,从而将两次挥手合并为一次,所以总共只出现 3 次挥手。
Delay ACK 是什么?它有什么优缺点?
Delay ACK 是指接收方收到数据后不立即发送 ACK,而是等待一段时间,希望在这段时间内应用层有数据要发送,从而将 ACK 和数据一起发送。优点是可以减少纯 ACK 包的数量(约减少 1/3),降低网络开销;缺点是可能造成延迟,而且其假设(应用层会很快回应)并不总是成立,可能引发问题。
Nagle 算法是用来解决什么问题的?它的工作原理是什么?
Nagle 算法用于解决发送小数据包导致的网络开销问题。其原理是:发送端不会立即发送小数据,而是积累数据直到达到一个 MSS(最大报文段长度)或者收到之前数据的 ACK 后再发送。这样可以避免大量小包,提高网络效率。
同时使用 Delay ACK 和 Nagle 算法会导致什么问题?
同时使用可能导致类似死锁的情况,造成不必要的延迟。例如,客户端启用 Nagle 算法,服务端启用 Delay ACK,当客户端发送一个大于 MSS 的请求时,第一个包发出后,服务端因 Delay ACK 不立即回复 ACK,而客户端因 Nagle 算法在等待 ACK 后才发送第二个包,双方互相等待,直到服务端延迟 ACK 超时,导致请求延迟。
如何关闭 Nagle 算法和 Delay ACK?
关闭 Nagle 算法:在 socket 上设置 TCP_NODELAY 选项,这样 write 操作会绕过 Nagle 算法直接发送数据。关闭 Delay ACK:在 socket 上设置 TCP_QUICKACK 选项,这样作为服务端在收到 TCP 段时会立即发送 ACK。在 Linux 系统中,TCP_QUICKACK 默认是关闭的。
Delay ACK 和 Nagle 算法的历史背景是什么?
大约在 1980 年代,Nagle 和 Berkeley 为了解决类似的问题分别发明了这两种机制。Berkeley 面临的问题是许多用户通过终端共享主机,网络被 ssh 或 telnet 等字符流量拥塞,于是采用 Delay ACK 来缓解。但 Nagle 认为他们没有抓住问题根源,并提出了自己的算法。