Linux网络:基于文件的网络架构

Linux网络:基于文件的网络架构

    • 网络架构
    • TCP全连接队列


网络架构

Linux中提供了多种系统调用,完成网络操作。比如TCP连接的建立,各种报文的收发等等。但是所有的Linux网络操作,都源于系统调用socket

在这里插入图片描述

Linuxman手册中,对这个系统调用做了描述,表示返回值是一个file descriptor文件描述符。后续建立连接、监听连接、收发报文都要基于这个返回值,也就是说:Linux中网络被当作一个文件处理,这符合Linux的一切皆文件理念。

接下来以TCP为例,讲解Linux中的网络架构,源码版本为Linux 2.6.26

  • struct tcp_sock

TCP由结构体struct tcp_sock管理。

struct tcp_sock {
	struct inet_connection_sock	inet_conn;
	u16	tcp_header_len;
	u16	xmit_size_goal;
	// ...
};

这是TCP最底层的结构体,每创建一个TCP连接,底层都会维护一个这样的结构体。它记录了TCP的部分信息。

tcp_header_len这个成员记录了 TCP 头部的长度,单位是字节。 TCP 头部长度是可变的,因为它可以包含多个 TCP 选项。这个值用于在发送数据时确定需要预留多少空间给 TCP 头部。

xmit_size_goal这个成员定义了输出数据包的目标大小。它是发送数据时的一个目标值,用于分段输出数据。这个值可以帮助 TCP 层决定每个数据包应该发送多少数据。

其中最重要的是inet_conn,它的类型是struct inet_connection_sock

  • struct inet_connection_sock
struct inet_connection_sock {
	/* inet_sock has to be the first member! */
	struct inet_sock	  icsk_inet;
	struct request_sock_queue icsk_accept_queue;
	//...
};

在这个结构体中,包含了各种连接的相关信息,比如拥塞控制算法的相关参数,报文的重传次数,退避算法的退避时长等等。

其中icsk_accept_queue称为全连接队列,这个会在后文专门讲解。

其第一个成员icsk_inet也是一个结构体,类型为struct inet_sock

  • struct inet_sock
struct inet_sock {
	/* sk and pinet6 has to be the first two members of inet_sock */
	struct sock		sk;
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
	struct ipv6_pinfo	*pinet6;
#endif
	/* Socket demultiplex comparisons on incoming packets. */
	__be32			daddr;
	__be32			rcv_saddr;
	__be16			dport;
	__u16			num;
	__be32			saddr;
	//...
}

该结构体是 Linux 内核中处理基于 IP 套接字的通用结构体,它包含了 IP 层的套接字信息。

其中#if#endif是一个条件编译信息,如果使用IPv6协议,就会启用内部的指针pinet6,指向一个管理IPv6的结构体。

其还存储了一些IP协议的相关信息,比如daddr是目标地址,dport是目标端口,saddr是源地址等等。

这个结构体的第一个成员sk,类型为struct sock

  • struct sock
struct sock {
	/*
	 * Now struct inet_timewait_sock also uses sock_common, so please just
	 * don't add nothing before this first member (__sk_common) --acme
	 */
	struct sock_common	__sk_common;

	//...
	struct sk_buff_head	sk_receive_queue;
	struct sk_buff_head	sk_write_queue;
	//...
}

struct sock 结构体是所有类型套接字的通用结构体,包括 TCPUDP 和原始套接字等。

其中sk_receive_queue指向接收缓冲区,用于存储已经接收到,但是未被用户读取走的数据。sk_write_queue指向发送缓冲区,也就是用户交给操作系统,但是尚未被发送到网络的数据。

至此,已经大致打通了一个套接字管理的四个结构体:

在这里插入图片描述

自顶向下为:

  1. struct sock:套接字的通用结构体
  2. struct inet_sock:管理IP层的相关信息
  3. struct inet_connection_sock:管理网络连接
  4. struct tcp_sock:管理TCP连接

但是目前好像还没有看到和文件相关的内容?

其实网络与文件的衔接,是由struct socket完成的。

struct socket {
	socket_state		state;
	unsigned long		flags;
	const struct proto_ops	*ops;
	struct fasync_struct	*fasync_list;
	struct file		*file;
	struct sock		*sk;
	wait_queue_head_t	wait;
	short			type;
};

Linux 中,struct socket 结构体是用户空间与内核空间之间的接口,用于实现网络通信。

例如第一个成员state表示套接字的状态,比如已连接、未连接、正在连接等等。ops是一个结构体指针,指向的结构体是一个方法集,内部包含套接字的函数指针。

