深入Linux网络子系统01:socket() 到底做了什么?

Linux——网络通信 包括多种机制,如管道、消息队列、信号、共享内存和信号量。这些机制都依赖于Linux内核提供的支持,用于实现不同进程之间的数据交换和同步。然而,这些通信方式在本地进程间通信中非常有用,但无法直接用于跨机器间的通信。 阅读详情


前言

  学习Linux网络编程,接触的第一个函数是socket(),在过往使用中,并没有真正搞懂操作系统做了什么。本文旨在记录学习Linux网络子系统源码的过程与思考,不死扣代码细节,仅聚焦于核心逻辑和数据流,希望能和同样对Linux网络感兴趣的朋友一起探讨学习。


一、一行代码,看懂入口

如无特殊说明,代码以TCP为例

int fd = socket(int domain, int type, int protocol);

socket 的类型由三个参数决定:

  • domain:协议族,决定套接字的寻址方式
    • AF_INET,使用IPv4协议
    • AF_INET6:使用IPv6协议
  • type:套接字类型,决定数据传输方式
    • SOCK_STREAM,提供有序、可靠、双向、基于连接的字节流,如TCP
    • SOCK_DGRAM,支持数据报(无连接、不可靠的、具有固定最大长度的消息),如UDP
  • protocol:具体传输协议,通常填0让系统选择
    • SOCK_STREAM:默认使用TCP
    • SOCK_DGRAM:默认使用UDP

返回值:

  • 成功:返回一个非负整数的文件描述符
  • 失败:-1

二、重点:调用 socket() 时,内核默默做了什么事情

1.调用框图

socket()

sock_create()

__sock_create()

sock_alloc() 分配 socket

pf->create() → inet_create()

sk_alloc() 分配 sock

sock_init_data() 初始化 sock,关联 socket 与 sock

sk->sk_prot->init() → tcp_v4_init_sock() 初始化 TCP

sock_map_fd()

get_unused_fd_flags() 获取空闲 fd

sock_alloc_file() 分配 file,关联 socket 与 file

返回文件描述符 fd

2.关键结构体

2.1 操作集

// IPv4协议族
static const struct net_proto_family inet_family_ops = {
	.family = PF_INET,
	.create = inet_create,
	.owner	= THIS_MODULE,
};

// 该数组定义了 IPv4 协议族下 TCP、UDP、ICMP 和 RAW 四种套接字类型的协议实现(prot)与操作接口(ops)
static struct inet_protosw inetsw_array[] =
{
	{
		.type =       SOCK_STREAM,
		.protocol =   IPPROTO_TCP,
		// 定义了协议本身的具体实现,负责具体协议算法的实现
		.prot =       &tcp_prot,
		// 定义了套接字层的操作接口,负责将用户空间的请求转化为内核协议栈的调用
		.ops =        &inet_stream_ops,
		.flags =      INET_PROTOSW_PERMANENT | INET_PROTOSW_ICSK,
	},

	{
		.type =       SOCK_DGRAM,
		.protocol =   IPPROTO_UDP,
		.prot =       &udp_prot,
		.ops =        &inet_dgram_ops,
		.flags =      INET_PROTOSW_PERMANENT,
	},

	{
		.type =       SOCK_DGRAM,
		.protocol =   IPPROTO_ICMP,
		.prot =       &ping_prot,
		.ops =        &inet_sockraw_ops,
		.flags =      INET_PROTOSW_REUSE,
	},

