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

从内核数据结构的角度理解socket

目录

一、socket核心:内核数据结构的三角关系

(1)struct socket:网络协议的抽象接口

(2)struct sock:传输层协议的控制中心

(3)struct file与files struct:文件系统的接入层

二、Socket调用全流程

(1)socket():创建核心结构,奠定基础协议

(2)bind():绑定地址,确定网络身份

(3)监听与连接:TCP 特有流程,依赖队列管理

1.listen():初始化连接队列,进入监听状态

2.connect():客户端发起连接,触发三次握手

3.accept():提取已完成连接,创建新套接字

(4)数据传输:read()/write() 与缓冲区管理

1.TCP 数据传输:可靠传输的缓冲区协作

2.UDP 数据传输:简单无状态的队列管理

(5)close():释放资源,终止连接


        在Linux网络编程中,socket是连接用户态和内核态的核心接口。从socket创建套接字到close关闭套接字,每一步都伴随着内核数据结构的创建、修改与销毁。本文将从内核数据结构的角度,详解socket的调用流程,重点对比TCP、UDP在数据结构上的设计差异,揭示网络是如何统一到文件系统中的。

        从数据结构视角看,Socket 的调用过程本质是内核通过 “三层结构”(files_structstruct filestruct socketstruct sock)实现的资源管理流程。TCP 因需可靠传输和连接管理,其 struct sock 设计复杂,包含大量状态和队列字段;UDP 作为无连接协议,结构精简,聚焦高效数据转发。

        这种 “统一框架(文件系统 + socket 抽象)+ 差异化实现(协议专属 struct sock)” 的设计,既保证了用户态接口的一致性(均通过 fd 操作),又满足了不同协议的特性需求,是 Linux 内核 “抽象复用” 与 “按需扩展” 思想的经典体现。理解这些数据结构的变化,能帮助我们更深入地掌握网络编程的本质,优化程序性能与可靠性。

一、socket核心:内核数据结构的三角关系

        Linux遵循一切皆文件的设计哲学,socket作为特殊的网络文件,其管理依赖三个核心数据结构的协作,这也是理解socket操作的基础。

(1)struct socket:网络协议的抽象接口

        struct socket是Socket的“管理层”结构,负责衔接用户态接口与内核协议逻辑。其中的ops字段指向协议专属的操作集(如TCP的tcp_prot_ops UDP 的udp_prot_ops),sk字段指向协议实现的核心数据结构struct sock。

        这个结构体也是最上层操作的,聚焦于对外(用户)暴露状态等。当调用如bind的时候,会从这个结构体中找到对应的操作集,然后调用其中的操作函数,从而修改struct sock的成员变量。

        struct socket中暴露给用户的状态较为简单,只有“未连接、已连接、正在连接、监听”等通用流程状态,目的是为了让用户知道该套接字处于流程的哪一步。而struct sock则更加复杂详细,在TCP中会有TCP_CLOSED\ESTABLISHED\SYN_SENT等11个状态;在UDP中由于其不是面向连接的协议,所以状态也会较为简单,通常只有“空闲、已绑定”等基础状态。

        至于这里的struct file指针,则是“一切皆文件”的体现,socket本质也是一种文件,所有每一个socket套接字会有一个fd文件描述符,而struct file中的private date也会指向该socket,是一种双向查找的关系。

(2)struct sock:传输层协议的控制中心

        struct sock是传输层协议的 “实干层” 结构,存储了协议运行的关键状态与资源。TCP 因需要可靠传输和连接管理,其 struct sock包含大量特有字段(如连接队列、滑动窗口);而 UDP 作为无连接协议,结构更精简,仅保留基础的地址和缓冲区信息。

        这里可以看到是利用联合体实现的多态。不过公共的字段(如发送缓冲区、接收缓冲区、IP地址、操作集等)。联合体的特点是他本身不知道自己是什么类型,谁填充这个字段,谁就负责管理他,所以这里有一个协议操作集,他们用于操作struct socket本身。体现一个层级架构。

        操作集在用户调用socket系统调用的时候,已经告诉了内核这个套接字应用什么协议SOCK_DGRAM、SOCK_STREAM,所以内核就会根据其填充他的操作集ops。

        如果未来要新增传输协议,比如替代TCP的某一部分,只需要定义自己的struct proto_ops,然后稍稍修改一下内核中socket创建时ops的指向即可。

(3)struct file与files struct:文件系统的接入层

        socket需要通过文件描述符fd被用户操作,这依赖文件系统的核心结构。

        struct file是 Socket 接入文件系统的 “适配器”,通过private_data关联struct socket;filese_struct则管理进程的所有fd,确保每个 Socket 对应唯一的fd索引。

        而在struct file中我们之前说过有个成员是f_ops,他是这个文件的操作集。不同类型的文件操作集指向的函数是不一样的。

二、Socket调用全流程

(1)socket():创建核心结构,奠定基础协议

        用户态调用