struct proto_ops {
	int		family;
	struct module	*owner;
	int		(*release)   (struct socket *sock);
	int		(*bind)	     (struct socket *sock,
				      struct sockaddr *myaddr,
				      int sockaddr_len);
	int		(*connect)   (struct socket *sock,
				      struct sockaddr *vaddr,
				      int sockaddr_len, int flags);
	int		(*socketpair)(struct socket *sock1,
				      struct socket *sock2);
	int		(*accept)    (struct socket *sock,
				      struct socket *newsock, int flags);
	int		(*listen)    (struct socket *sock, int len);
	int		(*setsockopt)(struct socket *sock, int level,
				      int optname, char __user *optval, int optlen);
	int		(*sendmsg)   (struct kiocb *iocb, struct socket *sock,
				      struct msghdr *m, size_t total_len);
	int		(*recvmsg)   (struct kiocb *iocb, struct socket *sock,
				      struct msghdr *m, size_t total_len,
				      int flags);
	// ...
};

在这个opt内部,可以看到很多系统调用的身影,比如listenbindsendmsgrecvmsg等等,TCP中收发信息使用的sendtorecvfrom系统调用,就是通过这些指针来实现的。

当使用套接字收发消息的时候,就会通过传入的文件描述符找到struct socket,进而找到opt,在opt内部查找收发消息的函数,完成数据传递。

那么为什么不能直接把这些函数写在struct sock内部,而是要使用一个struct proto_ops

这个struct sock既可以管理TCP也可以管理UDP,这两个协议所使用的函数当然不同。以bind为例,TCPstruct sock调用bind时,调用的其实是sock->opt->bind()UDP同理也去调用sock->opt->bind()。只要定义套接字时,opt这个指针指向不同的struct proto_ops,那么TCPUDP就会调用到不同的函数。

可以发现,这其实是C语言的一种多态实现方式,不同对象调用相同的方法,结果不一样。

struct socket还有一个sk成员,其类型为struct sock*,也就是指向之前四个层次的顶层。

在这里插入图片描述

struct socket中,还有一个和网络看似毫不相关的成员file,其类型为struct file*。这是Linux内核这中,描述一个文件的结构。也就是说struct socket对上将网络操作转化为文件操作,对下管理网络信息,并提供网络通信所需的函数

struct file中,有一个private_data成员:

struct file {
	//...
#ifdef CONFIG_SECURITY
	void			*f_security;
#endif
	/* needed for tty driver, and maybe others */
	void			*private_data;
	
	//...
};

其类型为void*,经过条件编译产生。如果一个文件指向网络操作,那么这个private_data就会启用,并指向struct socket

至此,就可以看清Linux的网络架构全貌了:

在这里插入图片描述

当用户调用接口创建套接字,会得到一个文件描述符sockfd,它指向一个文件。当通过sockfd操作网络时,会去查找该描述符对应的文件,在文件内部通过private_data找到struct socket,这里面包含各种网络操作的具体函数。再往下可以通过sk找到管理一个连接的struct sock,这里面包含这个连接的各种信息。

有了操作网络的方法和一个网络连接的相关信息,那么操作网络就可以实现了。

不过从用户的角度出发,用户看不到底层的网络信息,也无法直接接触socket->opt。也就是说Linux把网络完全隐藏起来了,对用户只表现为一个文件描述符sockfd以及文件操作。


TCP全连接队列

最后简单讲解一下TCP的全连接队列,全连接队列就是inte_connection_sock下的icsk_accept_queue。它完全由Linux内核管理,按理来说用户是无需关心这个内容的,但是在TCPlisten函数中,第二个参数backlog与其相关,所以再此要提一下。

listen函数声明:

int listen(int sockfd, int backlog);

这个函数用于让一个套接字开始进行TCP连接的监听,第一个参数传入文件描述符sockfd,第二个参数是一个int,这其实和全连接队列相关。

TCP连接的建立需要经过三次握手,在三次握手的过程中,双方主机要记录当前连接创建到哪一步了,为此Linux使用了全连接队列半连接队列来管理TCP连接。

在这里插入图片描述

以服务端为例,在三次握手过程中,server共有四种状态:等待连接、收到SYN、收到ACK、用户accept该连接。

其中accept由用户完成,剩下的由操作系统自动完成。一个套接字中很可能同时维护了多个TCP连接,不同连接又可能处于不同的状态,为此套接字使用了两个队列来维护这些状态。

在这里插入图片描述

当一个TCP连接收到SYN报文后,进入半连接队列,即这个连接创建了一半。当收到ACK报文后,说明连接创建完毕,此时离开半连接队列,进入全连接队列。

也就是说,全连接队列中存储的是已经完成三次握手,但是还没有被用户accept的数据

