操作系统:Linux -- 详解socket实现与网络编程接口
现在我们来瞧一瞧 Linux 的网络程序,不过想要入门 Linux 的网络编程,套接字也是一个绕不开的重要知识点,正是有了套接字,Linux 系统才拥有了网络通信的能力。而且网络协议的最底层也是套接字,有了这个基础,你再去看相关的网络协议的时候也会更加轻松。
本节先来了解套接字的作用、工作原理和关键数据结构。
如何理解套接字
根据底层网络机制的差异,计算机网络世界中定义了不同协议族的套接字(socket),比如 DARPA Internet 地址(Internet 套接字)、本地节点的路径名(Unix 套接字)、CCITT X.25 地址(X.25 套接字)等。
重点讲解跟网络子系统和 TCP/IP 协议栈息息相关的一种套接字——Internet 套接字。如果你对其他类型的套接字有兴趣,可以自行阅读这里的资料。
Internet 套接字是 TCP/IP 协议栈中传输层协议的接口,也是传输层以上所有协议的实现
同时,套接字接口在网络程序功能中是内核与应用层之间的接口。TCP/IP 协议栈的所有数据和控制功能都来自于套接字接口,与 OSI 网络分层模型相比,TCP/IP 协议栈本身在传输层以上就不包含任何其他协议。
在 Linux 操作系统中,替代传输层以上协议实体的标准接口,称为套接字,它负责实现传输层以上所有的功能,可以说套接字是 TCP/IP 协议栈对外的窗口。
Linux 套接字 API 适合所有的应用标准,现在的应用层协议也全部移植到了 Linux 系统中。但请你注意,在套接字层下的基础体系结构实现却是 Linux 系统独有的,Linux 内核支持的套接字结构如图所示。
我们创建套接字时,可以通过参数选择协议族,为应用程序指定不同的网络机制。如果指定为 PF_INET 协议族,这里的套接字就叫做 INET 套接字,它的套接字接口函数提供了 TCP/IP 网络服务功能。现在我先带你了解一下套接字的数据结构。
套接字的数据结构
在 Linux 操作系统下,对套接字、套接字的属性、套接字传输的数据格式还有管理套接字连接状态的数据结构分别做了一系列抽象定义。
每个程序使用的套接字都有一个 struct socket 数据结构与 struct sock 数据结构的实例。
Linux 内核在套接字层定义了包含套接字通用属性的数据结构,分别是 struct socket
与 struct sock
,它们独立于具体协议;而具体的协议族与协议实例继承了通用套接字的属性,加入协议相关属性,就形成了管理协议本身套接字的结构。
struct socket 数据结构
truct socket 是套接字结构类型,每个套接字在内核中都对应唯一的 struct socket 结构(用户程序通过唯一的套接字描述符来表示套接字,且描述符与 struct socket 结构一一对应)。
struct socket 数据结构定义如下:
struct socket {
socket_state state; // 套接字的状态
unsigned long flags; // 套接字的设置标志。存放套接字等待缓冲区的状态信息,其值的形式如SOCK_ASYNC_NOSPACE等
struct fasync_struct *fasync_list; // 等待被唤醒的套接字列表,该链表用于异步文件调用
struct file *file; // 套接字所属的文件描述符
struct sock *sk; // 指向存放套接字属性的结构指针
wait_queue_head_t wait; //套接字的等待队列
short type; // 套接字的类型。其取值为SOCK_XXXX形式
const struct proto_ops *ops; // 套接字层的操作函数块
}
struct sock 数据结构
在 Linux 内核的早期版本中,struct sock 数据结构非常复杂。从 Linux2.6 版本以后,从两个方面对该数据结构做了优化。
其一是将 struct sock 数据结构划分成了两个部分。一部分为描述套接字的共有属性,所有协议族的这些属性都一样;另一部分属性定义在了 struct sock_common 数据结构中
其二是为新套接字创建 struct sock 数据结构实例时,会从协议特有的缓冲槽中分配内存,不再从通用缓冲槽中分配内存。
struct sock 数据结构包含了大量的内核管理套接字的信息,内核把最重要的成员存放在 struct sock_common 数据结构中,struct sock_common 数据结构嵌入在 struct sock 结构中,它是 struct sock 数据结构的第一个成员。
struct sock_common
数据结构是套接字在网络中的最小描述,它包含了内核管理套接字最重要信息的集合。而 struct sock 数据结构中包含了套接字的全部信息与特点,有的特性很少用到,甚至根本就没有用到
struct sock_common {
unsigned short skc_family; /*地址族*/
volatile unsigned char skc_state; /*连接状态*/
unsigned char skc_reuse; /*SO_REUSEADDR设置*/
int skc_bound_dev_if;
struct hlist_node skc_node;
struct hlist_node skc_bind_node; /*哈希表相关*/
atomic_t skc_refcnt; /*引用计数*/
};
系统中 struct sock 数据结构组织在特定协议的哈希链表中,skc_node 是连接哈希链表中成员的哈希节点,skc_hash 是引用的哈希值。接收和发送数据放在数据 struct sock 数据结构的两个等待队列中:sk_receive_queue 和 sk_write_queue。这两个队列中包含的都是 Socket Buffer
内核使用 struct sock 数据结构实例中的回调函数,获取套接字上某些事件发生的消息或套接字状态发生变化。其中,使用最频繁的回调函数是 sk_data_ready
,用户进程等待数据到达时,就会调用该回调函数。
套接字与文件
套接字的连接建立起来后,用户进程就可以使用常规文件操作访问套接字了。
这种方式在内核中如何实现,这要取决于 Linux 虚拟文件系统层(VFS)的实现。
在 VFS 中,每个文件都有一个 VFS inode 结构,每个套接字都分配了一个该类型的 inode,套接字中的 inode 指针连接管理常规文件的其他结构。操作文件的函数存放在一个独立的指针表中,代码如下。
struct inode{
struct file_operation *i_fop // 指向默认文件操作函数块
}
套接字的文件描述符的文件访问的重定向,对网络协议栈各层是透明的。而 inode 和 socket 的链接是通过直接分配一个辅助数据结构来实现的,这个数据结构的代码如下。
struct socket_slloc {
struct socket socket;
struct inode vfs_inode;
}
套接字缓存
前面我们提到了一个 Socket Buffer,也就是套接字缓存,它代表了一个要发送或者处理的报文。在 Linux 网络子系统中,Socket Buffer 是一个关键的数据结构,因为它贯穿于整个 TCP/IP 协议栈的各层。Linux 内核对网络数据打包处理的全过程中,始终伴随着这个 Socket Buffer。
可以这样理解,Socket Buffer 就是网络数据包在内核中的对象实例。
Socket Buffer 主要由两部分组成。
1、数据包:存放了在网络中实际流通的数据。
2、管理数据结构(struct sk_buff):当在内核中对数据包进行时,内核还需要一些其他的数据来管理数据包和操作数据包,例如协议之间的交换信息,数据的状态,时间等
Socket Buffer 有什么作用呢?
struct sk_buff
数据结构中存放了套接字接收 / 发送的数据。
在发送数据时,在套接字层创建了 Socket Buffer 缓冲区与管理数据结构,存放来自应用程序的数据。
在接收数据包时,Socket Buffer 则在网络设备的驱动程序中创建,存放来自网络的数据。
在发送和接受数据的过程中,各层协议的头信息会不断从数据包中插入和去掉,sk_buff 结构中描述协议头信息的地址指针也会被不断地赋值和复位。
套接字的初始化
Linux 的网络体系结构可以支持多个协议栈和网络地址类型。内核支持的每一个协议栈都会在套接字层注册一个地址族。这就解释了为什么在套接字层可以有一个通用的 API,供完全不同的协议栈使用。
Linux 内核支持的地址族非常多,TCP/IP 协议栈在套接字层注册的地址族是 AF_INET,AF_INET 地址族是在内核启动时注册到内核中的。TCP/IP 协议栈与 AF_INET 地址族相连的处理函数,既可以在套接字初始化时与 AF_INET 地址连接起来,也可以在套接字中动态地注册新的协议栈。
套接字层的初始化要为以后各协议初始化 struct sock 数据结构对象、套接字缓冲区 Socket Buffer 对象等做好准备,预留内存空间。
套接字层初始化要完成的基本任务包括后面这三项。
- 初始化套接字的缓存槽
- 为 Socket Buffer 创建内存缓存槽
- 创建虚拟文件系统
初始化函数代码如下所示。
static int __init sock_init(void) {
int err;
/*
* 初始化.sock缓存
*/
sk_init();
/*
* 初始化sk_buff缓存
skb_init();
/* 初始化协议模块缓存
init_inodecache();
/* 注册文件系统类型 */
err = register_filesystem(&sock_fs_type);
if (err) goto out_fs;
sock_mnt = kern_mount(&sock_fs_type);
if (IS_ERR(sock_mnt)) {
err = PTR_ERR(sock_mnt);
goto out_mount;
}
}
地址族的值和协议交换表
套接字是一个通用接口,它可以与多个协议族建立接口,每个协议族中又可以实现多个协议实例。
TCP/IP 协议栈处理完输入数据包后,将数据包交给套接字层,放在套接字的接收缓冲区队列(sk_rcv_queue)。然后数据包从套接字层离开内核,送给应用层等待数据包的用户程序。用户程序向外发送的数据包缓存在套接字的传送缓冲区队列(sk_write_queue),从套接字层进入内核地址空间。
在同一个主机中,可以同时在多个协议上打开多个套接字,来接收和发送网络数据,套接字层必须确定哪个套接字是当前数据包的目标套接字。
怎么精准确定呢?
在 Linux 内核里有一个叫做 struct inet_protosw 的数据结构,它就负责完成这个功能,具体来看就是管理和描述 struct proto_ops 和 struct proto 之间的对应关系。这里 struct proto_ops 就是系统调用套接字的操作函数块,而 struct proto 就是跟内核协议相关的套接字操作函数块。
inet_protosw代码:
struct inet_protosw {
struct list_head list;
unsigned short type; /* AF_INET协议族套接字的类型,如TCP为SOCK_STREAM*/
unsigned short protocol; /* 协议族中某个协议实例的编号。如TCP协议的编码为IPPROTO_TCP */
struct proto *prot;
const struct proto_ops *ops;
unsigned char flags; /* 该套接字属性的相关标志 */
}
结合上面代码我们发现,内核使用 struct inet_protosw 数据结构实现的协议交换表,将应用程序通过 socketcall 系统调用指定的套接字操作,转换成对某个协议实例实现的套接字操作函数的调用。
struct inet_protosw 类型把 INET 套接字的协议族操作集与传输层协议操作集关联起来。该类型的 inetsw_array 数组变量实现了 INET 套接字的协议族操作集与具体的传输层协议关联。由 struct inet_protosw 数据结构类型数组 inetsw_array[]构成的向量表,称为协议交换表,协议交换表满足了套接字支持多协议栈这项功能。
小结
套接字是 UNIX 兼容系统的一大特色,是 UNIX 一切皆是文件操作概念的具体实现,从实现的角度来看,套接字是通信的抽象描述;从内核角度看,同时也是一个管理通信过程的对象——struct socket 结构。
Linux 的网络体系结构可以支持多个协议栈和网络地址类型,通过地址族的值和协议交换表,Linux 的套接字实现了支持多协议栈这项功能。
本节最关键的两个点:
1、从描述 Linux 套接字接口的数据结构、套接字接口初始化过程可知,Linux 套接字体系结构独立于具体网络协议栈的套接字,可以同时支持多个网络协议栈的工作。
2、套接字内核实现,我们具体分析了套接字从创建的过程。根据分析我们可以发现,任何协议栈都可以在套接字通用体系结构的基础上,派生出具有协议族特点的套接字接口。
操作系统:Linux -- 详解socket实现与网络编程接口的更多相关文章
- Java网络编程和NIO详解开篇:Java网络编程基础
Java网络编程和NIO详解开篇:Java网络编程基础 计算机网络编程基础 转自:https://mp.weixin.qq.com/s/XXMz5uAFSsPdg38bth2jAA 我们是幸运的,因为 ...
- Tomcat原理系列之六:详解socket如何封装成request(上)
目录 参与者 总结 @(详解socket如何封装成request) 看源码虽然不能马上提升你的编码水平.但能让你更好的理解编程. 因为我们tomcat多是以NIO形式处理请求,所以本系列讲的都是NIO ...
- DELL R720服务器安装Windows Server 2008 R2 操作系统图文详解
DELL R720服务器安装Windows Server 2008 R2 操作系统图文详解 说明:此文章中部分图片为网络搜集,所以不一定为DELL R720服务器安装界面,但可保证界面内容接近DELL ...
- Android热门网络框架Volley详解[申明:来源于网络]
Android热门网络框架Volley详解[申明:来源于网络] 地址:http://www.cnblogs.com/caobotao/p/5071658.html
- struts2常用标签详解(申明:来源于网络)
struts2常用标签详解(申明:来源于网络) 地址:http://blessht.iteye.com/blog/1184960
- linux 详解useradd 命令基本用法
linux 详解useradd 命令基本用法 时间:2019-03-24 本文章向大家介绍linux 详解useradd 命令基本用法,主要包括linux 详解useradd 命令基本用法使用实例.应 ...
- 如何使用socket进行java网络编程(二)
通过在如何使用socket进行java网络编程(一)中程序的编写,可以总结出一些常用的java socket编程的范例来. ServerSocket server = new ServerSocket ...
- ulimit 命令详解 socket查看linux最大文件打开数
ulimit 命令详解 Linux对于每个用户,系统限制其最大进程数.为提高性能,可以根据设备资源情况,设置各linux 用户的最大进程数 可以用ulimit -a 来显示当前的各种用户进程限 ...
- linux详解sudoers
sudo使用 Linux是多用户多任务的操作系统, 共享该系统的用户往往不只一个.出于安全性考虑, 有必要通过useradd创建一些非root用户, 只让它们拥有不完全的权限; 如有必要,再来提升权限 ...
- Unix操作系统监控详解(一)
一.描述 监控在检查系统问题运行状况以及优化系统性能工作上是一个不可缺少的部分.通过操作系统监控工具监视操作系统资源的使用情况,间接地反映了各服务器程序的运行情况.根据运行结果分析可以帮助我们快速定位 ...
随机推荐
- luogu-P3343题解
简要题意 给定一张 \(n\) 个点 \(m\) 条边的图,边的边权是 \([0, 1]\) 之间均匀分布的随机实数,且相互独立.求最小生成树的最大边权的期望值. 思路 首先有一个比较神秘的跟概率有关 ...
- ARC165F题解
前言 \(2024.10.19\) 日校测 \(T4\),思维太庙,被薄纱了,遂哭弱,写题解以记之. 简要题意 给你一个长度为 \(2n\) 的序列满足 \(\forall a_i\in[1,n]\) ...
- 帝国CMS下iframe标签无法引入视频,ueditor编辑器中html标签无法显示问题,设置ueditor默认行高为1.75
问题描述: 1.帝国cms后台添加优酷视频,使用到iframe,富文本编辑器中使用iframe引入视频后检查发现html代码未出现iframe字样,排查后发现为ueditor限制过滤了部分html代码 ...
- npm配置
在 Node.js 中,npm(Node Package Manager)是 JavaScript 包的默认管理工具.要查看 npm 的配置,你可以使用 npm config 命令配合不同的子命令来获 ...
- linux下配置ip为动态获取
点击查看代码 在Linux系统中配置网络接口以动态获取IP地址,通常需要使用DHCP(Dynamic Host Configuration Protocol).大多数现代Linux发行版都默认支持这个 ...
- LLM生成代码后,如何一键合并到源代码中(FastApply技术研究)
背景 在大语言模型越来越火的今天,越来越多的应用场景开始使用大语言模型来解决实际问题.而辅助编程可以算是大语言模型应用得最成功的场景之一了.早先的时候,更多使用的还是代码补全的能力,但是现在,各家产品 ...
- 百万架构师第四十七课:并发编程的原理(二)|JavaGuide
原文链接 JavaGuide <并发编程的艺术> 并发编程的实现原理 目标 上节课内容回顾 synchronized 原理分析 wait 和 notify Lock 同步锁 回顾 原子性 ...
- Qt QDateEdit下拉日历的样式设计
文章目录 QDateEdit样式设计 QDateEdit QCalendarWidget QDateEdit样式设计 最近做了一个用到QDateEdit的项目,涉及到对这个控件进行设计的方面,对于 ...
- CNVD挖掘思路
CNVD挖掘思路 CNVD获取条件 首先,先来了解一下目前cnvd发证资格 1.事件型 事件型漏洞必须是三大运营商(移动.联通.电信)的中高危漏洞,或者党政机关.重要行业单位.科研院所.重要企事业单位 ...
- 解决 Mac(M1/M2)芯片,使用node 14版本
前言 nvm 在安装 Node.js v14.21.3 时,报错: nvm install 14 Downloading and installing node v14.21.3... Downloa ...