前言
在 Linux 网络编程开发中,listen() 函数 的第二个参数 backlog 是容易被初学者忽略的知识点。大多数开发者仅知道该参数和连接排队相关,但不了解内核存在半连接队列、全连接队列两套独立管理结构,一旦并发连接量超过队列上限,会出现客户端连接超时、建链失败等问题。
本文结合完整 C++ TCP 服务端 / 客户端实验、数据结构图解、netstat状态观测、tcpdump命令行抓包,完整拆解 TCP 双队列底层逻辑,配套可复现的实操案例,所有内容基于本科计算机网络、Linux 系统编程体系,无复杂度解析,适合网络编程入门和知识学习。
一、TCP 两套连接队列
1.1 三次握手与双队列分工
TCP 建立连接需要三次握手,Linux 内核协议栈为监听端口维护两套独立队列,分别管理握手不同阶段的连接请求,两套队列各司其职,互不干扰:
-
半连接队列 存储服务器收到客户端SYN报文、回复SYN+ACK,但尚未收到客户端第三次握手ACK的连接。
-
全连接队列 客户端完成三次握手、连接进入ESTABLISHED状态,但应用程序未调用accept()从内核取出的连接。
1.2 backlog 参数
backlog仅约束全连接队列,结论:全连接队列最大有效长度 = backlog + 1。
同时系统全局参数net.core.somaxconn会对队列上限做兜底限制,最终生效长度取min(backlog+1, somaxconn),线上高并发服务需同步调高该内核参数,避免队列提前溢出。
队列溢出后果:当全连接队列填满,新完成三次握手的连接无法存入队列,内核不会回复 RST 重置报文,客户端会持续重传第三次握手 ACK,最终出现连接超时失败;服务端侧新连接长期停留在SYN_RECV状态,无法转为ESTABLISHED。
二、TCP 双队列复现实验
2.1 设计思路
-
服务端逻辑:调用Bind绑定端口,Listen(2)设置 backlog=2,永久不调用 accept (),所有完成握手的连接全部积压在全连接队列。
-
客户端逻辑:循环发起 TCP 连接,不主动断开;
-
预期现象:backlog=2,全连接队列最大容量为 3,启动 3 个客户端可正常建立ESTABLISHED连接;启动第 4 个客户端时,第四个连接停留在SYN_RECV,无法进入全连接队列。
2.2 源代码
2.2.1 服务端 test_server.cc
cpp
#include "tcp_socket.hpp"
#include <cstdio>
#include <unistd.h>
int main(int argc, char* argv[]) {
// 参数校验:./test_server [ip] [port]
if (argc != 3) {
printf("Usage ./test_server [ip] [port]\n");
return 1;
}
TcpSocket sock;
bool ret = sock.Bind(argv[1], atoi(argv[2]));
if (!ret) {
printf("Bind failed\n");
return 1;
}
// listen第二个参数backlog=2
ret = sock.Listen(2);
if (!ret) {
printf("Listen failed\n");
return 1;
}
// 核心:不调用accept,所有连接积压在内核全连接队列
while (1) {
sleep(1);
}
return 0;
}
2.2.2 客户端 test_client.cc
cpp
#include "tcp_socket.hpp"
#include <cstdio>
#include <unistd.h>
int main(int argc, char* argv[]) {
if (argc != 3) {
printf("Usage ./test_client [ip] [port]\n");
return 1;
}
TcpSocket sock;
bool ret = sock.Connect(argv[1], atoi(argv[2]));
if (ret) {
printf("connect ok\n");
} else {
printf("connect failed\n");
}
// 连接成功后持续持有,不断开
while (1) {
sleep(1);
}
return 0;
}
2.3 步骤与状态观测
-
编译运行服务端:./test_server 127.0.0.1 9090;
-
依次打开 3 个终端启动客户端:./test_client 127.0.0.1 9090;
-
执行netstat -an | grep 9090查看连接状态:
-
服务端监听端口:tcp 0 0 0.0.0.0:9090 0.0.0.0:* LISTEN
-
3 组双向ESTABLISHED连接,内核全连接队列未满;
- (4)启动第 4 个客户端,再次执行netstat 观测:新增一条服务端侧状态为SYN_RECV的记录,客户端侧显示ESTABLISHED。
原因:客户端已完成三次握手并收到服务端 SYN+ACK,本地连接置为 ESTABLISHED;但服务端全连接队列已满,无法将该连接从半连接队列迁移至全连接队列,服务端持续停留在SYN_RECV。
三、TCP 数据结构图解
3.1 层级关系

