TCP_DEFER_ACCEPT使内核在收到第三次ACK后暂不将连接移入accept队列,而是保留在SYN_RECV状态,直到首个数据包到达才触发accept(),从而避免worker为无数据空连接消耗资源。

直接让内核把“空连接”拦在 ESTABLISHED 之前,worker 进程就不用为只建连不发数据的客户端浪费资源了。
TCP_DEFER_ACCEPT 的核心作用
服务器收到第三次 ACK 后,不立即把连接放入 accept 队列,而是继续留在 SYN_RECV 状态,等待第一个真实数据包到达才触发 accept() 调用。这意味着 worker 进程(比如 Nginx 的 worker 或 Go 的 net.Listener.Accept)根本不会看到这个连接,直到有数据真正抵达。
为什么能减少非阻塞场景下的无效唤醒
- 普通模式下:三次握手一完成,连接就进 accept 队列,epoll_wait() 或类似机制立刻返回,worker 被唤醒调用 accept(),但此时 socket 上往往没有可读数据,紧接着又要阻塞在 recv() 或进入 epoll ET 模式下的边缘触发等待
- 开启 TCP_DEFER_ACCEPT 后:内核跳过早期唤醒,只有带 payload 的 TCP 包(如 HTTP 请求行)到达时,才把连接移入全连接队列并通知 epoll,worker 一 accept 就能立刻 read 到数据,避免一次“唤醒—检查—再等待”的空转
- 对短连接、HTTP/1.1 首包即请求的场景尤其有效,省掉一个无意义的上下文切换和系统调用开销
实际配置方式
在监听 socket 创建后、bind/listen 前,用 setsockopt 设置:
int timeout_sec = 10; // 内核据此推算 SYN-ACK 最多重传次数 setsockopt(sockfd, IPPROTO_TCP, TCP_DEFER_ACCEPT, &timeout_sec, sizeof(timeout_sec));
该值不是精确等待秒数,而是上限约束:若客户端在超时窗口内始终不发数据,内核会在重传耗尽后清理该半开连接,不发 RST,也不通知应用层。
配合其他参数使用效果更稳
- 搭配 net.ipv4.tcp_synack_retries 控制重传轮次,避免过长等待
- Nginx 中直接写 listen 80 deferred; 即启用(Linux 专属)
- 注意:此选项仅作用于监听套接字,且不可移植(BSD/macOS 不支持同名选项)


