	{
		.type =       SOCK_RAW,
		.protocol =   IPPROTO_IP,
		.prot =       &raw_prot,
		.ops =        &inet_sockraw_ops,
		.flags =      INET_PROTOSW_REUSE,
	}
};
// IPv4 地址族专有操作
const struct inet_connection_sock_af_ops ipv4_specific = {
	.queue_xmit	   = ip_queue_xmit,
	.send_check	   = tcp_v4_send_check,
	.rebuild_header	   = inet_sk_rebuild_header,
	.sk_rx_dst_set	   = inet_sk_rx_dst_set,
	.conn_request	   = tcp_v4_conn_request,
	.syn_recv_sock	   = tcp_v4_syn_recv_sock,
	.net_header_len	   = sizeof(struct iphdr),
	.setsockopt	   = ip_setsockopt,
	.getsockopt	   = ip_getsockopt,
	.addr2sockaddr	   = inet_csk_addr2sockaddr,
	.sockaddr_len	   = sizeof(struct sockaddr_in),
	.bind_conflict	   = inet_csk_bind_conflict,
	.mtu_reduced	   = tcp_v4_mtu_reduced,
};

2.2 socket 相关结构体

struct socket
struct sock
struct inet_sock
struct inet_connection_sock
struct tcp_sock

在 Linux 内核网络协议栈中,struct socket、struct sock、struct inet_sock、struct inet_connection_sock和 struct tcp_sock是层层嵌套、逐步特化的数据结构,共同构成了从通用套接字接口到具体 TCP 连接控制块的完整抽象链。整体继承关系如下:

struct socket
(面向用户态)

struct sock
(面向内核协议栈)

struct inet_sock
(AF_INET 家族扩展, IPv4 特有字段)

struct inet_connection_sock
(面向连接的传输层公共部分, 如 TCP)

struct tcp_sock
(TCP 协议全部状态与控制块)

3.调用过程

socket的相关函数都在net\socket.c中,socket()方法主要调用了两个函数,如下:

SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)
{
	// 创建 socket 对象
	retval = sock_create(family, type, protocol, &sock);
	// 将创建好的 struct socket 对象与一个文件描述符绑定,并返回该文件描述符给用户空间
	retval = sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK));
}

3.1 sock_create

该函数调用 __sock_create() ,其关键代码如下:

int __sock_create(struct net *net, int family, int type, int protocol, struct socket **res, int kern)
{
	// 分配一个 struct socket 对象
	struct socket *sock;
	sock = sock_alloc();
	// 协议族 net_proto_family 通过 net/ipv4/af_inet.c 中的 sock_register(&inet_family_ops) 注册进来
	pf = rcu_dereference(net_families[family]);
	// AF_INET 对应的是 int inet_create(...)
	err = pf->create(net, sock, protocol, kern);
}

inet_create主要做的工作包括:

static int inet_create(struct net *net, struct socket *sock, int protocol, int kern)
{
	struct sock *sk;
	struct inet_sock *inet;
	// 设置监听 socket 状态为 未连接状态
	sock->state = SS_UNCONNECTED;
	list_for_each_entry_rcu(answer, &inetsw[sock->type], list)
	{
		// inet_stream_ops 操作集赋值到 socket 上
		sock->ops = answer->ops;
		// 获取 tcp_prot
		answer_prot = answer->prot;
		// 分配一个 struct sock 对象,并将其与指定的协议族(IPv4)、协议实现(tcp_prot)关联起来
		sk = sk_alloc(net, PF_INET, GFP_KERNEL, answer_prot, kern);
		// 初始化 sock 对象,并将 socket 和 sock 关联起来
		sock_init_data(sock, sk);
		// 初始化TCP套接字,实际调用 tcp_prot 中的 tcp_v4_init_sock 方法
		err = sk->sk_prot->init(sk);
	}
}

sock_init_data主要工作:

void sock_init_data(struct socket *sock, struct sock *sk)
{
	// 接收队列	已经到达内核、等待用户 recvmsg()取走的 skb,初始化为空链表
	skb_queue_head_init(&sk->sk_receive_queue);
	// 发送队列	已发送但尚未得到 ACK或待发送的 skb,初始化为空链表
	skb_queue_head_init(&sk->sk_write_queue);
	// 设置监听 sock 状态为 关闭状态
	sk->sk_state		=	TCP_CLOSE;
	// 建立双向指针:sock->sk = sk,sk->sk_socket = sock
	sk_set_socket(sk, sock);
	sock->sk	=	sk;
	// 设置默认回调函数
	sk->sk_state_change	=	sock_def_wakeup;
	sk->sk_data_ready	=	sock_def_readable;
	sk->sk_write_space	=	sock_def_write_space;
}

