【Linux】TCP 全连接队列与 tcpdump 抓包

🌈 个人主页:Zfox_
🔥 系列专栏:Linux

一 :🔥 TCP 相关实验理解

🦋 listen 的第二个参数

LISTEN(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);

🌿 这里我们将 TcpServer.cppaccept 代码注释掉
在这里插入图片描述

注意:此时我们的 backlog 为 1

const static int default_backlog = 1;

此时启动 2 个客户端同时连接服务器, 用 netstat 查看服务器状态, 一切正常.
但是启动第 3 个客户端时, 发现服务器对于第 3 个连接的状态存在问题了

在这里插入图片描述

🦋 三次握手过程

🤝 TCP建立连接 的过程称为 三次握手,简单流程如下:

  • 第一次握手客户端发送 SYN 标志,表示发起连接请求;
  • 第二次握手服务器收到请求后,发送 SYN+ACK ,表示接受请求并同步;
  • 第三次握手客户端确认连接,发送 ACK

三次握手完成后,连接进入全连接队列,但此时应用层可能尚未处理这个连接请求。

在这里插入图片描述

💻 客户端状态正常, 但是服务器端出现了 SYN_RECV 状态, 而不是 ESTABLISHED 状态

⚙️ 这是因为, Linux 内核协议栈为一个 tcp 连接管理使用两个队列:

  1. 半连接队列(syn queue)存储已发送 SYN 的连接,但尚未完成三次握手的连接。每当收到 SYN 请求,服务器将这个连接放入半连接队列,直到三次握手完成或超时失败。(用来保存处于 SYN_SENTSYN_RECV 状态的请求)
  2. 全连接队列(accpetd 队列)三次握手完成后,连接进入全连接队列,等待被应用层(通常是通过 accept() 函数)处理。如果全连接队列已满,新连接将被丢弃。 (用来保存处于 ESTABLISHED 状态, 但是应用层没有调用 accept 取走的请求)

🔗 而全连接队列的长度会受到 listen 第二个参数的影响,全连接队列满了的时候,就无法继续让当前连接的状态进入 ESTABLISHED 状态了.

这个队列的长度通过上述实验可知, 是 listen 的第二个参数 + 1

🦋 总结

listen 的第二个参数的本质是当服务器压力很大或者来不及获取新连接的时候,操作系统在底层,在 tcp 层会为我们维护一个全连接队列,这个队列会把新到来的连接维护起来,当我们未来需要的时候再把新连接获取上去,这个队列的最大长度叫做 backlog + 1

🦋 初步理解全连接队列

💻 在操作系统中有应用层,传输层,网络层… 在传输层中有一个接收队列 accept_queue,建立连接时就进行三次握手。操作系统中用户访问的网站多种多样,并且会并发的运行,所以在操作系统内部一定是要通过数据结构来进行管理的!

连接本质就是操作系统内核中的一批数据结构!

在传输层中将这个数据结构放入队列中进行管理!应用层会调用 accept 获取连接,传输层就会返回给一个文件描述符供应用层使用,通过这个文件描述符,应用层就可以进行通信!这个队列就是全连接队列!

当应用层非常忙,来不及 accept,那么全连接队列中会挤压连接,这个总数不能超过 backlog !这个并不代表服务端只能同时处理 backlog + 1 个连接。全连接队列中的连接表示连接成功但来不及及时处理的连接!

全连接队列的本质就是生产消费模型,应用层从其中获取资源,传输层向其中放入资源!这个队列保证了在应用层较忙时无法获取连接时,可以先将一些连接维护起来,等待应用层调用,这样可以大大提升效率,提高连接吞吐量!减少服务端闲置率,增加给用户提供服务的效率和体验!

🦋 深入理解全连接队列

当服务器启动时,本质上是启动一个进程,那么就会有对应的 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 的入口,其内部还包含一个 const struct proto_ops 结构体
在这里插入图片描述

📐 这是一个方法集,集合了 bindconnect… 一系列的函数指针!

虽然我们 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 中最底层的结构体,其中有两个字段:接收队列 和 发送队列
在这里插入图片描述

struct 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 sockstruct inet_sockstruct 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 控制,可以通过调整该值增大队列容量:

echo 1024 > /proc/sys/net/core/somaxconn

此命令将全连接队列的大小增大至 1024。同时,还需在应用程序中设置适当的 backlog 参数,例如在使用 listen() 函数时指定更大的值:

listen(sockfd, 1024);

🦋 使用 SYN Cookie

🤩 当全连接队列满时Linux 内核可以启用 SYN Cookie 技术,避免拒绝新连接。SYN Cookie 通过不将连接放入全连接队列,而是将状态信息嵌入 SYN-ACK 中的序列号中,等到客户端响应 ACK 时,再恢复连接状态。

启用 SYN Cookie

echo 1 > /proc/sys/net/ipv4/tcp_syncookies

SYN Cookie 是一种有效的防御 SYN Flood 攻击的手段,同时能在高并发场景下提升连接处理能力。

