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

目录

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

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

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

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

二、Socket调用全流程

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

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

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

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

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

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

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

[1.TCP 数据传输:可靠传输的缓冲区协作](#1.TCP 数据传输:可靠传输的缓冲区协作)

[2.UDP 数据传输:简单无状态的队列管理](#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():创建核心结构,奠定基础协议

用户态调用

cpp 复制代码
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():绑定地址,确定网络身份

用户态调用

cpp 复制代码
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():初始化连接队列,进入监听状态

用户态调用

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

内核操作与结构变化

​​​​​​​

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

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

用户态调用(客户端):

cpp 复制代码
connect(fd, (struct sockaddr*)&server_addr, sizeof(server_addr));

内核操作与结构变化

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

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

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

用户态调用(服务器):

cpp 复制代码
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():释放资源,终止连接

用户态调用

cpp 复制代码
close(fd);

内核操作与结构变化

相关推荐
A小辣椒18 小时前
TShark:Wireshark CLI 功能
linux
A小辣椒1 天前
TShark:基础知识
linux
AlfredZhao1 天前
OCI 明明分配了 200G 系统盘,为什么 df 只看到 30G?
linux·oci
AlfredZhao2 天前
vi 删除指定范围的行,不用再反复按 dd
linux·vi
用户9718356334662 天前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪2 天前
linux 拷贝文件或目录到指定的位置
linux
大树883 天前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠3 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
霸道流氓气质3 天前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
bush43 天前
嵌入式linux学习记录十四、术语
linux·嵌入式