TCP全连接队列和半连接队列已满之后的连接建立过程抓包分析

本文详细分析了Linux内核中TCP连接的全连接队列和半连接队列在满载情况下的处理流程。当全连接队列已满,新的连接请求会被服务端接纳但不转为ESTABLISHED状态,而半连接队列满时,服务端会丢弃新的SYN分节。通过抓包实例展示了连接建立过程,并探讨了队列长度设置对连接处理的影响。

另外,0端口是系统的保留端口号,若bind时用了0端口,则表示由系统分配一个端口,该端口介于1024和5000之间。


在进行client不断的对server端进行connect的过程中发现下面这个syn_recv状态,而且循环6w次的链接只进行了2W多次就出错了。





于是去查找了下原因:

Linux内核协议栈为一个tcp连接管理使用两个队列,一个是半链接队列(用来保存处于SYN_SENT和SYN_RECV状态的请求),一个是全连接队列(accpetd队列)(用来保存处于established状态,但是应用层没有调用accept取走的请求)。


也就是说,client进行connect之后先进入server的半连接队列,client就直接进入established状态,如果server的全连接队列(accept队列)未满,则将其放入全连接队列(未被应用层进行accpet函数调用取走),然后server也进入established状态。如果全连接队列满了,则server停留在syn_recv状态。

其中全连接队列somaxconn的参数为1表示监听队列的总长度(实际可以完成somaxconn+1个连接的建立)

半连接队列tcp_max_syn_backlog为1(实际可以将tcp_max_syn_backlog+1个syn分节放入其中)

但是在应用层有 int listen(int sockfd, int backlog);。。。然后实际的全连接队列的长度限制是min(backlog, somaxconn);

默认的话半连接队列的长度是/proc/sys/net/ipv4/tcp_max_syn_backlog,默认是1024。如果开启了syncookies,那么基本上没有限制。

全连接队列的长度是/proc/sys/net/core/somaxconn,默认是128,表示最多有129个established链接等待accept。(为什么是129?详见下面的附录1)。


当全连接队列已满且半连接队列未满的情况下:

当客户端发起一个syn分节时,服务端不会丢弃该syn分节,而是直接响应ack和syn,这时客户端响应ack,并成为established状态,而服务端收到ack响应后,试图将该syn分节从半连接队列中移除,并加入全链接队列,然后由于全连接队列已经满了,这时,在默认情况下,服务端啥也不做,而且不会将该连接由SYN_RECV变成ESTABLISHED,服务端仅仅只是创建一个定时器,以固定间隔重传syn和ack到客户端,直到到达系统默认的synack重传阖值,然后服务端将处于半连接队列里面的syn分节丢弃。

当全连接已满且半连接队列也满的情况下:

当客户端发起一个syn分节时,服务端发现半连接队列已经满了,同时该syn分节尚未重传过,服务端直接丢弃该syn分节,然后客户端过了4秒重传syn分节,这个时候服务端发现半连接队列已满同时,该syn分节已经重传过了,服务端收下了该syn分节,并响应客户端syn+ack,客户端收到syn+ack后,响应ack。客户端三次握手已经完成,而服务端收到ack之后,发现全连接队列是满的,这个时候不会将该连接从SYN_RECV转换成ESTABLISHED,服务端创建定时器,定时重传syn+ack, 直到到达系统默认的synack重传阖值,然后服务端将处于半连接队列里面的syn分节丢弃





实际队列中的数量比max的数值大1,标号从0开始,直到max数值也能使用:



实际队列中的数量比max的数值大1,标号从0开始,直到max数值也能使用:


附录1

when I use linux TCP socket, and find there is a bug in function sk_acceptq_is_full()

When a new SYN comes, T