三 :🔥 使用 TCP dump 进行抓包, 分析 TCP 过程

自己测试的时候要注意哦, 注意和代码结合哦, 我们代码中故意在 close(sockfd) 那里留了一个问题

TCPDump 是一款强大的网络分析工具, 主要用于捕获和分析网络上传输的数据包。

🦋 安装 tcpdump

🎁 tcpdump 通常已经预装在大多数 Linux 发行版中。 如果没有安装, 可以使用包管理器进行安装。

  • 例如 Ubuntu, 可以使用以下命令安装:
sudo apt-get update
sudo apt-get install tcpdump
  • 在 Red Hat 或 CentOS 系统中, 可以使用以下命令:
sudo yum install tcpdump

🦋 常见使用

  1. 捕获所有网络接口上的 TCP 报文
  • 🍑 使用以下命令可以捕获所有网络接口上传输的 TCP 报文:
$ sudo tcpdump -i any tcp

注意: -i any 指定捕获所有网络接口上的数据包, tcp 指定捕获 TCP 协议的数据包。 i 可以理解成为 interface 的意思

  1. 捕获指定网络接口上的 TCP 报文
  • 🍑 如果你只想捕获某个特定网络接口(如 eth0) 上的 TCP 报文, 可以使用以下命令:
$ 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 tcp
  1. 捕获特定源或目的 IP 地址的 TCP 报文
  • 🍑 使用 host 关键字可以指定源或目的 IP 地址。 例如, 要捕获源 IP 地址为 192.168.1.100 的 TCP 报文, 可以使用以下命令:
$ sudo tcpdump src host 192.168.1.100 and tcp
  • 🍑 要捕获目的 IP 地址为 192.168.1.200 的 TCP 报文, 可以使用以下命令:
$ sudo tcpdump dst host 192.168.1.200 and tcp
  • 🍑 同时指定源和目的 IP 地址, 可以使用 and 关键字连接两个条件:
$ sudo tcpdump src host 192.168.1.100 and dst host 192.168.1.200 and tcp
  1. 捕获特定端口的 TCP 报文
  • 🍑 使用 port 关键字可以指定端口号。 例如, 要捕获端口号为 80 的 TCP 报文(通常是HTTP 请求) , 可以使用以下命令:
$ sudo tcpdump port 80 and tcp
  1. 保存捕获的数据包到文件
  • 🍑 使用 -w 选项可以将捕获的数据包保存到文件中, 以便后续分析。 例如:
$ sudo tcpdump -i eth0 port 80 -w data.pcap

这将把捕获到的 HTTP 流量保存到名为 data.pcap 的文件中。

  • 了解: pcap 后缀的文件通常与 PCAP(Packet Capture) 文件格式相关, 这是一种用于捕获网络数据包的文件格式
  1. 从文件中读取数据包进行分析
  • 🍑 使用 -r 选项可以从文件中读取数据包进行分析。 例如:
tcpdump -r data.pcap

这将读取 data.pcap 文件中的数据包并进行分析

注意事项

  • 使用 tcpdump 时, 请确保你有足够的权限来捕获网络接口上的数据包。 通常, 你需要以 root 用户身份运行 tcpdump
  • 使用 tcpdump 的时候, 有些主机名会被云服务器解释成为随机的主机名, 如果不想要, 就用 -n 选项
  • 主机观察三次握手的第三次握手, 不占序号

🦋 通过抓包验证三次握手和四次挥手的过程:

📦 此时需要用到 gitee 上的测试代码进行验证:

  • 首先我们使用抓取源目的 ip 和 tcp 的方式 抓取客户端发来的 SYN 数据包(第一次握手) 此时只启动客户端:
tcpdump -n src host ip地址 and tcp

在这里插入图片描述

此时我们就抓取到了 Flags 为 S 的 SYN 报文

💻 此时再将服务端和客户端同时启动由于我是在同一台主机上做的测试,所以会重复,实际上圈出来的三个就是 SYNSYN + ACKACK , 可以看到第二个 ACK 就是对第一个 SYN 的确认序号

在这里插入图片描述

第三次的 ACK 就自动置 1 了, 双方开始正常通信

此时我们直接 CTRL + C 杀掉客户端 可以发现抓取到 FIN 标识位 和 ACK 但是为什么只有两次挥手呢?

🧱 我们有理由相信我们代码中是有 bug 的!! 很有可能没有关闭文件描述符!!!
在这里插入图片描述
🍉 果然 我们忘记加上 close() 了;
在这里插入图片描述
此时由于客户端和服务器关闭连接几乎是同时的,此时就造成了捎带应答!!!

在这里插入图片描述
此时我们让服务器 sleep 1 秒再退出

在这里插入图片描述
此时就能看到标准的 四次挥手了!!!

在这里插入图片描述

四:🔥 共勉

以上就是我对 【Linux】TCP 全连接队列与 tcpdump 抓包 的理解,觉得这篇博客对你有帮助的,可以点赞收藏关注支持一波~😉
在这里插入图片描述

评论 81
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值