启用 tcp_defer_accept 机制可使监听套接字仅在客户端发送数据后才激活 worker 处理连接,nginx 中通过 listen 指令添加 deferred 参数实现,需配合非阻塞 i/o 与内核参数调优(如 tcp_synack_retries≤3),显著降低空连接带来的 cpu 和上下文切换开销。

要让监听套接字只在客户端真正发送数据后才激活 Worker 处理连接,核心是启用 TCP_DEFER_ACCEPT 机制——它不是延迟 accept 调用本身,而是让内核跳过“仅完成三次握手、无数据”的连接,直到第一个业务数据包抵达才将其放入 accept 队列。
nginx 中启用 deferred 的正确写法
在 nginx 配置的 listen 指令中直接添加 deferred 参数即可:
-
listen 80 deferred;(IPv4 默认) -
listen [::]:80 deferred;(IPv6) - 支持组合参数,如:
listen 443 ssl deferred;
该参数仅在 Linux 系统生效,且必须配合非阻塞 I/O(epoll + non-blocking socket)使用。nginx 内部会自动在 socket() 后、listen() 前调用 setsockopt(..., IPPROTO_TCP, TCP_DEFER_ACCEPT, &seconds, sizeof(seconds)),默认超时值为 3 秒。
手动设置 TCP_DEFER_ACCEPT(C/Go 等原生服务)
若不使用 nginx,需在监听 socket 创建后、listen() 调用前显式配置:
- 定义整型变量
defer_sec = 3;(推荐 3~5 秒) - 调用:
setsockopt(listen_fd, IPPROTO_TCP, TCP_DEFER_ACCEPT, &defer_sec, sizeof(defer_sec)); - 确保该 socket 已设为非阻塞模式(
fcntl(fd, F_SETFL, O_NONBLOCK)) - 注意:不能在
bind()或listen()之后设置,否则无效
配套内核参数调优
单独开启 TCP_DEFER_ACCEPT 不足以发挥最大效果,需同步调整底层 TCP 行为:
- 降低 SYN+ACK 重传次数,避免内核提前丢弃合法连接:
sysctl -w net.ipv4.tcp_synack_retries=3 - 确认当前值:
cat /proc/sys/net/ipv4/tcp_synack_retries - 该值应 ≤ defer 秒数对应的大致重传窗口(3 次重传约覆盖 3~5 秒),防止客户端弱网下 ACK 丢失导致连接被丢弃
Worker 激活逻辑的变化
启用后,Worker 不再为“空连接”做无意义处理:
-
epoll_wait()返回可读事件时,该 listen fd 对应的连接必定已携带数据 -
accept()返回的新连接 fd 可立即调用recv()或read(),几乎不会返回EAGAIN或空读 - 省去“先 accept、再等待 EPOLLIN”的两阶段轮询,减少文件描述符占用和 epoll_ctl 系统调用次数
- 对 HTTP keep-alive、移动端心跳等大量建连不发数据的场景,显著降低 CPU 和上下文切换开销