用户态进程到 TCP 传输控制块的内核层级调用链路:
-
task_struct:Linux 进程核心结构体,包含struct files_struct文件描述符管理结构;
-
fd_array :文件描述符数组,每个 fd 指向独立struct file;
-
struct file私有数据private_data绑定struct socket套接字结构;
-
struct socket 持有通用传输控制块struct sock;
-
struct sock为多态基类,衍生inet_sock、tcp_sock;所有 TCP 连接的收发缓冲区、半 / 全连接队列、状态标识,全部存储在tcp_sock结构体中,是内核管理 TCP 连接的底层载体。
3.2 sk_buff

sk_buff是 Linux 网络协议栈承载单个数据包的标准容器,网卡收发、TCP 队列排队的数据包全部封装为该结构,四个指针划分内存区域:
-
head:整块缓冲区内存起始地址;
-
data:当前有效协议数据起始,可前后移动,用于动态增删二层 / IP/TCP 头部;
-
tail:当前有效数据末尾;
-
end:缓冲区内存边界;
内存分区从前往后依次为:头空间、二层帧头、IP 头、TCP 头、应用层数据、尾空间。头部 / 尾部预留空闲空间,协议栈处理报文时无需拷贝数据,仅移动指针即可完成头部增减,提升网络性能。
图中nr_frags = 2代表数据包存在分片,分片数据通过frag_list链表挂载,适合大报文分段存储。
四、tcpdump 命令行抓包
tcpdump是 Linux 原生命令行抓包工具,无需图形界面,适合服务器线上故障排查,支持精准过滤 IP、端口、TCP 标志位,抓包文件可导出.pcap格式在 Wireshark 中可视化分析。
4.1 安装
-
Ubuntu/Debian 系列
bashsudo apt-get update sudo apt-get install tcpdump -
CentOS/RHEL 系列
bashsudo yum install tcpdump注意:tcpdump 必须 root 权限运行,普通用户执行需加sudo前缀。
4.2 抓包命令分类
4.2.1 按网络接口
bash
# 捕获所有网卡全部TCP报文
sudo tcpdump -i any tcp
# 仅捕获eth0网卡流量
sudo tcpdump -i eth0 tcp
参数说明:-i指定网络接口,any匹配本机全部网卡;末尾tcp过滤仅保留 TCP 协议报文。
4.2.2 按源/目的 IP 报文
bash
# 捕获源IP 127.0.0.1的TCP包
sudo tcpdump src host 127.0.0.1 and tcp
# 捕获发往127.0.0.1的TCP包
sudo tcpdump dst host 127.0.0.1 and tcp
# 双向过滤:仅抓取127.0.0.1与192.168.1.10交互流量
sudo tcpdump src host 127.0.0.1 and dst host 192.168.1.10 and tcp
4.2.3 按端口过滤
bash
# 抓取9090端口所有TCP流量(服务端监听端口)
sudo tcpdump port 9090 and tcp
4.2.4 抓包存储
bash
# 将9090端口抓包数据保存至test.pcap
sudo tcpdump -i any port 9090 -w test.pcap
# 离线读取pcap文件分析数据包
tcpdump -r test.pcap
4.3 注意事项
-
-n:不反向解析主机名、端口名称,避免云服务器 DNS 解析延迟、乱码主机名;线上抓包必加:
-
-v/-vv/-vvv:逐级提升报文打印详细程度,查看 TCP 序列号、窗口大小、校验和;
-
第三次握手纯 ACK 报文数据长度为 0,无应用层负载。
结语
TCP 连接队列是网络编程底层核心基础,看似简单的listen函数背后涉及完整内核队列管理机制。本文通过可复现 C++ 实验代码、内核结构体图解、命令行抓包实验,覆盖理论、代码、观测完整链路。读者们可本地搭建实验环境,修改 backlog 参数重复观测队列溢出现象,加深对 TCP 内核双队列模型的理解。