int fd = socket(AF_INET, SOCK_STREAM, 0); // 创建 TCP 套接字
// 或
int fd = socket(AF_INET, SOCK_DGRAM, 0);  // 创建 UDP 套接字

内核操作与结构变化

核心差异:TCP 的 struct sock 会预分配连接管理所需的内存(如 request_sock_queue),而 UDP 直接初始化缓冲区队列,结构更轻量。

(2)bind():绑定地址,确定网络身份

   用户态调用

struct sockaddr_in addr = {.sin_family = AF_INET,.sin_port = htons(8080),.sin_addr.s_addr = INADDR_ANY
};
bind(fd, (struct sockaddr*)&addr, sizeof(addr));

        这里有sockaddr_in和sockaddr两个结构体,他们体现了C语言版本的继承。无论是父类还是子类都是相同的大小,但是第一位成员永远是协议族,当访问到第一个成员之后,就可以按照该协议进行解析这个sockaddr类,从而获取有效信息。这也是必须要填入协议族的原因。

在sockaddr中是由2位协议类型+14位空白字节组成的。可以看做基类

而sockaddr_in则是对他进行重写,前两位仍然表示协议族,后面14个字节则划分成了端口号、IPV4地址、和8位空白字段。可以看做是子类。

除了他们,还有许多的“子类”。如sockaddr_in6、sockaddr_un、sockaddr_nl等,他们分别用于不同的场景。

内核操作与结构变化

核心差异:绑定操作对 TCP 和 UDP 逻辑相同,均是填充地址信息,无协议特有逻辑。

(3)监听与连接:TCP 特有流程,依赖队列管理

1.listen():初始化连接队列,进入监听状态

用户态调用

listen(fd, 10); // 最大已完成连接队列长度为 10

内核操作与结构变化

​​​​​​​

核心差异:UDP 无 listen() 操作,其 struct sock 无连接队列字段,无需初始化。

2.connect():客户端发起连接,触发三次握手

用户态调用(客户端):

connect(fd, (struct sockaddr*)&server_addr, sizeof(server_addr));

内核操作与结构变化

​​​​​​​​​​​​​​

核心差异:UDP 的 connect() 仅在 struct sock 中记录远程地址(sk_daddr),无三次握手,不改变状态(仍为无连接)。

3.accept():提取已完成连接,创建新套接字

用户态调用(服务器):

int client_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &len);

内核操作与结构变化

核心差异:UDP 无 accept() 操作,数据传输直接使用绑定的套接字,无需创建新套接字。

(4)数据传输:read()/write() 与缓冲区管理

TCP 和 UDP 的数据传输均依赖struct sock中的数据发送 / 接收队列(sk_write_queue/sk_receive_queue),但因协议特性,管理逻辑差异显著,且实现细节也大不相同。

1.TCP 数据传输:可靠传输的缓冲区协作

2.UDP 数据传输:简单无状态的队列管理

(5)close():释放资源,终止连接

用户态调用

close(fd);

内核操作与结构变化

http://www.lryc.cn/news/619776.html

相关文章:

  • 9 ABP Framework 中的 MVC 和 Razor Pages
  • SpringMVC 6+源码分析(六)参数处理
  • 基于R语言的现代贝叶斯统计学方法(贝叶斯参数估计、贝叶斯回归、贝叶斯计算实践过程
  • Datawhale AI夏令营第三期多模态RAG方向 Task3
  • 算法详细讲解 - 离散化/区间合并
  • 【慕伏白】Kali 系统下安装 docker
  • 弹性扩展新范式:分布式LLM计算的FastMCP解决方案
  • Python(二):MacBook安装 Python并运行第一个 Python 程序
  • 【QT】QT实现鼠标左右滑动切换图片
  • MySQL中的缓存机制
  • 如何在VS里使用MySQL提供的mysql Connector/C++的debug版本
  • 如何把ubuntu 22.04下安装的mysql 8 的 数据目录迁移到另一个磁盘目录
  • 设计模式笔记_行为型_策略模式
  • OpenJDK 17 源码 安全点轮询的信号处理流程
  • 资源查看-lspci命令
  • 如何准备一场技术演讲
  • 各种排序算法(二)
  • 磁悬浮轴承转子设计避坑指南:深度解析核心要点与高可靠性策略
  • 基于js和html的点名应用
  • 【电气】NPN与PNP
  • B系列树详细讲解
  • 16-docker的容器监控方案-prometheus实战篇
  • Python 类元编程(导入时和运行时比较)
  • Windows也能用!Claude Code硬核指南
  • [激光原理与应用-259]:理论 - 几何光学 - 平面镜的反射、平面透镜的折射、平面镜的反射成像、平面透镜的成像的规律
  • 网刻软件iVentoy软件使用方法
  • @进程管理工具 - Glances工具详细指南
  • Django REST Framework视图
  • Java 大视界 -- Java 大数据机器学习模型在金融资产配置优化与风险收益平衡中的应用(395)
  • 解惑rust中的 Send/Sync(译)