TCP_DEFER_ACCEPT 使内核在三次握手完成后暂不将连接置为 ESTABLISHED,而是停留于 SYN_RECV 状态,仅当首个数据包到达才触发 accept();需在 bind 后、listen 前调用 setsockopt 设置,timeout 单位为秒,0 表示禁用;它减少无效 accept 调用、fd 占用、epoll_ctl 次数及内存调度开销。

直接让内核把“空连接”拦在 ESTABLISHED 之前,worker 进程就不用为只建连不发数据的客户端浪费资源了。
它怎么跳过早期唤醒
TCP_DEFER_ACCEPT 的核心作用是:服务器收到第三次 ACK 后,不立即把连接放入 accept 队列,而是继续留在 SYN_RECV 状态,等待第一个真实数据包到达才触发 accept() 调用。这意味着 worker 进程(比如 Nginx 的 worker 或 Go 的 net.Listener.Accept)根本不会看到这个连接,直到有数据真正抵达。
- 三次握手完成 ≠ 连接进入应用层处理队列
- 内核代为“守门”,只放行带有效载荷的连接
- 没有数据的 ACK 不会触发 epoll_wait 返回可读事件给监听 socket
如何正确设置 TCP_DEFER_ACCEPT
需在调用 listen() 之前,对监听 socket 设置该选项:
- 包含头文件:netinet/tcp.h
- 调用 setsockopt:setsockopt(listen_fd, IPPROTO_TCP, TCP_DEFER_ACCEPT, &timeout, sizeof(int))
- timeout 值单位是秒,内核会将其转换为 SYN/ACK 重传次数(参考 net.ipv4.tcp_synack_retries)
- timeout = 0 表示禁用该功能
注意:此选项仅对监听 socket 有效,且必须在 bind() 之后、listen() 之前设置,否则会被忽略。
它真正节省的是哪几类开销
不是减少网络延迟,而是削减事件循环和 worker 的无效负载:
- 系统调用减少:避免大量 accept() 调用返回后又立刻 close() 空连接
- 文件描述符占用下降:空连接不进入 ESTABLISHED 状态,不消耗 fd
- epoll_ctl 次数降低:连接未被 accept,就不会被加入 worker 的 epoll 实例
- 内存与调度压力缓解:每个空连接原本要分配 socket 结构、缓冲区、task_struct 上下文等
搭配其他参数效果更佳
TCP_DEFER_ACCEPT 单独启用已有收益,但配合以下内核调优能发挥更大作用:
- 增大全连接队列:net.core.somaxconn,防止有效连接被丢弃
- 增大半连接队列:net.ipv4.tcp_max_syn_backlog,应对 SYN 泛洪或突发建连
- 启用时间戳与 TIME_WAIT 复用:net.ipv4.tcp_timestamps=1 和 net.ipv4.tcp_tw_reuse=1,提升短连接吞吐
- 确认监听队列是否溢出:netstat -s | grep -i 'listen drops'










