epoll为什么用红黑树?

news2024/11/25 4:25:20

网络编程的时候有没有碰到过 Socket 对象?或者在配置代理的时候,有没有碰到配置 Socket 地址?当你看到服务端 Socket、客户端 Socket 等名词时,是否可以明确理解这些概念?
学习好这些知识有一条主线,就是抓住操作系统对 Socket 文件的设计。以“epoll 为什么用红黑树”为引,开启今天的学习,将这部分知识一网打尽!

Socket 是什么?

首先,Socket 是一种编程的模型。

下图中,从编程的角度来看,客户端将数据发送给在客户端侧的Socket 对象,然后客户端侧的 Socket 对象将数据发送给服务端侧的 Socket 对象。Socket 对象负责提供通信能力,并处理底层的 TCP 连接/UDP 连接。对服务端而言,每一个客户端接入,就会形成一个和客户端对应的 Socket 对象,如果服务器要读取客户端发送的信息,或者向客户端发送信息,就需要通过这个客户端 Socket 对象。

但是如果从另一个角度去分析,Socket 还是一种文件,准确来说是一种双向管道文件。什么是管道文件呢?管道会将一个程序的输出,导向另一个程序的输入。那么什么是双向管道文件呢?双向管道文件连接的程序是对等的,都可以作为输入和输出。

比如下面这段服务端侧程序:

var serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress(80));

看起来我们创建的是一个服务端 Socket 对象,但如果单纯看这个对象,它又代表什么呢?如果我们理解成代表服务端本身合不合理呢——这可能会比较抽象,在服务端存在一个服务端 Socket。但如果我们从管道文件的层面去理解它,就会比较容易了。其一,这是一个文件;其二,它里面存的是所有客户端 Socket 文件的文件描述符。

当一个客户端连接到服务端的时候,操作系统就会创建一个客户端 Socket 的文件。然后操作系统将这个文件的文件描述符写入服务端程序创建的服务端 Socket 文件中。服务端 Socket 文件,是一个管道文件。如果读取这个文件的内容,就相当于从管道中取走了一个客户端文件描述符。

