从内核数据结构的角度理解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);

内核操作与结构变化

相关推荐
Mr_Xuhhh14 分钟前
传输层协议 TCP(1)
运维·服务器·网络·c++·网络协议·tcp/ip·https
星哥说事1 小时前
如何将堡塔云WAF备份、迁移到新的服务器
linux
楽码1 小时前
端到端应用Hmac加密
服务器·后端·算法
ycchenG71 小时前
缓存元数据损坏操作步骤(lvmcache修复)
linux·缓存
扶风呀2 小时前
具有熔断能力和活性探测的服务负载均衡解决方案
运维·负载均衡
一乐小哥2 小时前
Docker 拉取镜像超时?别再瞎抄配置了!亲测 3 个有效镜像源 + 避坑指南
linux·docker
GDAL2 小时前
Docker pull拉取镜像命令的入门教程
运维·docker·容器
Fanmeang2 小时前
MP-BGP Hub-Spoken实验案例+通信过程(超详细)
运维·网络·华为·mpls·vpn·mpbgp·hubspoke
cpsvps3 小时前
Docker存储卷备份策略于VPS服务器环境的实施标准与恢复测试
服务器·docker·容器