<think>我们正在区分TCP半连接队列应用层连接队列。根据引用[1][2],半连接队列存储的是SYN请求(尚未完成三次握手),而全连接队列(即应用层连接队列)存储的是已完成三次握手的连接,等待应用层accept。区分方法如下:1.半连接队列的表现:-服务端无法响应SYN包,导致客户端收不到SYN+ACK,客户端会重传SYN包。-通过`netstat-s`命令可以查看半连接队列溢出的统计信息,例如在Linux下:```netstat-s|grep-i"SYNstoLISTEN"```如果输出中有非零的`SYNstoLISTENsocketsdropped`,说明半连接队列[^2]。2.应用层连接队列全连接队列的表现:-服务端可以完成三次握手,但应用层没有及时accept,导致连接堆积在全连接队列。-当全连接队列时,新的已完成三次握手的连接会被丢弃。此时,客户端发送的ACK会被服务端正常接收,但服务端会丢弃该连接(如果全连接队列)。-同样可以用`netstat-s`查看全连接队列溢出:```netstat-s|grep-i"timesthelistenqueueofasocketoverflowed"```或者```netstat-s|grep"overflowed"```如果有非零的计数,说明全连接队列曾经过。3.抓包分析:-半连接队列时,服务端不会回复SYN+ACK(客户端发送SYN后没有响应)。-全连接队列时,服务端会回复SYN+ACK,并完成三次握手,但在完成握手后,如果队列,服务端可能会直接丢弃该连接(客户端会认为连接建立,但服务端不会处理,因此客户端发送数据后可能会收到RST)。4.客户端表现:-半连接队列:客户端连接超时,重传SYN包(典型表现为连接时间较长,最终失败)。-全连接队列:客户端连接很快建立(因为完成了三次握手),但当客户端发送数据时,可能会收到RST(连接重置)或者服务端无响应(取决于具体实现)。5.服务端监控:-半连接队列长度:通过`ss-lnt`可以查看Send-Q(全连接队列最大长度Recv-Q(当前全连接队列长度)。但半连接队列长度需要从内核统计信息中获取(如`/proc/net/netstat`中的`ListenOverflows``ListenDrops`)。-全连接队列:使用`ss-lnt`可以查看每个监听端口的Recv-Q(当前全连接队列中的连接数)Send-Q(全连接队列的最大长度)。总结:-半连接队列:服务端不响应SYN,客户端SYN重传。-全连接队列:服务端完成三次握手,但连接被丢弃,客户端在发送数据时可能遇到错误。因此,通过查看`netstat-s`中的丢弃统计、抓包观察握手过程以及客户端的表现,可以区分是半连接队列还是全连接队列。</think> ### 如何区分 TCP 半连接队列应用层连接队列 #### 1. 监控队列溢出指标 | **队列类型** | **监控命令** | **关键指标** | **溢出表现** | |------------|-------------|-------------|-------------| | **TCP半连接队列** | `netstat -s \| grep -i "SYNs to LISTEN"` | `SYNs to LISTEN sockets dropped` > 0<br> `TCPListenOverflows` 增加[^1] | 客户端SYN重传增加<br>服务端无SYN+ACK响应 | | **应用层连接队列** | `ss -lnt \| grep <端口>` | `Recv-Q` = `Send-Q` 持续状态<br> `times the listen queue overflowed` > 0 | 连接建立后无响应<br>应用日志显示"accept queue full" | #### 2. 队列建立时机差异(图示) ```mermaid sequenceDiagram participant C as Client participant S as Server C->>S: SYN (seq=x) Note right of S: SYN进入半连接队列 S->>C: SYN+ACK (seq=y, ack=x+1) C->>S: ACK (ack=y+1) Note right of S: 连接移入应用层队列 S->>应用进程: accept()调用 Note right of S: 应用层取出连接 ``` #### 3. 队列的根本原因 - **半连接队列** $Queue_{syn} = min(backlog, net.core.somaxconn, tcp\_max\_syn\_backlog)$[^2] 典型触发场景: - SYN洪水攻击 - `tcp_max_syn_backlog`设置过低 - 大量并发连接请求(如系统启动瞬间) - **应用层队列** $Queue_{accept} = min(backlog, net.core.somaxconn)$ 典型触发场景: - 应用未及时调用accept() - 工作线程阻塞(死锁/资源不足) - `backlog`参数配置过小 #### 4. 诊断流程 ```mermaid graph TD A[连接超时/失败] --> B{查看 netstat -s} B -- SYN dropped >0 --> C[半连接队列] B -- overflowed >0 --> D[应用层队列] C --> E[检查 tcp_max_syn_backlog] C --> F[启用 tcp_syncookies] D --> G[检查应用 accept 效率] D --> H[增加 backlog 值] E --> I[调整值 > 预期并发数*1.5] G --> J[使用 strace 跟踪系统调用] J -- accept阻塞 --> K[优化线程池/修复死锁] ``` #### 5. 实际案例验证方法 1. **强制半连接队列溢出** ```bash # 临时减小队列触发溢出 sysctl -w net.ipv4.tcp_max_syn_backlog=10 hping3 -S -p 80 --flood 目标IP ``` 观测:`netstat -s`显示SYN dropped增加 2. **模拟应用层队列** ```python # Python模拟阻塞accept import socket s = socket.socket() s.bind(('0.0.0.0', 8080)) s.listen(1) # 设置极小backlog while True: time.sleep(1000) # 模拟不调用accept ``` 观测:`ss -lnt`显示Recv-Q=Send-Q持续相等 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值