自学内容网 自学内容网

【linux拓展(三)】深入理解socket的原理

💓博主CSDN主页:杭电码农-NEO💓

⏩专栏分类:Linux从入门到精通

🚚代码仓库:NEO的学习日记🚚

🌹关注我🫵带你学更多操作系统知识
  🔝🔝


1. 前言

不知道各位看官是否写过网络套接字编程, 你有没有发现, 使用accept函数获取连接时, 是获取了一个文件描述符fd,通过操作这个fd来进行网络的IO,那么网络的连接和文件描述符fd到底有什么关系呢?

本章重点:

本篇文章会讲诉TCP全连接队列的概念和实质,讲解一个TCP连接对应的实体, 以及Linux内核的实现方案, 最后会讲解文件是如何与连接关联到一起的


2. 理解全连接队列

在进行socket套接字编程时, listen函数的第二个参数, 我们在学习初期, 往往会将它设置为0. 但是这个参数(backlog)究竟代表什么?(下面为man手册)

在这里插入图片描述

先说结论
listen的第二个参数为底层全连接队列的最大连接数+1

那么到底什么是全连接队列呢?

全连接队列本质上就是一块内存缓冲区, 当客户端与服务器完成三次握手后, 操作系统会将连接抽象为一个结构体, 并将此结构体链接到全连接队列中, 一旦上层调用函数accept, 就会从全连接队列中拿走一个连接, 若全连接队列已满, 则三次握手就不会成功! 我们可以这样来做测试: 将listen的第二个参数设置为0,代表全连接队列中最多只存放一个连接, 并且我们将accept函数给注释掉, 保证在全连接队列中的连接不会被拿走. 这时再尝试和服务器通信

服务器代码示例:

服务器代码示例(点击跳转)

此时使用telnet或其他方式访问服务器, 我们会发现, 只能有一个连接, 第二个连接会connect error, 这其实就代表此时的服务器的全连接队列满了, 并且, 及时不调用accept函数, 发现第一个连接的三次握手也能正确进行. 这也证明了accept是不参与三次握手的,它仅仅只是将连接拿走而已

在这里插入图片描述


3. 理解套接字和文件的关系

先明确一点:
在代码中创建套接字时,内核会创建一个struct socket结构体
0,1,2号fd默认是标准输入/输出/错误
所以在创建套接字时, 往往对应的fd是3
所以套接字到底是怎样和fd联系起来的?

先看一下进程和文件的联系

在这里插入图片描述

再来看看file结构体的源码

在这里插入图片描述

Linux的源码当中, file结构体中有一个指针, 它指向的其实就是struct socket结构体, 以此来将文件和我们的套接字联系起来, 但是这样还远远不够, 下面再来看看socket结构体的源码

socket结构体源码
在这里插入图片描述

可以看见, socket结构体中也有一个指针回指向file结构体,这样就连串起来了

在这里插入图片描述


4. 理解套接字和连接的关系

先明确一点:
连接其实是一个结构体,TCP连接对应的结构体是tcp_sock

接下来是tcp_sock的多层嵌套结构

先来看看tcp_sock的源码
在这里插入图片描述

在tcp_sock结构体中,第一个成员是struct inet_connection_sock对象, 先记住, 后面再看它的结构. 我们可以发现, 会有max_window等字段, 这代表的是最大接受窗口.

struct inet_connection_sock的结构剖析
在这里插入图片描述

struct inet_connection_sock中的第一个成员为struct inet_sock结构, 先记住它. 然后可以看看下面的一些字段, request_sock_queue代表连接队列,inet_bind_bucket代表绑定的网络IP等信息, 这些字段我们都有迹可查

struct inet_sock的结构剖析
在这里插入图片描述

struct inet_sock的第一个成员是struct sock类型变量,先记住它. 我们还可以在此结构中看见inet_addr等字样, 代表对方和我们的IP地址

总结出结论
经过了这么多次的嵌套结构, 我们可以发现一件事情, struct sock这个结构体变量, 一定是连接实体: tcp_sock的第一个成员. 这点通过不断的嵌套,相信大家能够理解. 最后, 我们再来看套接字对应的实体: socket的结构, 会发现此结构体中有一个字段: struct sock* 这个指针类型恰好指向的tcp_sock结构体的第一个成员,请看下图

在这里插入图片描述

当我们需要使用struct inet_sock结构体中的字段时, 只需要对这个指针进行强转为struct inet_sock类型. 当我们需要使用struct inet_connection_sock结构体中的字段时,只需要对这个指针进行强转为struct inet_connection_sock类型,以此类推. 这种通过结构体嵌套进行不同结构访问的操作, 叫做"C语言风格的多态"

综上所述,套接字实体中存在一个指针,指向: 连接实体中的第一个成员,以此来把套接字和连接建立起联系


5. 打通连接,文件和套接字

所以我们可以通过操作一个文件(fd)来控制最底层连接的最根本原因, 是因为文件和套接字直接关联, 而套接字又和底层的连接直接关联, 所以Linux系统下一切皆文件,只需要操作fd就能操作连接. 并且如果你有兴趣去看看Linux内核代码的话,你会发现在这些嵌套结构体中,会包含输入输出缓冲区,全连接队列信息,IP地址信息, 最大窗口大小信息, 慢启动参数等双方通信时会使用到的内容,都包含在其中,那么站在内核角度,就可随意操控这些变量的值了

使用accept拿走连接后发现的事

三次握手后,会创建一个tcp_sock并连接到3号fd对应的全连接队列中. 使用accept拿连接时,会创建一个struct file和struct socket结构体, 然后再创建一个4号fd(socket结构),此结构中的struct sock指针会指向底层的tcp连接(tcp_sock),从而4号fd就对应了底层的连接了


6. 总结以及拓展

现在终于知道为什么操作fd就能对底层的连接情况进行修改了吧,其实Linux的内核设计的是非常优雅的, 可以来看看udp_sock结构体,对应udp连接

在这里插入图片描述

可以看见udp_sock结构的第一个成员是inet_sock,是不是很熟悉,是的没错, udp_sock仅仅只是比tcp_sock少了一个inet_connection_sock结构,他们的底层结构其实是一样的!Linux系统通过这种方式来实现多态,是不是很吊.其实不仅如此,Linux系统是用C语言实现多态的鼻祖,在VFS当中,还用到了不同的方法实现多态,感兴趣可以去看看


原文地址:https://blog.csdn.net/m0_61982936/article/details/142786188

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!