TCP 的半连接与全连接队列详解
字数 3285 2025-12-12 01:37:52
TCP 的半连接与全连接队列详解
1. 知识点/问题描述
这个问题考察的是在 TCP 三次握手过程中,服务器端用来管理连接请求的两个关键队列:半连接队列(也称为 SYN 队列)和全连接队列(也称为 ACCEPT 队列)。理解它们的区别、工作流程、以及队列溢出时的行为和应对措施,对于排查服务器高并发连接问题、理解 SYN Flood 攻击的原理和防御机制至关重要。
2. 知识背景与核心概念
首先,回忆 TCP 三次握手 的基本过程:
- 客户端发送 SYN 报文到服务器,请求建立连接。
- 服务器收到 SYN 后,回复 SYN+ACK,并进入 SYN_RCVD 状态。
- 客户端收到 SYN+ACK 后,回复 ACK,服务器收到 ACK 后,连接建立完成,服务器进入 ESTABLISHED 状态,并将其交付给应用程序处理。
在这个过程中,服务器需要暂时存储那些“握手还未完成”的连接,以及那些“握手已完成、但还未被应用程序取走”的连接。这两种中间状态分别由 半连接队列 和 全连接队列 来管理。
- 半连接队列:存放处于 SYN_RCVD 状态的连接。即服务器已发出 SYN+ACK,正在等待客户端回复 ACK。这个队列中的连接尚未完成三次握手。
- 全连接队列:存放已完成三次握手(处于 ESTABLISHED 状态)、但尚未被服务器应用程序通过
accept()系统调用取走的连接。这个队列是内核维护的,用于缓冲已建立的连接,等待应用程序处理。
3. 工作流程的逐步详解
步骤 1:客户端发送 SYN
- 客户端选择一个随机初始序列号
client_isn,向服务器的某个监听端口发送一个 SYN 报文。 - 服务器内核收到 SYN 后,会检查半连接队列是否已满。
步骤 2:服务器处理 SYN,进入半连接队列
- 如果半连接队列未满,服务器会为该连接分配一个最小的资源结构(通常是一个
request_sock结构),记录连接信息(如四元组、初始序列号等),状态置为 SYN_RCVD,并将此结构放入半连接队列。 - 然后,服务器发送 SYN+ACK 给客户端,并启动一个重传定时器(用于在 SYN+ACK 丢失时重传)。
步骤 3:客户端回复 ACK,连接迁移至全连接队列
- 客户端收到 SYN+ACK 后回复 ACK。
- 服务器收到 ACK 后,在半连接队列中找到对应的连接,将其状态变为 ESTABLISHED,并将该连接从半连接队列中移出,放入全连接队列。
- 此时,从协议角度,TCP 连接已建立,但服务器应用程序还不知道这个连接的存在,因为它还没有调用
accept()来获取。
步骤 4:应用程序通过 accept() 获取连接
- 服务器应用程序在监听套接字上调用
accept()系统调用。 - 如果全连接队列不为空,则内核会从队列头部取出一个已建立的连接,为其创建一个新的套接字描述符(通常称为“已连接套接字”),返回给应用程序。
- 取出的连接从全连接队列中移除,交由应用程序处理后续的数据收发。
4. 队列溢出及其后果
这是面试和实际运维中的重点。两个队列都有长度限制,当队列满时,服务器的行为不同:
a) 半连接队列满
- 当服务器收到新的 SYN 请求,但半连接队列已满时,默认行为是丢弃这个 SYN,不回复 SYN+ACK,相当于对 SYN 不做响应。
- 客户端会因收不到 SYN+ACK 而超时重传 SYN。
- 这为 SYN Flood 攻击提供了条件:攻击者发送大量伪造源 IP 的 SYN 报文,耗尽服务器的半连接队列,导致合法用户无法建立连接。
b) 全连接队列满
- 当服务器收到客户端的 ACK,试图将连接从半连接队列移到全连接队列时,如果全连接队列已满,服务器的行为取决于系统配置(主要是
tcp_abort_on_overflow参数):- 默认行为(
tcp_abort_on_overflow = 0):服务器会忽略这个 ACK,即不将连接移入全连接队列,但也不立即关闭连接。它会等待一段时间,期望应用程序调用accept()腾出空间后,再重试(实际上是通过重传机制,稍后解释)。 - 激进行为(
tcp_abort_on_overflow = 1):服务器直接回复 RST 报文,重置这个连接。客户端会看到“Connection reset by peer”之类的错误。
- 默认行为(
在实际中,全连接队列满时,服务器忽略 ACK 的后果是:
- 服务器没有对这个 ACK 给出确认,所以客户端认为连接已建立(因为它发出了 SYN 并收到了 SYN+ACK,又发出了 ACK)。
- 服务器没有发出数据确认,所以客户端如果发送数据,服务器不会回复 ACK,导致客户端超时重传数据。
- 服务器在收到重传的数据报文(带有 ACK 标志)时,会再次尝试将连接移入全连接队列。如果此时队列有空位,则成功建立;如果仍然满,则继续忽略。
- 多次失败后,连接可能因超时而关闭。
5. 相关系统参数与调优
在 Linux 系统中,可以通过以下参数查看和调整这两个队列:
- 半连接队列长度:由
net.ipv4.tcp_max_syn_backlog和应用程序调用listen()时传入的backlog参数共同决定(取两者中较大的一个,在较新内核中,还受net.core.somaxconn影响)。 - 全连接队列长度:由
listen()的backlog参数和系统全局参数net.core.somaxconn共同决定,取两者的较小值。- 例如:
listen(sockfd, backlog)中的backlog参数指定了全连接队列的最大长度,但最终值不会超过net.core.somaxconn。
- 例如:
- SYN Flood 防御参数:
net.ipv4.tcp_syncookies = 1:当半连接队列满时,启用 SYN Cookie 机制。服务器不再将连接信息存入队列,而是通过加密算法生成一个 Cookie 值作为初始序列号放在 SYN+ACK 中。客户端回复的 ACK 会带回这个 Cookie,服务器验证 Cookie 合法后才分配资源建立连接。这可以有效抵御 SYN Flood 攻击,但会失去一些 TCP 选项信息。
- 队列状态查看命令:
- 半连接队列:
netstat -tnpa | grep SYN_RECV | wc -l - 全连接队列当前长度和最大长度:
ss -lnt查看“Send-Q”列(当前全连接队列长度)和“Recv-Q”列(当前全连接队列中已就绪但未被取走的连接数,但通常看 Send-Q 即可)。
- 半连接队列:
6. 总结与常见问题
- 核心区别:半连接队列存放未完成三次握手的连接(SYN_RCVD 状态),全连接队列存放已完成握手但未被应用接受的连接(ESTABLISHED 状态)。
- 溢出影响:半连接队列满会导致新的连接请求被丢弃;全连接队列满会导致已完成握手的连接被延迟处理或重置。
- 调优重点:根据服务器并发连接需求,合理设置
listen()的backlog参数、net.core.somaxconn和net.ipv4.tcp_max_syn_backlog,并在必要时启用tcp_syncookies以防 SYN Flood。 - 常见故障:服务器出现“连接超时”或“连接被重置”,可能源于全连接队列满且
tcp_abort_on_overflow=1;出现大量 SYN_RECV 状态连接,可能是 SYN Flood 攻击或半连接队列设置过小。
通过理解这两个队列的工作机制,你就能深入把握 TCP 连接建立过程中服务器端的资源管理和并发处理能力,从而更好地进行服务器性能调优和故障排查。