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中,如果没有安装,可以先安装

bash 复制代码
sudo apt-get update
sudo apt-get install tcpdump

常见使用:

bash 复制代码
sudo tcpdump -i any tcp

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

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

bash 复制代码
ifconfig

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

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

比如

bash 复制代码
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端口的包)

bash 复制代码
sudo tcpdump port 80 and tcp

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

bash 复制代码
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,也就是捎带应答。

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

相关推荐
铁松溜达py1 小时前
“MIME 媒体类型“用来标识网络传输内容的格式标准
开发语言·网络·python·媒体
硬核科技2 小时前
变压器在电源中的核心作用
网络·单片机·嵌入式硬件·硬件工程·智能硬件·开关电源
毅凉2 小时前
Linux笔记
linux·c语言·网络·数据库
FPGA_Linuxer3 小时前
xilinx hbm ip运用
网络·网络协议·tcp/ip
i嗑盐の小F3 小时前
【 ACM独立出版,见刊后1个月检索!!!】第二届通信网络与机器学习国际学术会议(CNML 2024,10月25-27)
网络·图像处理·人工智能·深度学习·算法·机器学习·计算机视觉
金灰3 小时前
wx小程序渗透思路
网络·windows·安全·小程序·notepad++
mit6.8243 小时前
[Linux#49][UDP] 2w字详解 | socketaddr | 常用API | 实操:实现简易Udp传输
linux·网络·c++·笔记·后端
南叔先生3 小时前
Linux 性能优化 copy
网络·数据库·php
不悔哥4 小时前
openwrt wsdd模块介绍
linux·c语言·网络·tcp/ip·智能路由器