tcp_v4_init_sock的主要工作:

static int tcp_v4_init_sock(struct sock *sk)
{
	// 面向连接的 socket 扩展结构体
	struct inet_connection_sock *icsk = inet_csk(sk);
	// 在 sock_init_data的基础上进一步设置 TCP 特有的字段,使 socket 具备 TCP 协议运行所需的状态和参数
	tcp_init_sock(sk);
	// 绑定 IPv4 特定的网络层操作
	icsk->icsk_af_ops = &ipv4_specific;
}

tcp_init_sock的主要工作:

void tcp_init_sock(struct sock *sk)
{
	struct inet_connection_sock *icsk = inet_csk(sk);
	struct tcp_sock *tp = tcp_sk(sk);
	// 初始化乱序队列
	tp->out_of_order_queue = RB_ROOT;
	// 初始化TCP定时器(重传定时器、延迟 ACK 定时器、keepalive 定时器)
	tcp_init_xmit_timers(sk);
	// 初始化 prequeue
	tcp_prequeue_init(tp);
	// 设置 TCP 缓冲区大小
	sk->sk_sndbuf = sysctl_tcp_wmem[1];
	sk->sk_rcvbuf = sysctl_tcp_rmem[1];
}

至此,一个 SOCK_STREAM 内核对象已经创建好了。

3.2 sock_map_fd

该函数关键代码如下:

static int sock_map_fd(struct socket *sock, int flags)
{
	struct file *newfile;
	// 在当前进程的文件描述符表中找到一个空闲的 slot,并返回其索引
	int fd = get_unused_fd_flags(flags);
	// sock_alloc_file分配一个新的 struct file实例,并建立与 socket 的双向关联:
	// 1. 将 sock->file指向新文件
	// 2. 将 file->private_data指向 sock
	newfile = sock_alloc_file(sock, flags, NULL);
}

三、总结

对socket()方法主要做了以下几件事情:

  1. 分配内核 socket 结构:面向用户
  2. 设置监听 socket 状态为 SS_UNCONNECTED
  3. 创建并初始化协议对应的 sock:面向网络协议栈
  4. 设置监听 sock 状态为 TCP_CLOSE
  5. 初始化接收队列、发送队列、乱序队列
  6. 初始化缓冲区大小
  7. 绑定文件描述符
  8. 返回文件描述符

四、后续预告

本篇是网络系统调用开篇,下一篇我们继续拆解:
bind() 到底干了什么?为什么客户端通常不需要 bind?内核端口绑定原理?

五、写在最后

以往学习过程中经常只输入不输出,这次坚持记录每一个关键知识点、每一次踩坑与复盘,不追求速成,坚持稳步积累,慢慢搭建属于自己的网络、运维、性能调优技术体系,能将实际遇到的问题转换成机器语言。

十四.linux网络编程之Socket实践 想仔细参考的请参考文档:十四.linux网络编程之Socket实践.note 链接:http://note.youdao.com/noteshare?id=e3ac0516654da4cae514d0f3a908832f&sub=D7243B3B936D451DAC67D01C62AB8512 接上节,socket编程接口API函数具体参考上节,已经详细梳理过 五.soekct实践编程... 阅读详情

相关推荐

linux raw socket

raw socket即原始套接字,可以通过创建socket时,设置参数SOCK_RAW来创建。相比平时使用SOCK_STREAM创建的应用层的套接字,raw socket可以直接处理ip首部和tcp首部,并且可以监听抓取经过本机的ip数据包和mac帧,可以处理ICMP和IGMP等网络控制报文。通常有两种方式: //发送接收ip数据包 socket(PF_INET,SOCK_RAW,IPPROTO_T

