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)根本不会看到这个连接,直到有数据真正抵达。
- 普通流程:SYN → SYN+ACK → ACK → 连接进 accept 队列 → worker 调用 accept() → 创建 socket、分配内存、初始化上下文
- 启用 TCP_DEFER_ACCEPT 后:SYN → SYN+ACK → ACK → 连接仍卡在 SYN_RECV → 内核暂不通知应用 → 只有首个 data 包到达时,才完成状态跃迁并入队
对 worker 的实际减负效果
在高并发低活跃场景(如大量长连接保活、移动端心跳、HTTP/1.1 keep-alive 但后续无请求),大量连接只完成握手却长期不发业务数据。这些连接若全进 accept 队列,会持续占用:
- worker 的文件描述符(每个 accept 返回 fd 都要注册到 epoll/kqueue)
- worker 的内存(socket 结构体、接收缓冲区预分配等)
- worker 的调度开销(epoll_wait 返回后需处理 fd、检查是否可读)
开启该选项后,这些“静默连接”被内核过滤掉,worker 完全不感知,相当于把连接接纳动作从“连接建立即触发”延迟到“有数据才触发”,显著降低空转负载。
配置与注意事项
在监听 socket 上设置该选项,单位是秒(内核将其转换为 SYN+ACK 重传次数上限):
- Linux 下用 setsockopt(sockfd, IPPROTO_TCP, TCP_DEFER_ACCEPT, &seconds, sizeof(seconds))
- 常见取值:3~5 秒 —— 太短易误杀慢启动客户端;太长则延迟释放僵死连接
- 必须配合调大 net.ipv4.tcp_synack_retries(如设为 3),避免重传次数和 DEFER 时间错配导致提前丢弃
- 注意:该选项不可移植,仅 Linux 支持;且不适用于需要立即响应 connect() 的协议(如某些自定义健康检查)
和其它优化协同使用
TCP_DEFER_ACCEPT 不是孤立方案,应与以下机制搭配:
- 增大 syn backlog(net.ipv4.tcp_max_syn_backlog):防止 SYN 洪水挤占正常连接空间
- 启用 tcp_fastopen(net.ipv4.tcp_fastopen):允许第一次 SYN 携带数据,进一步压缩握手延迟
- 合理设置 keepalive 参数:对已建立但长期空闲的连接,由内核探测而非 worker 主动轮询
- 限制单 IP 连接数或使用连接池:从应用层控制连接总量,避免内核队列过载