在一个套接字中,用户能同时处理的TCP连接是有限的,那么超出能力范围的TCP连接,就会暂存在全连接队列中,当用户处理完一个连接,再去全连接队列读取出一个连接。

假设服务器同时接收到大量的网络请求,整个操作系统就要创建很多TCP连接,进行很多次握手,这让本就繁忙的CPU雪上加霜。因此操作系统不能允许大量的TCP连接同时建立,就算建立好了连接,用户也不一定会读取这个连接。

因此全连接队列与半连接队列是有长度限制的,如果队列满了之后,操作系统收到新的TCP连接,操作系统会直接丢弃这个连接请求,或者返回一个RST报文,表示拒绝这个请求。

listen第二个参数backlog用于指定全连接队列的长度,但也不完全取决于这个参数。操作系统也会给出一个全连接队列的最大长度somaxconn,其在路径/proc/sys/net/core/somaxconn下。

在这里插入图片描述

最终的全连接队列长度会取min(backlog, somaxconn)

全连接队列内部的连接,需要通过accept函数接收,而accept函数的返回值是一个sockfd,这其实说明了Linux中网络系统转化到文件系统的时机。

TCP三次握手过程中,会预先创建struct sock,存储一些网络的相关信息,但是这个时候并不会创建struct file,因为Linux操作网络无需通过文件。

当用户accept一个连接的时候,Linux得知用户需要操作这个连接,那么就会为这个连接创建struct file,并完成文件系统到网络系统的映射关系,把文件描述符sockfd返回给用户。

也就是说,从网络系统到文件系统的映射,是在用户accept的时候完成的。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/918892.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【SpringBoot】23 文件预览(kkFileView)

Gitee仓库 https://gitee.com/Lin_DH/system 介绍 文件预览功能是指在不打开或编辑文件的情况下,通过某种方式查看文件的内容、格式或者部分内容的功能。该功能通常用于文件管理系统、办公工具、在线教育平台、企业协作平台、电子邮件客户端等领域,能…

Android笔记(三十七):封装一个RecyclerView Item曝光工具——用于埋点上报

背景 项目中首页列表页需要统计每个item的曝光情况,给产品运营提供数据报表分析用户行为,于是封装了一个通用的列表Item曝光工具,方便曝光埋点上报 源码分析 核心就是监听RecyclerView的滚动,在滚动状态为SCROLL_STATE_IDLE的时…

关于Java合并多个Excel中的数据【该数据不是常规列表】,并入库保存的方案

1. 背景 最近在使用RPA(机器人流程自动化)做数据采集的时候。发现那个RPA采集,一次只能采集相同格式的数据,然后入到Excel或者库中。由于院内系统的业务限制,导致采集的数据是多个Excel,并且我们这边的需求…

Robot | 用 RDK 做一个小型机器人(更新中)

目录 前言架构图开发过程摄像头模型转换准备校准数据使用 hb_mapper makertbin 工具转换模型 底版开发 结语 前言 最近想开发一个小型机器人,碰巧看到了 RDK x5 发布了,参数对于我来说非常合适,就买了一块回来玩。 外设也是非常丰富&#xf…

NPOI 实现Excel模板导出

记录一下使用NPOI实现定制的Excel导出模板&#xff0c;已下实现需求及主要逻辑 所需Json数据 对应参数 List<PurQuoteExportDataCrInput> listData [{"ItemName": "电缆VV3*162*10","Spec": "电缆VV3*162*10","Uom":…

TCP/IP--Socket套接字--JAVA

目录 一、概念 二、分类 1.流套接字 2.数据报套接字 三、UDP数据报套接字编程 1.API介绍 2.基于UDP实现简单回显服务器 一、概念 Socket套接字&#xff0c;是由系统提供⽤于⽹络通信的技术&#xff0c;是基于TCP/IP协议的⽹络通信的基本操作单元。 基于Socket套接字的⽹络…

从大数据到大模型:现代应用的数据范式

作者介绍&#xff1a;沈炼&#xff0c;蚂蚁数据部数据库内核负责人。2014年入职蚂蚁&#xff0c;承担蚂蚁集团的数据库架构职责&#xff0c;先后负责了核心链路上OceanBase&#xff0c;OceanBase高可用体系建设、NoSQL数据库产品建设。沈炼对互联网金融、数据库内核、数据库高可…

2024雪浪小镇·京东科技上海产业对接会

11月15日下午对接会由京东科技主办在上海南翔温德姆酒店顺利召开,来自上海本地的AIoT及工业互联网优秀企业、投资人、京东生态合作伙伴齐聚一堂,共同探讨技术赋能和产业协同之路,加速企业发展和促进产业升级。 无锡经开区是无锡最年轻、最具创新动力、产业张力、宜居魅力和开放…