cplusplus 3083

《Linux系统编程篇》Linux Socket 网络编程01 API介绍(Linux 进程间通信(IPC))——基础篇

道可道,非常道;名可名,非常名。无名,天地之始;有名,万物之母。故常无欲,以观其象;常有欲,以观其体。两者同出而异名,同为之玄。玄之又玄,众妙之门。——老子《道德经》

Strange_Head的博客 1538

Linux网络协议之socket(套接字)通信基础

很多网络编程函数诞生早于 IPv4 协议,那时候都使用的是 struct sockaddr 结构体,为了向前兼容,现在sockaddr 退化成了(void *)的作用,传递一个地址给函数,至于这个函数是 sockaddr_in 还是 sockaddr_in6,由地址族确定,然后函数内部再强制类型转化为所需的地址类型。在各种计算机体系结构中,对于字节、字等的存储机制有所不同,因而引发了计算机通信领域中一个很重要的问题,即通信双方交流的信息单元(比特、字节、字、双字等等)应该以什么样的顺序进行传送。

m0_74282605的博客 1584

【探索Linux】P.26(网络编程套接字基本概念—— socket编程接口 | socket编程接口相关函数详细介绍 )

这篇文章详细介绍了常见的 Socket API,包括各种函数的用途和功能。首先列出了这些常见函数,并逐一进行了详细介绍。其中包括了socket()用于创建套接字、bind()用于绑定地址、listen()用于监听连接等基本函数,以及sendto()、recvfrom()等用于数据传输的函数。此外,还介绍了一些与套接字操作相关的函数,如setsockopt()、getsockopt()以及地址转换函数inet_pton()等。通过该文章,读者可以全面了解这些常见 Socket 函数的作用和用法。

Yawesh的博客 2899

从学生项目到工程实践:手把手教你用ATE测试电源芯片的Rdson与漏电流

本文详细介绍了如何将学术级的电源芯片测试方案转化为工业级解决方案,重点讲解使用ATE设备测试Rdson(导通电阻)和漏电流的关键技术。从设备选型、测试夹具设计到量产环境下的工程化实现,提供了实用的优化技巧和常见问题解决方案,帮助工程师提升测试效率和精度。

weixin_42525289的博客 182

从CCS3.3到CCS10:工程迁移与开发环境升级全攻略

本文详细介绍了从CCS3.3迁移到CCS10的完整流程与实战指南。针对老旧DSP项目,阐述了升级的必要性,并逐步指导CCS10的安装配置、界面操作、核心的工程移植步骤,以及移植后常见的代码调整与问题解决方案,帮助开发者高效完成开发环境升级,充分利用新版本的工具链提升开发效率。

weixin_29304985的博客 379

Linux网络编程——socket 通信基础

socket 本身有“ 插座 ”的意思,在 Linux 环境下,用于表示 进程间网络通信 的 特殊文件类型。本质为 内核 借助 缓冲区 形成的 伪文件。既然是文件,那么理所当然的,我们可以使用 文件描述符 引用套接字。与管道类似的,Linux 系统将其封装成文件的目的是为了 统一接口,使得 读写套接字 和 读写文件 的操作一致。区别是 管道 主要应用于 本地进程间通信,而 套接字 多应用于 网络进程间数据的传递。

weixin_43412762的博客 3473

全栈开发指南:从前端到后端的全面掌握

全栈开发人员(Full Stack Developer)是指能够处理前端和后端开发工作的开发人员。他们需要掌握各种技术和工具,能够独立完成从需求分析、设计、开发到部署的整个流程。这篇博客将详细介绍全栈开发涉及的各个方面,为您提供从入门到进阶的指导。

Where there is a will, there is a way ! 3453

【Linux网络编程】Socket-TCP

