当前位置: 首页 > article >正文

TCP全连接队列和tcpdump抓包

全连接队列

listen第二个参数

服务器在调用listen的时候,listen的第二个参数  + 1,就是TCP全连接队列的长度。

当客户端的连接进入established 状态后,如果服务器没有调用accept将连接取走,那么该连接就会待在TCP全连接队列中,直到上层调用accept将其取走。

 在全连接队列中的连接可以维持很长一段时间,除非上层调用accep取走,或者被上层主动关闭了;如果全连接队列满了,那么新来的链接就无法进入established状态,OS也会为这种状态的连接建立一种临时的数据结构,这个数据结构里的很多字段都未初始化完,会被OS保存在半连接队列中,在半连接队列中的连接保存时间一般比较短,一般是半分钟到一分钟。

为什么要有全连接队列?

当服务器很忙的时候,来不及调用accept接口,那么此时新连接就会被放在全连接队列当中,当服务器空闲下来时,就可以直接将全连接队列中的连接取走,然后进行业务处理。如果没有全连接队列,那么当服务器突然空闲下来的时候,这时候突然又没有连接来访问了,这就导致服务器的对资源的利用效率降低,吞吐量降低。会增加服务器的闲置率,减少给用户提供服务的效率和体验。

那全连接队列很长可以吗?

当然不可以。假设一个新来的用户一过来就在全连接末尾排队,说明此时的服务器压力已经很大了。如果这个队列很长,为了维护这个队列占用了大把的内存空间,服务器的处理速度慢,用户还要排很长时间,那为什么不让这个队列长度短一点,把节省下来的内存空间给服务器进行运算呢?

这个全连接的本质其实就是生产者消费者模型。服务器相当于消费者,负责处理连接,客户端相当于生产者,创造连接,并且将连接放入到缓冲区,也就是全连接队列中。

总结:

  全连接队列本质上就是当服务器压力太大的时候,OS会在底层会为服务器将来不及处理的连接维护起来,等服务器空闲的时候再把连接获取上去。其中队列的长度就是listen的第二个参数 back_log + 1。 

从内核层面理解socket和连接

连接的本质也是一种数据结构。 

服务器也是一个进程,它有一个自己的task_struct 结构体,内部有一个自己的文件描述符表 struct files_struct 里面有一个 struct file* fd_array[] 文件描述符数组。

并且在进程启动的时候,OS会默认给我们打开 标准输入,标准输出,标准错误输出,分别占了 0,1,2三个文件描述符。在这里,当我们创建listen套接字的时候,会给用户返回3号这个文件描述符。既然它有文件描述符,那么也有自己的struct file结构体。

以上是文件系统部分。当我们创建socket套接字的时候,内核会帮我们创建一个 struct socket结构体。

我们发现这里面包含了一个回指向struct file结构体的指针

 另外在struct file结构体中也包含了如下字段

void* private_data。

 在创建套接字的时候,这个void* private_data会指向struct socket,于是它俩之间就关联起来了!

对于struct socket结构体,我们可以理解它是网络socket的入口。 为什么这么说呢,我们再来详细说明一下struct socket内部的一些字段。

 

 const struct proto_ops* 这是一个指针,里面包含了一组方法簇。

所以上层在读写套接字时,就使用这里面的函数指针调用不同的方法。 

当我们创建TCP套接字的时候,OS会在底层为用户创建一个struct tcp_sock

 这就是在三次握手完成以后,OS会在内核给连接创建一个数据结构,就是它。

仔细留意这个结构体的第一个成员 ,又是一个结构体,类型是 inet_connection_sock。示意图:

 在inet_connection_sock结构体中包含了很多跟tcp连接相关的信息,如下:

并且在这里面 

struct request_sock_queue就是管理TCP全连接队列的。 

 但是我们又发现了它的第一个成员还是一个结构体

也就是struct inet_sock。 看一下它里面长啥样,inet_sock意思也就是网络套接字的意思。

 所以我们看到了很多跟网络相关的字段,比如端口号,ip等。示意图又多了一层

 但是又发现这个结构体居然还嵌套了一个结构体

也就是 struct sock 。所以示意图再加一层

 但是这个结构体是不是很眼熟?没错,这就是一开始在 struct socket结构体里面,有一个指向它的指针:

 struct sock结构体:

 这里面包含的更多的是一些报文的信息。其中里面还包含了两个很重要的字段

它俩就是接收和发送缓冲区。

这里面也有指针

 再回到刚刚,所以只要OS创建了一个 tcp_sock

那么刚刚嵌套的那些结构体自然也就都有了。并且我们注意到,这些嵌套的结构体都是上一层的第一个字段,也就意味着可以直接用指针来进行访问! 需要访问哪一个结构体内部的字段,只需要对这个指针进行相应的强转即可。这其实也就是C风格的多态。

另外UDP也有自己的套接字

它的第一个字段也是一个结构体,但是与TCP不一样的是,它的一个字段的结构体直接就是 inet_sock,也就是网络相关的套接字。因为UDP的实现比TCP简单,它不需要连接队列什么的,所以它不需要那么多字段。 

所以相比之下,UDP不需要再嵌套 inet_connection_sock这个结构体。

