


目录
[一 :🔥 TCP 相关实验理解](#一 :🔥 TCP 相关实验理解)
[1-1 listen 的第二个参数](#1-1 listen 的第二个参数)
[1-2 三次握手过程](#1-2 三次握手过程)
[1-3 总结](#1-3 总结)
[1-4 初步理解全连接队列](#1-4 初步理解全连接队列)
[1-5 深入理解全连接队列](#1-5 深入理解全连接队列)
[二 :🔥 全连接队列满的优化](#二 :🔥 全连接队列满的优化)
[🐦 增大全连接队列大小](#🐦 增大全连接队列大小)
[🐦 使用 SYN Cookie](#🐦 使用 SYN Cookie)
[三 :🔥 使用 TCP dump 进行抓包, 分析 TCP 过程](#三 :🔥 使用 TCP dump 进行抓包, 分析 TCP 过程)
[🐦 安装 tcpdump](#🐦 安装 tcpdump)
[🐦 常见使用](#🐦 常见使用)
[3-2 🥪 通过抓包验证三次握手和四次挥手的过程:](#3-2 🥪 通过抓包验证三次握手和四次挥手的过程:)
前言
TCP(传输控制协议)作为互联网中保障可靠通信的核心协议,其底层机制与实际应用优化一直是网络技术学习与实践的重点。本次内容围绕TCP相关实验、全连接队列优化及抓包分析展开,通过对listen参数、三次握手等核心机制的实验探究,深入剖析全连接队列的运行逻辑与优化方案,并结合tcpdump抓包工具的实操应用,验证TCP关键通信过程。旨在通过"理论探究+实验验证+优化实践"的思路,系统梳理TCP协议的核心知识点,为理解TCP通信原理、解决实际网络问题提供清晰的理论与实践支撑。
一 :🔥 TCP 相关实验理解
1-1 listen 的第二个参数
cppLISTEN(2) Linux Programmer's Manual NAME listen - listen for connections on a socket SYNOPSIS #include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int listen(int sockfd, int backlog);
- 基于封装的 TcpSocket 实现以下测试代码
- 对于服务器, listen 的第二个参数设置为 1, 并且不调用 accept
- 测试代码链接:https://gitee.com/longleizhi/linux-tcp-network-program/tree/master/TestBacklog
🌿 这里我们将 TcpServer.cpp 的 accept 代码注释掉
注意:此时我们的 backlog 为 1
cppconst static int default_backlog = 1;此时启动 2 个客户端同时连接服务器, 用 netstat 查看服务器状态, 一切正常.
但是启动第 3 个客户端时, 发现服务器对于第 3 个连接的状态存在问题了
1-2 三次握手过程
🤝 TCP建立连接 的过程称为 三次握手,简单流程如下:
- 第一次握手 :客户端发送 SYN 标志,表示发起连接请求;
- 第二次握手 :服务器收到请求后,发送 SYN+ACK ,表示接受请求并同步;
- 第三次握手 :客户端确认连接,发送 ACK。
三次握手完成后,连接进入全连接队列,但此时应用层可能尚未处理这个连接请求。
💻 客户端状态正常, 但是服务器端出现了 SYN_RECV 状态, 而不是 ESTABLISHED 状态
⚙️ 这是因为, Linux 内核协议栈为一个 tcp 连接管理使用两个队列:
- 半连接队列(syn queue): 存储已发送 SYN 的连接,但尚未完成三次握手的连接 。每当收到 SYN 请求,服务器将这个连接放入半连接队列,直到三次握手完成或超时失败。(用来保存处于 SYN_SENT 和 SYN_RECV 状态的请求)
- 全连接队列(accpetd 队列): 三次握手完成后,连接进入全连接队列,等待被应用层(通常是通过 accept() 函数)处理 。如果全连接队列已满,新连接将被丢弃。 (用来保存处于 ESTABLISHED 状态, 但是应用层没有调用 accept 取走的请求)
🔗 而全连接队列的长度会受到 listen 第二个参数的影响,全连接队列满了的时候,就无法继续让当前连接的状态进入 ESTABLISHED 状态了.
这个队列的长度通过上述实验可知, 是 listen 的第二个参数 + 1
1-3 总结
listen 的第二个参数的本质是当服务器压力很大或者来不及获取新连接的时候,操作系统在底层,在 tcp 层会为我们维护一个全连接队列,这个队列会把新到来的连接维护起来,当我们未来需要的时候再把新连接获取上去,这个队列的最大长度叫做 backlog + 1
1-4 初步理解全连接队列
💻 在操作系统中有应用层,传输层,网络层... 在传输层中有一个接收队列 accept_queue ,建立连接时就进行三次握手。操作系统中用户访问的网站多种多样,并且会并发的运行,所以在操作系统内部一定是要通过数据结构来进行管理的!
连接本质就是操作系统内核中的一批数据结构!
在传输层中将这个数据结构放入队列中进行管理!应用层会调用 accept 获取连接,传输层就会返回给一个文件描述符供应用层使用,通过这个文件描述符,应用层就可以进行通信!这个队列就是全连接队列!
当应用层非常忙,来不及 accept ,那么全连接队列中会挤压连接,这个总数不能超过 backlog !这个并不代表服务端只能同时处理 backlog + 1 个连接。全连接队列中的连接表示连接成功但来不及及时处理的连接!
全连接队列的本质就是生产消费模型,应用层从其中获取资源,传输层向其中放入资源!这个队列保证了在应用层较忙时无法获取连接时,可以先将一些连接维护起来,等待应用层调用,这样可以大大提升效率,提高连接吞吐量!减少服务端闲置率,增加给用户提供服务的效率和体验!
1-5 深入理解全连接队列
当服务器启动时,本质上是启动一个进程,那么就会有对应的 task_struct。在这个结构体中都会有 struct files_struct !其中包含文件描述符表 struct file*fd_array[],每个元素都指向文件结构体 struct file。
🍺 当创建网络套接字时,会创建一个 struct socket 结构体!在内核中时这样一个结构:
🦈 可以看到 struct socket 结构体内部有一个 struct file 结构体,但是未来我们是想通过文件描述符找到对应的套接字,然后进行读取数据。可是现在是 struct socket 结构体内部有一个 struct file 结构体,如果通过 struct file 结构体找到套接字呢?
🐑 在 struct file 结构体有一个指针 void private_data*, 这个指针指向 struct socket 结构体。这样两个结构体就联系起来了!
🚪 struct socket 结构体是 网络 Socket 的入口,其内部还包含一个 c****onst struct proto_ops 结构体
📐 这是一个方法集,集合了 bind,connect ... 一系列的函数指针!
虽然我们 struct socket 结构体是内核中的套接字结构,但建立连接时真实的数据结构是 struct socket 结构体中 struct sock sk 所指向的 tcp_sock 结构体!
🛜 这是 TCP 套接字,其中包含了慢启动算法阈值* ,拥塞窗口大小 ,关联进程... 一系列 TCP 协议中的对应字段!这个 tcp_sock 就是三次握手时候建立的结构体!其中的第一个成员 struct inet_connection_sock 是复制连接属性的! 这里就包含连接的相关信息。全连接队列就在这个结构体中!
- icsk_accept_queue
这里有超时重传的触发时间,TCP 连接的状态,握手失败重试次数,全连接队列...等数据。
struct inet_connection_sock 中的第一个成员是 struct inet_sock 结构体,这是网络层的结构体。
🏡 struct inet_sock 结构体其中包含了 目的端口号 ,源端口号 ,目的 IP 地址 和 源 IP 地址 等数据!更重要的是其中第一个成员是 struct sock 结构体,里面包含着报文的一些属性 。这是整个 tcp_sock 中最底层的结构体,其中有两个字段:接收队列 和 发送队列
cppstruct sk_buff_head sk_receive_queue; struct sk_buff_head sk_write_queue;这两个队列对于网络通信至关重要,因为它们直接参与了数据的接收和发送过程。今天不详细讲解。
我们再回过来看 struct socket,其中有一个结构体指针 struct sock sk*,这个指针可以指向 tcp_sock 中最底层的 struct sock 结构体,然后可以通过类型转换,最终读取到整个 tcp_sock 结构体!也就是说,这个指针指向了 tcp_sock 结构体!
通过强制类型转换 这个指针可以直接指向struct sock、struct inet_sock、struct ine_connection_sock访问对应的数据
通过结构体嵌套的方式,使用公共指针指向结构体头部对象的方式 这就是 C风格的多态! 此时struct sock就是基类!
🪣 同样的创建 UDP 套接字时,udp_sock 的第一个成员是 struct inet_sock 结构体(因为 udp 不需要连接所以没有包含连接属性结构体)。那么最终也是一个 struct sock 结构体,所以也可以通过C风格的多态实现!
通过 基类 struct socket,我们可以进行 tcp 和 udp 的通信,所以说他是网络 socket 的入口。
二 :🔥 全连接队列满的优化
💻 在服务器全连接队列满时,我们可以采取以下措施进行优化:
🐦 增大全连接队列大小
🍝 在 Linux 系统中,全连接队列 的大小由 /proc/sys/net/core/somaxconn 控制,可以通过调整该值增大队列容量:
bashecho 1024 > /proc/sys/net/core/somaxconn此命令将全连接队列的大小增大至 1024。同时,还需在应用程序中设置适当的 backlog 参数,例如在使用 listen() 函数时指定更大的值:
cpplisten(sockfd, 1024);🐦 使用 SYN Cookie
当全连接队列满时,Linux 内核可以启用 SYN Cookie 技术,避免拒绝新连接。SYN Cookie 通过不将连接放入全连接队列,而是将状态信息嵌入 SYN-ACK 中的序列号中,等到客户端响应 ACK 时,再恢复连接状态。
启用 SYN Cookie:
bashecho 1 > /proc/sys/net/ipv4/tcp_syncookiesSYN Cookie 是一种有效的防御 SYN Flood 攻击的手段,同时能在高并发场景下提升连接处理能力。
三 :🔥 使用 TCP dump 进行抓包, 分析 TCP 过程
自己测试的时候要注意哦, 注意和代码结合哦, 我们代码中故意在 close(sockfd) 那里留了一个问题
TCPDump 是一款强大的网络分析工具, 主要用于捕获和分析网络上传输的数据包。🐦 安装 tcpdump
tcpdump 通常已经预装在大多数 Linux 发行版中。 如果没有安装, 可以使用包管理器进行安装。
- 例如 Ubuntu , 可以使用以下命令安装:
bashsudo apt-get update sudo apt-get install tcpdump
- 在 Red Hat 或 CentOS 系统中, 可以使用以下命令:
bashsudo yum install tcpdump🐦 常见使用
1。捕获所有网络接口上的 TCP 报文
- 🍑 使用以下命令可以捕获所有网络接口上传输的 TCP 报文:
bash$ sudo tcpdump -i any tcp注意: -i any 指定捕获所有网络接口上的数据包, tcp 指定捕获 TCP 协议的数据包。 i 可以理解成为 interface 的意思
2.捕获指定网络接口上的 TCP 报文
- 🍑 如果你只想捕获某个特定网络接口(如 eth0) 上的 TCP 报文, 可以使用以下命令:
cpp$ ifconfig eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500 inet 172.18.45.153 netmask 255.255.192.0 broadcast 172.18.63.255 inet6 fe80::216:3eff:fe03:959b prefixlen 64 scopeid 0x20<link> ether 00:16:3e:03:95:9b txqueuelen 1000 (Ethernet) RX packets 34367847 bytes 9360264363 (9.3 GB) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 34274797 bytes 6954263329 (6.9 GB) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 $ sudo tcpdump -i eth0 tcp3.捕获特定源或目的 IP 地址的 TCP 报文
- 🍑 使用 host 关键字可以指定源或目的 IP 地址。 例如, 要捕获源 IP 地址为 192.168.1.100 的 TCP 报文, 可以使用以下命令:
bash$ sudo tcpdump src host 192.168.1.100 and tcp
- 🍑 要捕获目的 IP 地址为 192.168.1.200 的 TCP 报文, 可以使用以下命令:
bash$ sudo tcpdump dst host 192.168.1.200 and tcp
- 🍑 同时指定源和目的 IP 地址, 可以使用 and 关键字连接两个条件:
bash$ sudo tcpdump src host 192.168.1.100 and dst host 192.168.1.200 and tcp4.捕获特定端口的 TCP 报文
- 🍑 使用 port 关键字可以指定端口号。 例如, 要捕获端口号为 80 的 TCP 报文(通常是HTTP 请求) , 可以使用以下命令:
bash$ sudo tcpdump port 80 and tcp5.保存捕获的数据包到文件
- 🍑 使用 -w 选项可以将捕获的数据包保存到文件中, 以便后续分析。 例如:
bash$ sudo tcpdump -i eth0 port 80 -w data.pcap这将把捕获到的 HTTP 流量保存到名为 data.pcap 的文件中。
- 了解: pcap 后缀的文件通常与 PCAP(Packet Capture) 文件格式相关, 这是一种用于捕获网络数据包的文件格式
6.从文件中读取数据包进行分析
- 🍑 使用 -r 选项可以从文件中读取数据包进行分析。 例如:
bashtcpdump -r data.pcap这将读取 data.pcap 文件中的数据包并进行分析
注意事项
- 使用 tcpdump 时, 请确保你有足够的权限来捕获网络接口上的数据包。 通常, 你需要以 root 用户身份运行 tcpdump 。
- 使用 tcpdump 的时候, 有些主机名会被云服务器解释成为随机的主机名, 如果不想要, 就用 -n 选项
- 主机观察三次握手的第三次握手, 不占序号
3-2 🥪 通过抓包验证三次握手和四次挥手的过程:
📦 此时需要用到 gitee 上的测试代码进行验证:
- 首先我们使用抓取源目的 ip 和 tcp 的方式 抓取客户端发来的 SYN 数据包(第一次握手) 此时只启动客户端:
bashtcpdump -n src host ip地址 and tcp此时我们就抓取到了 Flags 为 S 的 SYN 报文
💻 此时再将服务端和客户端同时启动, 由于我是在同一台主机上做的测试,所以会重复,实际上圈出来的三个就是SYN、SYN + ACK、ACK, 可以看到第二个**
ACK就是对第一个SYN**的确认序号第三次的 ACK 就自动置 1 了, 双方开始正常通信
此时我们直接 CTRL + C 杀掉客户端 可以发现抓取到 FIN 标识位 和 ACK 但是为什么只有两次挥手呢?
🧱 我们有理由相信我们代码中是有bug的!! 很有可能没有关闭文件描述符!!!
🍉 果然 我们忘记加上 close() 了;
此时由于客户端和服务器关闭连接几乎是同时的,此时就造成了捎带应答!!!
此时我们让服务器 sleep 1 秒再退出
此时就能看到标准的 四次挥手了!!!
提炼与总结
一、TCP相关实验核心认知
实验是理解TCP底层逻辑的关键,核心聚焦连接建立与队列管理:
listen参数:第二个参数指定半连接队列(SYN队列)上限,影响服务器接收新连接的初始能力。
三次握手:通过"SYN→SYN+ACK→ACK"交互,同步双方序列号、确认通信能力,为可靠传输奠基。
全连接队列:存已完成三次握手、待应用接收的连接,需与半连接队列区分;深入理解需关联状态转换、队列满影响及处理机制。
二、全连接队列满的优化策略
全连接队列满会丢弃新连接,核心优化方案:
增大队列大小:调系统参数扩容量,适用于硬件充足、应用处理效率高的场景,提升缓冲能力。
启用SYN Cookie:半连接队列将满时,编码关键信息到SYN+ACK的Cookie字段,减少队列占用,缓解全连接队列压力,适用于高并发场景。
三、TCPdump抓包的实践价值与核心应用
TCPdump是验证TCP理论、排查问题的核心工具,核心要点:
基础应用:完成安装后,掌握网卡、端口等过滤规则,实现精准抓包。
实践价值:直观验证三次握手、四次挥手过程,呈现序列号、标志位等关键信息,实现理论与实际对应。
四、整体核心脉络
核心脉络:以"理论落地实践"为核心,通过实验探究连接建立(三次握手)与队列管理;针对队列满问题提供"扩容+机制优化"方案;借TCPdump完成理论验证,形成闭环。重点掌握:三次握手可靠性、两队列关联机制、SYN Cookie原理、抓包过滤分析能力。
四:结束语
以上是我对于【Linux网络编程】 TCP 全连接队列与 tcpdump 抓包 的理解
感谢您的三连支持!!!