接下来介绍一个新函数popen。

Quietcoder的博客 1083

Ubuntu22.04上如何用Docker一键部署Ragflow+Ollama(附CUDA配置避坑指南)

本文详细介绍了在Ubuntu22.04系统上使用Docker一键部署Ragflow+Ollama的完整流程,特别提供了CUDA配置的避坑指南。从硬件兼容性检查、驱动安装到容器网络配置,手把手教你搭建本地化大模型环境,解决CUDA驱动冲突等常见问题,助力开发者快速构建AI应用开发平台。

weixin_26875109的博客 285

别再到处找模型了!手把手教你将TI、ADI官网的SPICE模型导入LTspice(附路径避坑指南)

本文详细介绍了如何将TI、ADI官网的SPICE模型导入LTspice的全流程,包括模型获取、路径配置、符号生成等关键步骤,并提供了路径避坑指南。通过实战案例和技巧分享,帮助电子工程师高效利用外部元器件模型进行电路仿真,提升设计效率。

weixin_42535703的博客 408

【Linux网络编程】socket基础

🔥个人主页Quitecoder🔥linux笔记仓。

Quietcoder的博客 1019

mac(apple silicon)arm64安装spm12详细教程

3.下载matlab runtime R2023b for macOS (macA64),注意必须下载这个,只下载matlab是不可以的。默认安装目录是/Applications/MATLAB/MATLAB_Runtime/R2023b。注意最后的命令,路径根据自己的matlab_runtime安装位置进行调整,然后就可以安装成功了。5. 解压spm12.zip,解压后将文件夹拖到应用程序下(Applications)2.下载spm12,这个版本可兼容mac芯片!6.打开mac终端,输入。

weixin_62332001的博客 2025

linux下socket 入门心得

        最近学习linux网络编程,linux下网络编程是基于socket。socket在百度百科上的解释很形象——“网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket。建立网络通信连接至少要一对端口号(socket)。socket本质是编程接口(API),对TCP/IP的封装,TCP/IP也要提供可供程序员做网络开发所用的接口,这就是Socket编程...

qq_30360671的博客 1834

AI自动化流程在金融风控中的3个典型案例

AI自动化风控流程全流程自动化:最小化人工干预,实现从数据输入到决策输出的闭环自适应学习:通过持续数据反馈实现模型自动迭代优化实时决策能力:满足毫秒级(支付风控)至分钟级(信贷审批)的响应需求多模态数据融合:整合结构化、非结构化和实时流数据智能决策解释:自动生成符合监管要求的决策解释人机协同机制:在高风险场景下实现AI与人类专家的协同决策实施后关键绩效指标(KPI)变化指标实施前实施后改进幅度平均审批时间3.2天4.7分钟-99.7%自动审批率15%85%+467%

架构师的AI之路,分享AI应用开发架构的学习与实践。 1537

美军利用MANET技术实现无人作战单元协同作战

本文首先介绍了移动自组织网络 (MANET)以及Wave Relay®波形中继技术,并探讨了波形中继MANET平台在美空军ABMS项目等军事方面的运用情况。

guanyifu9的博客 855

Linux Kernel 网络编程之网络子系统发展历程深度树形分析

Linux网络子系统经历了五个关键发展阶段:1) 1991-1994年基础期,实现SLIP/PPP协议和基础驱动;2) 1995-1999年框架形成期,引入BSD socket接口和完善TCP/IP协议栈;3) 2000-2009年互联网爆发期,推出Netfilter/iptables和NAPI优化;4) 2010-2019年现代网络期,实现eBPF革命、多队列网卡和虚拟化支持;5) 2020年至今的云原生期,发展XDP高速处理和BPF拥塞控制。核心技术演进包括从简单sk_buff到现代240字节优化结构,

zhilin_tang的专栏 730

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

socket编程(2024超级详细、Linux)