![在这里插入图片描述](https://img-blog.csdnimg.cn/022ab1cd713645ccae0f07839c7eda49.png)

如上图所示,服务端 Socket 文件相当于一个客户端 Socket 的目录,线程可以通过 accept() 操作每次拿走一个客户端文件描述符。拿到客户端文件描述符,就相当于拿到了和客户端进行通信的接口。

前面我们提到 Socket 是一个双向的管道文件,当线程想要读取客户端传输来的数据时,就从客户端 Socket 文件中读取数据;当线程想要发送数据到客户端时,就向客户端 Socket 文件中写入数据。客户端 Socket 是一个双向管道,操作系统将客户端传来的数据写入这个管道,也将线程写入管道的数据发送到客户端

有同学会说,那既然可以双向传送,这不就是两个单向管道被拼凑在了一起吗?这里具体的实现取决于操作系统,Linux 中的管道文件都是单向的,因此 Socket 文件是一种区别于原有操作系统管道的单独的实现。

总结下,Socket 首先是文件,存储的是数据。对服务端而言,分成服务端 Socket 文件和客户端 Socket 文件。服务端 Socket 文件存储的是客户端 Socket 文件描述符;客户端 Socket 文件存储的是传输的数据。读取客户端 Socket 文件,就是读取客户端发送来的数据;写入客户端文件,就是向客户端发送数据。对一个客户端而言, Socket 文件存储的是发送给服务端(或接收的)数据。

综上,Socket 首先是文件,在文件的基础上,又封装了一段程序,这段程序提供了 API 负责最终的数据传输

服务端 Socket 的绑定

为了区分应用,对于一个服务端 Socket 文件,我们要设置它监听的端口。比如 Nginx 监听 80 端口、Node 监听 3000 端口、SSH 监听 22 端口、Tomcat 监听 8080 端口。端口监听不能冲突,不然客户端连接进来创建客户端 Socket 文件,文件描述符就不知道写入哪个服务端 Socket 文件了。这样操作系统就会把连接到不同端口的客户端分类,将客户端 Socket 文件描述符存到对应不同端口的服务端 Socket 文件中。

因此,服务端监听端口的本质,是将服务端 Socket 文件和端口绑定,这个操作也称为 bind。有时候我们不仅仅绑定端口,还需要绑定 IP 地址。这是因为有时候我们只想允许指定 IP 访问我们的服务端程序。

扫描和监听

对于一个服务端程序,可以定期扫描服务端 Socket 文件的变更,来了解有哪些客户端想要连接进来。如果在服务端 Socket 文件中读取到一个客户端的文件描述符,就可以将这个文件描述符实例化成一个 Socket 对象。

在这里插入图片描述

之后,服务端可以将这个 Socket 对象加入一个容器(集合),通过定期遍历所有的客户端 Socket 对象,查看背后 Socket 文件的状态,从而确定是否有新的数据从客户端传输过来。

在这里插入图片描述

上述的过程,我们通过一个线程就可以响应多个客户端的连接,也被称作I/O 多路复用技术

响应式(Reactive)

在 I/O 多路复用技术中,服务端程序(线程)需要维护一个 Socket 的集合(可以是数组、链表等),然后定期遍历这个集合。这样的做法在客户端 Socket 较少的情况下没有问题,但是如果接入的客户端 Socket 较多,比如达到上万,那么每次轮询的开销都会很大。

从程序设计的角度来看,像这样主动遍历,比如遍历一个 Socket 集合看看有没有发生写入(有数据从网卡传过来),称为命令式的程序。这样的程序设计就好像在执行一条条命令一样,程序主动地去查看每个 Socket 的状态。

在这里插入图片描述

命令式会让负责下命令的程序负载过重,例如,在高并发场景下,上述讨论中循环遍历 Socket 集合的线程,会因为负担过重导致系统吞吐量下降。

与命令式相反的是响应式(Reactive),响应式的程序就不会有这样的问题。在响应式的程序当中,每一个参与者有着独立的思考方式,就好像拥有独立的人格,可以自己针对不同的环境触发不同的行为。

从响应式的角度去看 Socket 编程,应该是有某个观察者会观察到 Socket 文件状态的变化,从而通知处理线程响应。线程不再需要遍历 Socket 集合,而是等待观察程序的通知。

在这里插入图片描述

当然,最合适的观察者其实是操作系统本身,因为只有操作系统非常清楚每一个 Socket 文件的状态。原因是对 Socket 文件的读写都要经过操作系统。在实现这个模型的时候,有几件事情要注意。

  1. 线程需要告诉中间的观察者自己要观察什么,或者说在什么情况下才响应?比如具体到哪个 Socket 发生了什么事件?是读写还是其他的事件?这一步我们通常称为注册

  2. 中间的观察者需要实现一个高效的数据结构(通常是基于红黑树的二叉搜索树)。这是因为中间的观察者不仅仅是服务于某个线程,而是服务于很多的线程。当一个 Socket 文件发生变化的时候,中间观察者需要立刻知道,究竟是哪个线程需要这个信息,而不是将所有的线程都遍历一遍。

为什么用红黑树?

关于为什么要红黑树,这里我给你再仔细解释一下。考虑到中间观察者最核心的诉求有两个。

第一个核心诉求,是让线程可以注册自己关心的消息类型。比如线程对文件描述符 =123 的 Socket 文件读写都感兴趣,会去中间观察者处注册。当 FD=123 的 Socket 发生读写时,中间观察者负责通知线程,这是一个响应式的模型。

第二个核心诉求,是当 FD=123 的 Socket 发生变化(读写等)时,能够快速地判断是哪个线程需要知道这个消息

所以,中间观察者需要一个快速能插入(注册过程)、查询(通知过程)一个整数的数据结构,这个整数就是 Socket 的文件描述符。综合来看,能够解决这个问题的数据结构中,跳表和二叉搜索树都是不错的选择。

因此,在 Linux 的 epoll 模型中,选择了红黑树。红黑树是二叉搜索树的一种,红与黑是红黑树的实现者才关心的内容,对于我们使用者来说不用关心颜色,Java 中的 TreeMap 底层就是红黑树。

总结

总结一下,Socket 既是一种编程模型,或者说是一段程序,同时也是一个文件,一个双向管道文件。你也可以这样理解,Socket API 是在 Socket 文件基础上进行的一层封装,而 Socket 文件是操作系统提供支持网络通信的一种文件格式。

在服务端有两种 Socket 文件,每个客户端接入之后会形成一个客户端的 Socket 文件,客户端 Socket 文件的文件描述符会存入服务端 Socket 文件。通过这种方式,一个线程可以通过读取服务端 Socket 文件中的内容拿到所有的客户端 Socket。这样一个线程就可以负责响应所有客户端的 I/O,这个技术称为 I/O 多路复用。

主动式的 I/O 多路复用,对负责 I/O 的线程压力过大,因此通常会设计一个高效的中间数据结构作为 I/O 事件的观察者,线程通过订阅 I/O 事件被动响应,这就是响应式模型。在 Socket 编程中,最适合提供这种中间数据结构的就是操作系统的内核,事实上 epoll 模型也是在操作系统的内核中提供了红黑树结构。

所以epoll 为什么用红黑树

在 Linux 的设计中有三种典型的 I/O 多路复用模型 select、poll、epoll。

select 是一个主动模型,需要线程自己通过一个集合存放所有的 Socket,然后发生 I/O 变化的时候遍历。在 select 模型下,操作系统不知道哪个线程应该响应哪个事件,而是由线程自己去操作系统看有没有发生网络 I/O 事件,然后再遍历自己管理的所有 Socket,看看这些 Socket 有没有发生变化。

poll 提供了更优质的编程接口,但是本质和 select 模型相同。因此千级并发以下的 I/O,你可以考虑 select 和 poll,但是如果出现更大的并发量,就需要用 epoll 模型。

epoll 模型在操作系统内核中提供了一个中间数据结构,这个中间数据结构会提供事件监听注册,以及快速判断消息关联到哪个线程的能力(红黑树实现)。因此在高并发 I/O 下,可以考虑 epoll 模型,它的速度更快,开销更小。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Dockerfile构建镜像缓慢解决方案总结

分几种不同的情况,不断更新中 1、Dockerfile apk add 下载更新软件时,比较慢,如何解决 例子如下: 更好一下仓库源 RUN sed -i s/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g /etc/apk/repositories或者改成科大的镜像 RUN…

android核心架构Framework组件介绍

作为一个android开发者,核心架构是必须要了解的。只有了解每个核心层的作用,才能更深入的理解和学习。本篇主要讲解Java Framework层核心代码流程。 文章目录一,Android系统架构1.System Apps2.Java Framework3.系统运行库层4.硬件抽象层&…

Matplotlib库的简单用法

Matplotlib库的简单用法 Matplotlib是python科学计算中最基础、最重要的绘图库,是Python中最流行的数据可视化库之一,它提供了大量的绘图函数和工具,可以让用户创建各种类型的图表和图形,一般使用matpltlib完全可以满足我们绘图需…

“成功学大师”杨涛鸣被抓

我是卢松松,点点上面的头像,欢迎关注我哦! 4月15日,号称帮助一百多位草根开上劳斯莱斯,“成功学大师”杨涛鸣机其团队30多人已被刑事拘留,培训课程涉嫌精神传销,警方以诈骗案进行立案调查。 …

基于4412的dm9000驱动移植

1 概述 以太网高速稳定的特性比Wifi无线传输有一定的优势,当无线传输无法满足一些智能设备,需要开发设计以太网模块。Linux支持以太网系统,结合4412开发板,重点学习dm9000驱动的设计与实现。 2 硬件资源分析 2.1 4412开发板以太网…

【Python】Python中的列表,元组,字典

文章目录列表创建列表获取元素修改元素添加元素查找元素删除元素列表拼接遍历列表切片操作元组创建元组元组中的操作字典创建字典添加/修改元素删除元素查找字典的遍历合法的key类型列表 列表是一种批量保存数据的方式,列表使用[]表示 创建列表 创建两个空列表 …

nginx优化及配置

nginx隐藏版本号 查看方法 浏览器F12 看network头部看server curl -i 192.168.232.7 获取头部(查版本号) 配置文件改 添加server_tokens off 改源码 cd /src/core vim nginx.h 修改 修改的IIS为window常用的软件服务 重新编译安装 cd nginx_1.2…

【C++】while 循环应用案例 - 猜数字游戏

目录 1、缘起 2、案例描述 3、代码 4、相关知识点 4.1、rand() 函数 4.2、srand() 函数 5、总结 1、缘起 猜数字游戏是一种简单而又有趣的游戏,在这个游戏中,计算机会生成一个随机数字,玩家需要通过不断猜测来猜出这个数字。在本篇博…

使用docker搭建lnmp环境+redis服务

lnmp搭建过程,前文已经写了传送门,本文主要写一下运行redis容器和php-fpm容器内安装redis扩展 redis 1.创建宿主机配置和数据文件夹 [rootlocalhost ~] mkdir -p /lnmp/redis/{data,conf}2.放置/lnmp/redis/conf/redis.conf文件 点我下载 3.启动容器 …

k8s+kubeedge+sedna安装全套流程+避坑指南+解决办法

最近在学习边缘计算要用到kubeedge,安装了好多次总会遇到各种各样的问题,因此在这里一一列出,以方便下次安装。则里面可能出错的地方太多,如果有问题,请私信联系。 一、环境准备 节点IP环境软件云端节点172.23.70.23…

Python|矿产卫片Excel经纬度坐标数据转换为shp点数据——OGR库实现

1.实验需求 基于Excel表格里面的经纬度坐标数据,自动生成点shp矢量文件,并添加属性信息。 2.编程思路详解 ①使用Pandas库读取原始矿产图斑列表表格; xlsx_path = uC:\\Users\\YaoJun\\Desktop\\矿产图斑列表.xlsx #sheet_name默认为0,即读取第一个sheet的数据 df = pd.…

TPM管理工作应该如何开展?

在制造行业,Total Productive Maintenance(TPM)管理被广泛认为是提高生产效率和设备可靠性的有效方式。然而,实施TPM管理需要深入的专业知识和经验。本文将探讨如何开展TPM管理工作,以确保制造企业的生产效率和设备可靠…

2023-Python实现烯牛数据采集

文章目录👉1、目标网址👉2、接口分析👉3、代码实现【JS 逆向百例】 1/100 学习记录:哈喽~ 前面我们接触了一些JS逆向的数据获取,如果前面的百度,有道翻译和正方教务系统的登录加密你已掌握,说明…

计组2.4——加法器的设计

计组:2.4算术逻辑单元异或门实现奇偶校验的原理串行加法器&&并行加法器并行加法器的优化算术逻辑单元 控制信号: 当M0时表示算术运算 当M1时表示逻辑运算 S0~ S3表示做什么运算,因此ALU可以表示16种算数运算和16种逻辑运算 Ai,Bi代表…

PL-VINS线特征处理部分源码阅读

PL-VINS线特征处理部分源码阅读1 linefeature_tracker2 三角化单目三角化双目三角化3 后端优化线特征状态量重投影误差本文主要阅读PL-VINS中引入线特征的代码实现,包括线特征表示方法(Plcker参数化方法、正交表示法)、前端线特征提取与匹配、…

遥感、GIS及GPS在土壤空间数据分析、适应性评价、制图及土壤普查中的应用

摸清我国当前土壤质量与完善土壤类型,可以为守住耕地红线、保护生态环境、优化农业生产布局、推进农业高质量发展奠定坚实基础,为此,2022年初国务院印发了《关于开展第三次全国土壤普查的通知》,决定自2022年起开展第三次全国土壤…

微信支付,JSAPI支付,APP支付,H5支付,Native支付,小程序支付功能详情以及回调处理

一.支付相关文档地址支付wiki:https://pay.weixin.qq.com/wiki/doc/apiv3/index.shtml支付api: https://pay.weixin.qq.com/wiki/doc/apiv3/apis/index.shtml开发工具包(SDK)下载:https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay6_0.shtm…

靶机精讲之CTF4

主机发现 靶机193 端口扫描 服务扫描 80,25(明确版本)攻击面更大 web渗透 blog是交互式的程序 发现index可进行手动爆破(地址包含) http://192.168.10.193/index.html?page../../../../../../../../etc/passwd 无发…

雨水情测报系统+智慧水库大坝安全监测系统

解决方案 雨水情测报系统智慧水库大坝安全监测系统,系统主要由降雨量监测站、水库水位监测站、大坝安全监测中的渗流量、渗流压力和变形监测站及视频和图像监测站等站点组成,同时建立规范、统一的监测平台,集数据传输、信息共享、数据储存于…

Git简单使用~下载、安装、命令行使用、IDEA使用

文章目录一、Git下载二、Git安装三、命令行操作四、IDEA使用gitee4. 查看Gitee仓库一、Git下载 官网下载地址:Git (git-scm.com) 点击"Download for Windows",跳转至详细下载页面。 以Windows64位安装版为例,点击"64-bit…