延时应答
- 延时应答
-
- [为什么不立刻 ACK?](#为什么不立刻 ACK?)
- 延迟确认的原理
- 延迟确认的触发条件与安全限制
- [延迟确认与 Nagle 算法的冲突](#延迟确认与 Nagle 算法的冲突)
- 什么时候该关掉延迟确认?
延时应答
为什么不立刻 ACK?
默认情况下,接收方每收到一个报文段就立刻发回 ACK,并在 ACK 中携带当前接收窗口(rwnd)。这个窗口直接决定了发送方能连续发送多少数据(流量控制)。
如果接收方的应用处理速度跟不上网络速度,缓冲区很容易被填满。此时立即回复的 ACK 会通告一个极小甚至为 0 的窗口 ,发送方只能停下或发送很小的数据段,陷入糊涂窗口综合症(Silly Window Syndrome),网络吞吐量急剧下降。
延迟确认的原理
延迟确认的策略很简单:收到数据后不立刻 ACK,而是等一小会儿。在这段等待时间里,上层应用程序很有可能正好读取了一些数据,释放了缓冲区空间。
- 于是接收方的剩余窗口变大了;
- 接收方这时再发出 ACK,携带的就是一个更大的窗口;
- 发送方看到大窗口后,可以立即连续发出更多、更大的报文段,显著提升吞吐。
应用程序 接收方 发送方 应用程序 接收方 发送方 只能等待窗口更新,无法发送 故意延迟 ACK,等待应用消费 立即发送大量数据,吞吐量提高 alt [如果立刻确认] [延迟确认] 发送数据包,填满窗口 (剩余窗口=0) ACK + 窗口=0 随后应用读取数据,释放空间 稍后的窗口更新 ACK + 窗口=5000 应用读取缓冲,释放 5000 字节 ACK + 窗口=5000 (一次性通告大窗口)
📌 核心收益:用极短的时间延迟(毫秒级),让 ACK 通告的窗口从一个即时快照变成一个经过优化的可用值,使发送方真正利用到接收方的处理能力。
延迟确认的触发条件与安全限制
如果接收方一直憋着不发 ACK,发送方就会误以为丢包而触发超时重传,性能反而恶化。因此 TCP 实现设定了严格规则:
| 限制类型 | 规则说明 | 目的 |
|---|---|---|
| 数量限制 | 通常每收到 2 个满大小(Full-sized)报文段 就必须发送一个 ACK(不再继续延迟) | 避免连续延迟导致发送方窗口停滞 |
| 时间限制 | 每个延迟 ACK 最多等待 一个定时器周期(通常 40ms,最多可达 200ms),超时立即发送 | 防止触发发送方的重传超时(RTO) |
同时,某些情况下必须立即确认,延迟机制自动失效:
- 收到乱序报文(需快速告知对端);
- 收到带有 PSH 标志的报文(应用要求立即交付);
- 当前可以捎带 ACK(有数据要发时,顺便把确认信息带出去);
- 启用了快速确认模式(例如
TCP_QUICKACK)。
是
(乱序/PSH/捎带)
否
累积收到第 2 个满报文
定时器超时(如 40ms)
收到 TCP 报文段
需要立即确认?
立即发送 ACK
启动/重置延迟定时器
延迟期间
发送 ACK,携带最新窗口
发送 ACK
完成
这种机制让延迟确认在充分利用窗口 和保持确认及时性之间取得了平衡。
延迟确认与 Nagle 算法的冲突
延迟确认带来的一个经典问题是它与 Nagle 算法互锁,形成短暂的死锁:
- Nagle 算法要求:当连接上有未确认的数据时,不能发送小报文,必须攒到 MSS 大小或等 ACK 返回;
- 延迟确认机制:接收方故意不立刻 ACK,想等多攒几个包或窗口变化再确认。
结果:发送方等 ACK 才能继续发,接收方等更多数据才发 ACK,双方互相等待,直到延迟 ACK 的定时器超时(40ms 级),造成明显的请求停顿。对于交互式应用(如 SSH、游戏、HTTP/1.1 短连接)非常致命。
解决方案:
- 对延迟敏感的应用禁用 Nagle (设置
TCP_NODELAY); - 或者让接收方禁用延迟确认 (设置
TCP_QUICKACK); - 协议设计上避免一连串小数据写(写汇聚、请求合并)。
什么时候该关掉延迟确认?
| 场景 | 建议 |
|---|---|
| 大文件传输、流媒体、吞吐优先 | 保留默认的延迟确认,能显著提升传输效率 |
| 在线游戏、VoIP、交易指令、低延迟交互 | 禁用延迟确认,或缩短最大延迟时间,优先保证即时交付 |
| 高并发短连接 Web 服务 | 注意 Nagle 与延迟确认的互锁,通常开启 TCP_NODELAY |
| 自己设计可靠协议 | 如需要类似机制,必须同时考虑数量+时间边界,并警惕与拥塞控制的互相影响 |
在 Linux 上,可以通过 setsockopt 打开 TCP_QUICKACK 让接收方每次立刻确认;或配置 tcp_delack_min 等内核参数微调延迟确认行为。通常情况下,TCP 协议栈的自动管理已经足够好,无需手工干预。