本文是作者在学习linux网络编程的时候所写的学习笔记,同时方便巩固自己学习的知识和网友们一起分享,尚若发现有什么不对的地方,欢迎指正。

从无到有,守正出奇。 1756

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

linux -网络编程01一网络基本概念和Socket编程

函数描述: 创建socket参数说明:domain: 协议版本AF_UNIX AF_LOCAL本地套接字使用type:协议类型SOCK_STREAM 流式, 默认使用的协议是TCP协议SOCK_DGRAM 报式, 默认使用的是UDP协议protocal:一般填0, 表示使用对应类型的默认协议.返回值:成功: 返回一个大于0的文件描述符失败: 返回-1, 并设置errno当调用socket函数以后, 返回一个文件描述符, 内核会提供与该文件描述符相对应的读和写缓冲区,

cat_fish_rain的博客 539

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

【Linux】网络基础探索:开启你的网络之旅

详细讲解了网络基础概念,带领读者快速入门了解网络通信原理

You build it, you run it 5174

Linux 网络编程基础:从 TCP/UDP 到 Socket 编程

地址转换工具inet_aton(字符串 IP→网络字节序)和inet_ntoa(网络字节序→字符串 IP)简化 IP 处理。协议选择:需可靠性选 TCP,需实时性选 UDP;UDP 需自行处理丢包和重传。错误处理:所有 Socket 函数需检查返回值,处理errno错误码(如EAGAIN表示资源暂时不可用)。Linux 网络编程的核心是理解 TCP/UDP 的差异、合理使用端口号、处理字节序转换,并掌握 Socket 编程的基本流程。

HJACNP的博客 1947

Linux——socket网络通信

Socket套接字由远景研究规划局(Advanced Research Projects Agency, ARPA)资助加里福尼亚大学伯克利分校的一个研究组研发。其目的是将TCP/IP协议相关软件移植到UNIX类系统中。设计者开发了一个接口,以便应用程序能简单地调用该接口通信。这个接口不断完善,最终形成了Socket套接字。Linux系统采用了Socket套接字,因此,Socket接口就被广泛使用,到现在已经成为事实上的标准。与套接字相关的函数被包含在头文件中。socket。

感觉画质不如...原神的博客 4193

网络通信Socket(Linux)

网络通信之套接字Socket 预备知识根据自身基础选择性查看 文章目录网络通信之套接字Socket预备知识:IP 及 端口网络模型字节序IP地址转换虚拟地址空间文件描述符sockaddr结构体 / sockaddr_insocket 编程TCP通信流程socket常用函数详解服务器端代码实现客户端代码实现 预备知识: IP 及 端口 IP: 本质为一个整型数,表示计算机在网络中的地址,IP协议有两个:IPv4 和 IPv6 ​ IPv4协议:(目前用的最多)32位整型数表示,4个字节 ,也可使用点分十进..

weixin_49730048的博客 4023

(网络编程笔记):SOCKET编程

SOCKET编程 传统的进程间通信借助内核提供的IPC机制进行,但是只能限于本机通信,若要跨机通信,,就必须使用网络通信。( 本质上借助内核-内核提供了socket伪文件的机制实现通信----实际上是使用文件描述符),这就需要用到内核提供给用户的socket API函数库 既然提到socket伪文件,所以可以使用文件描述符相关的函数read write 使用socket会建立一个socket pair 如下图,一个文件描述符操作两个缓冲区, 这点跟管道是不同的, 管道是两个文件描述符操作一个内...

baidu_41388533的博客 414

【C++】网络通信-Socket

C++网络通信Socket套接字

IdealSanX的博客 3380

Linux网络编程(2024超级详细、Linux)

本文是作者在学习linux网络编程的时候所写的学习笔记,同时方便巩固自己学习的知识和网友们一起分享,尚若发现有什么不对的地方,欢迎指正。

从无到有,守正出奇。 6127
下一篇: 深入Linux网络子系统02:bind()到底做了什么?
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值