同理

 struct sock* 经过强转,同样也可以指向udp_sock。

它们的方法集也会变得不同。

在struct socket里面还有一个 type字段,就可以标识这是一个tcp还是一个udp套接字。

这样一看,struct socket可以看作基类,tcp_sock 和udp_sock可以看作是子类。

所以struct socket也成为 BSD socket 也就是通用网络接口。

学到这里其实可以在系统层面上给网络进行分层,

struct file属于第一层 :虚拟文件层。未来所有的套接字都可以变成文件。

struct socket 属于第二层:通用套接字层。

inet_sock 属于第三层:通用网络层,因为inet可以本地通信,所以不只有tcp和udp两种套接字。

struct inet_device 属于第四层: 网络设备层。跟网卡设备打交道的。(了解)

刚刚说的都是关于创建listen套接字的。

那么接收连接呢?

假设listen套接字的文件描述符是3,那么当三次握手成功之后,OS就会为新连接创建一个tcp_sock结构体,当然这个连接不用关系全连接队列的这些字段。创建好之后,就会把这个结构体放入到 3号文件的 全连接队列里。

当上层调用accept获取后,那么OS就会创建一个 struct file,和一个 struct socket,此时的文件描述符就是4,并将 struct socket 里面的 struct sock* 指向刚刚拿上来的 tcp_sock,这样也就关联起来了。那么以后就可以直接通过4号文件描述符,来对这个套接字进行读和写的操作了。 

补充:

在sk_buff里面,通过控制指针的移动来对报文进行解包,提取有效载荷。 

抓包

使用TCP dump抓包 

Ubuntu中,如果没有安装,可以先安装

sudo apt-get update
sudo apt-get install tcpdump

 常见使用:

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

另外云服务和本地的抓包一般是不一样的。

ifconfig

lo就是本地环回。云服务器下网络通信用的就是eth0接口。 

指定特定源IP地址 && 特定目的IP地址

比如

sudo tcpdump src host 192.168.1.100 and dst host 192.168.1.200
and tcp

src host表示的是指定源IP地址,dst host表示的是指定目的IP地址。

指定端口号抓包

例如捕获端口号为80的包(访问80端口的包)

sudo tcpdump port 80 and tcp

 保存捕获的数据包到文件,例如:

sudo tcpdump -i eth0 port 80 -w data.pcap

将抓到的包保存在 data.pcap文件中。

另外使用 tcpdump 的时候,有些主机名会被云服务器解释成为随机的主机名,如果不 想要,就用  -n 选项

 三次握手的抓包示例(服务器端启动的抓包软件):

 一个包的标志位有一个 S,说明是对方发送了 SYN请求,接着服务器发送了 S. 说明了服务器发送了SYN + ACK。最后客户端发了 . ,说明是一个ACK,至此三次握手完成。并且注意一些细节,在第二个包 SYN + ACK的时候,注意到 ack是上一个 seq 的序列号 + 1,接着在第三个包客户端给服务器 ACK时,此时的 ack = 1,这次的ack不是说确认了序号1,它是被置为了1,而且没有携带自己的序号seq。

四次挥手的抓包示例:

这里看着好像只有三次挥手。其实是因为在服务器的代码逻辑中,客户端退了,服务器立马就关闭客户端的文件描述符了,所以在图中的第二个包,服务器发送给客户端的也是一个FIN + ACK,也就是捎带应答。

如果客户端关闭了,但是服务器还有数据要给客户端发送,然后再关闭,那么就会是正常的四次挥手,如下:

 


http://www.kler.cn/a/302886.html

相关文章:

  • 一键生成本地SSL证书:打造HTTPS安全环境
  • 量化交易系统开发-实时行情自动化交易-3.4.2.2.Okex交易数据
  • PostgreSQL分区表:基础语法与运维实践
  • 从0开始学习Linux——文件管理
  • Appium配置2024.11.12
  • 前端常用布局模板39套,纯CSS实现布局
  • MinIO【部署 02】Linux集群版本及Windows单机版、单机多目录版、分布式版(cmd启动脚本及winsw脚本分享)
  • 模版方法模式template method
  • CMU 10423 Generative AI:lec3(Learning Large Language Models)
  • vim 安装与配置教程(详细教程)
  • Linux学习-Ansible(二)
  • 解码企业数字化转型的四大核心促因
  • 数据结构加餐:三路划分、自省排序、文件归并排序
  • vue3 使用swiper制作带缩略图的轮播图
  • 视频笔记1
  • Winform实现弹出定时框功能
  • HarmonyOS开发之(下拉刷新,上拉加载)控件pulltorefresh组件的使用
  • 汽车材料展︱2025 广州国际汽车轻量化技术及车用材料展览会
  • 用Qt 对接‌百度语音识别接口
  • 如何使用studio layout inspector
  • 工具、环境等其他小问题归纳
  • uniapp对tabbar封装,简单好用
  • Unity3d中制作触发区域为圆形的按钮
  • YOLOv5-6.x源码分析----数据集创建之dataloaders.py
  • 【Python 千题 —— 算法篇】寻找最长回文子串
  • JavaWeb【day11】--(SpringBootWeb案例)