什么是 TCP_DEFER_ACCEPT
默认情况下,三次握手一完成内核就把连接放入 accept 队列并唤醒应用,应用随后可能发现对端其实什么都没发过来。TCP_DEFER_ACCEPT 让内核等到连接上收到第一个数据包(或等待超时)之后才唤醒监听方,从而减少空连接带来的唤醒与系统调用开销。它改变的是唤醒时机,不改变握手本身,因此适合“连上就发数据”的协议(HTTP、Redis 等),不适合连接后长时间静默的协议。
基本信息
| 接口 | setsockopt(fd, IPPROTO_TCP, TCP_DEFER_ACCEPT, &secs, sizeof(secs)) |
|---|---|
| 参数含义 | 等待数据的最大秒数,超时后仍会唤醒 |
| nginx 对应 | listen 80 deferred; |
| 适用协议 | 连接建立后立即发送请求的短连接场景 |
| 不适用 | 连接后由服务端先发数据的协议 |
| 观测 | ss -ltn 的 Recv-Q、accept 队列长度 |
典型使用场景
- 高并发短连接服务:降低空连接带来的唤醒风暴
- 压测与抗扫描:大量只握手不发数据的连接不再逐个唤醒应用
- HTTP/HTTPS 前端:请求总是由客户端先发,符合该语义
- 降低系统调用次数:把部分 CPU 开销从应用侧移到内核判定
- 与负载均衡配合:确认 L4 探活不会因未发数据而受影响
用法示例
用法示例
int secs = 3;
/* 等待至多 3 秒;期间没有数据则不唤醒 accept */
setsockopt(fd, IPPROTO_TCP, TCP_DEFER_ACCEPT, &secs, sizeof(secs));
/* 设 0 表示关闭该行为,恢复默认语义 */
int off = 0;
setsockopt(fd, IPPROTO_TCP, TCP_DEFER_ACCEPT, &off, sizeof(off));nginx 与观察
nginx 与观察
server {
listen 80 deferred; # 配合内核行为,握手后等数据
listen 443 ssl deferred;
}
ss -ltn | head -5 # 看监听队列与 Recv-Q
top -b -n1 | head -5 # 对比开启前后的软中断与应用唤醒开销行为对照
| 配置 | 唤醒时机 | 适用判断 |
|---|---|---|
| 默认 | 握手完成即唤醒 | 服务端需先发数据的协议 |
| deferred(secs>0) | 收到首个数据或超时 | HTTP、Redis 这类客户端先发的协议 |
| deferred 秒数过大 | 超时前一直不处理 | 探活可能被拖延,需要评估 |
| 对静默协议启用 | 连接迟迟不被处理 | 属于误用,会表现为请求超时 |
它只作用于监听 socket 的唤醒语义:连接在 accept 队列中的状态与超时参数仍由内核维护,排查“连接建立了但应用没处理”时要先确认队列与超时设置。
对连接后长时间不发数据的协议强行启用,会导致连接迟迟不被处理,最终被对端或中间设备超时断开,表现为难以解释的握手成功但无响应。