Vue3踩坑记录

目录 一、定义常变量 1.1、ref和reactive到底用谁&#xff1f; 二、双向绑定 2.1、直接改变表格该行数据 2.1、在弹窗改变表格该行数据 一、定义常变量 1.1、ref和reactive到底用谁&#xff1f; 已知&#xff1a;使用ref定义基础类型数据&#xff1b;使用reactive定义复…

ROM修改进阶教程------安卓14去除修改系统应用后导致的卡logo验证步骤 适用安卓13 14 安卓15可借鉴参考

上期的博文解析了安卓14 安卓15去除系统应用签名验证的步骤解析。我们要明白。修改系统应用后有那些验证。其中签名验证 去卡logo验证 与可降级安装应用验证等等的区别。有些要相互结合使用。今天的博文将对修改系统应用后卡logo验证做个步骤解析。 通过博文了解💝💝�…

2024.11.18晚Linux复习课笔记

第一章 cat -n显示行号 -b不显示空行号 pwd 打印当前的工作目录 cd ls 打印当前工作的所有文件 -a -A -l:显示当前文件的详细信息 -r:递归显示 passwd:修改密码 ip a 查看ip地址 poweroff shutdown -h 关机 reboot shutdown -r 第二章 man --help …

数据可视化如何帮助企业提升数据洞察力?

数据驱动时代&#xff0c;企业每天都在面对数据的洪流。一方面&#xff0c;拥有海量数据意味着蕴藏着无尽的机会&#xff1b;另一方面&#xff0c;如果无法提炼这些数据背后的价值&#xff0c;它们只会成为业务发展的负担。例如&#xff0c;许多企业手握丰富的销售数据&#xf…

报错java: java.lang.NoSuchFieldError: Class com.sun.tools.javac.tree.JCTree$JCImport does not ...解决方法

在运行项目时出现java: java.lang.NoSuchFieldError: Class com.sun.tools.javac.tree.JCTree$JCImport does not have member field com.sun.tools.javac.tree.JCTree qualidzz这样的报错 解决方法 1.第一步&#xff1a;在pom文件中将lombok的版本改成最新的 此时1.18.34是新…

MyBatisPlus(Spring Boot版)的基本使用

1. 初始化项目 1.1. 配置application.yml spring:# 配置数据源信息datasource:# 配置数据源类型type: com.zaxxer.hikari.HikariDataSource# 配置连接数据库信息driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/mybatis_plus?characterEncodi…

【MongoDB】MongoDB的集群,部署架构,OptLog,集群优化等详解

文章目录 一、引入复制集的原因二、复制集成员&#xff08;一&#xff09;基本成员&#xff08;二&#xff09;主节点&#xff08;Primary&#xff09;细化成员 三、复制集常见部署架构&#xff08;一&#xff09;基础三节点&#xff08;二&#xff09;跨数据中心 四、复制集保…

Golang | Leetcode Golang题解之第564题寻找最近的回文数

题目&#xff1a; 题解&#xff1a; func nearestPalindromic(n string) string {m : len(n)candidates : []int{int(math.Pow10(m-1)) - 1, int(math.Pow10(m)) 1}selfPrefix, _ : strconv.Atoi(n[:(m1)/2])for _, x : range []int{selfPrefix - 1, selfPrefix, selfPrefix …

git根据远程分支创建本地新分支

比如我当前本地仓库有4个 remote 仓库&#xff0c;我希望根据其中的一个 <remote>/<branch> 创建本地分支&#xff1a; 先使用 github fetch <remote> 拉取 <remote> 的分支信息&#xff0c;然后在 git checkout -b 创建新分支时使用 -t <remote>…

r-and-r——提高长文本质量保证任务的准确性重新提示和上下文搜索的新方法可减轻大规模语言模型中的迷失在中间现象

概述 随着大规模语言模型的兴起&#xff0c;自然语言处理领域取得了重大发展。这些创新的模型允许用户通过输入简单的 "提示 "文本来执行各种任务。然而&#xff0c;众所周知&#xff0c;在问题解答&#xff08;QA&#xff09;任务中&#xff0c;用户在处理长文本时…

Redis 概 述 和 安 装

安 装 r e d i s: 1. 下 载 r e dis h t t p s : / / d o w n l o a d . r e d i s . i o / r e l e a s e s / 2. 将 redis 安装包拷贝到 /opt/ 目录 3. 解压 tar -zvxf redis-6.2.1.tar.gz 4. 安装gcc yum install gcc 5. 进入目录 cd redis-6.2.1 6. 编译 make …