TCP连接队列

在讲 TCP 三次握手 时,操作系统内核实际上维护了两个非常关键的队列:

  • 半连接队列(SYN队列)
  • 全连接队列(Accept队列)

这两个队列直接关系到服务端的并发能力和抗攻击能力

一、整体流程

客户端发起连接时:

1
2
3
客户端 → SYN → 服务端
服务端 → SYN+ACK → 客户端
客户端 → ACK → 服务端

在这个过程中:

阶段 连接状态 所在队列
收到 SYN 半连接(未完成握手) 半连接队列
收到 ACK 完整连接 全连接队列

二、半连接队列(SYN Queue)

2.1 定义

半连接队列用于存放:

已经收到客户端 SYN,但还没完成三次握手的连接

即状态为:

1
SYN_RECV

2.2 工作流程

  1. 服务端收到客户端 SYN
  2. 内核创建一个“请求连接”(request sock)
  3. 放入 半连接队列
  4. 回复 SYN+ACK,等待客户端 ACK

2.3 特点

  • 存的是“未完成连接”
  • 每个元素比较轻量(不是完整 socket)
  • 容量有限(由内核参数控制)

常见参数(Linux):

1
net.ipv4.tcp_max_syn_backlog

2.4 风险:SYN Flood 攻击

攻击方式:

  • 大量发送 SYN
  • 不回 ACK

结果:

  • 半连接队列被占满
  • 正常请求无法进入

防御手段:

  • SYN Cookie
  • 增大 backlog
  • 限速

三、全连接队列(Accept Queue)

3.1 定义

全连接队列用于存放:

已经完成三次握手,等待应用层 accept 的连接

状态:

1
ESTABLISHED

3.2 工作流程

  1. 收到客户端 ACK
  2. 三次握手完成
  3. 连接从半连接队列移除
  4. 加入 全连接队列
  5. 等待应用调用:
1
accept()

3.3 特点

  • 存的是完整 socket
  • 可以直接读写数据
  • 队列满会影响新连接

3.4 队列大小

listen(fd, backlog) 决定:

1
listen(sockfd, backlog)

注意:

  • backlog 不是简单等于全连接队列大小
  • 实际受以下限制:
1
net.core.somaxconn

四、两个队列的关系(核心理解)

1
2
3
4
5
6
7
8
9
10
           SYN
客户端 ───────────► 半连接队列

│ ACK

全连接队列

│ accept()

应用程序

五、关键问题

5.1 如果半连接队列满了会怎样?

  • 新的 SYN 被丢弃
  • 或启用 SYN Cookie(不进队列)

5.2 如果全连接队列满了?

  • 已完成握手的连接无法进入队列
  • 客户端可能:
    • 收不到响应
    • 或被 RST

5.3 为什么要分两个队列?

核心原因:

队列 作用
半连接队列 防御攻击、降低资源占用
全连接队列 提供稳定的应用层连接

本质是:

将“未确认连接”和“已建立连接”隔离,提高系统稳定性

5.4 backlog 到底控制哪个?

这是经典坑点

结论(Linux):

1
2
backlog ≈ 全连接队列大小
半连接队列由 tcp_max_syn_backlog 控制

但:

  • 实际行为会被内核优化调整
  • 两个队列都会受 backlog 影响(间接)

5.5 客户端有这两个队列吗?

半连接队列(SYN队列)和全连接队列(Accept队列)主要只存在于服务端,客户端没有“这两个队列”的概念(但有自己的连接状态管理)

这两个队列是为了解决一个核心问题:服务端要同时处理大量“被动连接请求”

六、总结

可以直接这样答

TCP 服务端维护两个队列:

  • 半连接队列:存放收到 SYN 但未完成三次握手的连接(SYN_RECV)
  • 全连接队列:存放完成三次握手、等待 accept 的连接(ESTABLISHED)

三次握手过程中:

  • 第一次握手进入半连接队列
  • 第三次握手完成后进入全连接队列

半连接队列主要用于防御 SYN Flood 攻击,全连接队列用于提供给应用层处理。

两者分别由 tcp_max_syn_backloglisten backlog 控制。