多路转接之epoll
本篇博客介绍: 多路转接之epoll
多路转接之epoll
- 初识epoll
- epoll相关系统调用
- epoll的工作原理
- epoll服务器编写
- 成员变量
- 构造函数
- 循环函数
- HandlerEvent函数
- epoll的优缺点
我们学习epoll分为四部分
- 快速理解部分概念 快速的看一下部分接口
- 讲解epoll的工作原理
- 手写epoll服务器
- 工作模式
并且在这四个部分的内容学习完毕之后我们学习一下Reactor模式
初识epoll
按照man手册的说法
epoll是为了处理大量句柄而做出改进的poll
它在2.5.44内核中被引入到Linux
也是目前来说最常用的一种多路转接IO方式
epoll相关系统调用
epoll函数有三个相关的系统调用 分别是
- epoll_create
- epoll_ctl
- epoll_wait
epoll_create函数
epoll_create函数的作用是创建一个epoll模型 函数原型如下
int epoll_create(int size);
参数说明:
- 目前来说 epoll_create的参数是被废弃的 我们设置为256或者512就行 这样设计的原因是为了向前兼容
返回值说明:
- 返回一个epoll模型 (实际上就是一个文件描述符)
epoll_ctl函数
epoll_ctl函数的作用是对创建出来的epoll模型进行操控 函数原型如下
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
参数说明:
- int epfd 标识一个我们的IO模型
- int op operator 表示我们想要做出什么样的操作
- int fd 表示我们需要添加的文件描述符
- epoll_event *event 表示我们需要关心哪些事件
返回值说明:
- 函数成功调用返回0 失败返回-1 同时错误码将被设置
epoll_wait函数
epoll_wait函数的作用是监视我们关心的关键描述符 函数原型如下
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
参数说明:
- int epfd 标识我们的epoll模型
- struct epoll_event *events 输出型参数 内核会拷贝已经就绪的事件到这里面
- int maxevents events数组的元素个数
- int timeout 和poll函数中的timeout一样 等待事件 单位是毫秒
epoll的工作原理
我们之前的学习的多路转接函数 无论是select还有poll 它们都需要我们做下面的操作
- 让我们维护一个第三方的数组
- 都需要遍历整个数组
- 都需要经历用户到内核 内核到用户的事件通知
而我们的epoll工作模式则不同
操作系统硬件上的工作模式如下
这是一个缩略版的操作系统图
那么现在问题来了 操作系统是如何知道硬件里面有数据了呢?
(这个硬件可以是网卡 可以是键盘等等)
具体解释如下图
而epoll的工作原理如下
还是该图
当我们创建一个epoll模型之后操作系统底层会帮助我们维护一颗红黑树
红黑树的节点里面维护着很多元素 其中最重要的是两个
- 文件描述符
- 事件
所以说这颗红黑树解决的是用户通知内核的问题
用户通知内核自己要关心哪些文件描述符的哪些事件之后 操作系统就会生成一个节点然后插入到这颗红黑树当中
而这颗红黑树就是对应我们select和poll当中的数组
只不过此时它就由操作系统进行维护了
而我们内核通知用户的则是通过消息队列通知
我们可以这么理解 在内核维护的红黑树旁边有一个消息队列
每当有fd的事件就绪的时候就会在该队列上添加一个元素
于是我们用户读取的时候时间复杂度变为了O(1)
操作系统什么时候构建就绪队列节点呢?
操作系统在调用驱动的时候构建就绪队列节点
在生成红黑树节点的时候 在驱动中 每个节点都会生成一个自己的回调函数
于是在经历了硬件中断到读取数据的过程后 操作系统会调用驱动中的回调函数来获取该节点的数据 并且根据这些数据(fd和events)构建就绪节点 最后将构建好的节点插入到队列中
我们将上面的一整套机制称为epoll模型
那么我们现在再来回顾下epoll的三个函数
- epoll_create
- epoll_ctl
- epoll_wait
它们的作用分别是
- epoll_create : 创建epoll模型 包括红黑树 就绪队列 回调函数等
- epoll_ctl : 对于红黑树的节点进行注册
- epoll_wait : 获取就绪队列中的内容
为什么epoll_create返回一个文件描述符 而epoll_ctl和epoll_wait需要用到这个文件描述符呢?
这个问题最本质的原因是因为文件描述符表是随进程的 具体理解我们可以看下图
我们都知道每个进程都对应一个PCB结构 而每个PCB结构中都会有一个file struct结构体 这个结构体中有一个文件数组 每个下标对应一个文件描述符
而epoll_create的本质就是打开了一个文件 所以被分配了一个文件描述符
在这个文件中有个void* p指针 可以找到我们上面说的那些红黑树 就绪队列等等
这里还有一些关于epoll服务器的一些小细节
epoll底层维护的红黑树key值是什么呢?
是fd文件描述符 它是一个绝佳的天然key值 既不会重复 又容易排序
用户需要关系os对于fd和event的管理吗
不需要 os会在底层完成这些事
epoll为什么高效呢
- 因为epoll底层维护的是红黑树结构 对比数组来说增删改查有着天然的优势
- 我们不需要主动去询问哪些文件是否就绪 os会自动将其添加到就绪队列中
- 在寻找就绪文件的时候 由于我们使用的是就绪队列 时间复杂度是O(1) 而遍历数组的时间复杂度则是O(N)
epoll有线程安全问题嘛?
没有
实际上就绪队列是一个经典的生产者消费者模型 os生成数据 而用户消费数据 所以说这个队列实际上是一个临界资源 所以说操作系统在底层对其做了一些加锁处理 让他变为线程安全的
如果底层没有就绪事件 我们上层应该怎么办呢?
根据timeout参数来决定
- 如果timeout为0 则是非阻塞
- 如果timeout为-1 则是阻塞
- 如果timeout大于0 则表示我们要等待多少毫秒之后去读取
epoll服务器编写
接下来我们开始设计一个epoll服务器
成员变量
首先作为一个基于TCP协议的服务器 我们必须要有listen套接字和端口号
int _listensock; uint16_t _port;
其次作为一个epoll服务器 我们还必须要有一个epfd作为句柄来标识一个epoll模型
int _epfd;
此外我们还需要设置一个数组来接收epoll_wait的数据
struct epoll_event* _revs; int _revs_num;
构造函数
ep_server(const int& port = default_port) :_port(port) { // 1. create listensock _listensock = Sock::Socket(); Sock::Bind(_listensock , _port); Sock::Listen(_listensock); // 2. create epoll_epfd = epoll::createepoll(); logMessage(DEBUG , "create epoll_server success, epfd: %d , listensock: %d " ,_epfd , _listensock); // 3. append listen socket to epollif(epoll::epollctl(_epfd , EPOLL_CTL_ADD , _listensock , EPOLLIN)) {logMessage(DEBUG , "epollctl add success %d");} else {exit(6);} }
我们这里不直接使用epoll的原生函数来进行操作 而是进行一下封装
封装后的epoll类如下
class epoll
{ public: static const int gsize = 256; public: static int createepoll() { int epfd = epoll_create(gsize); if (epfd > 0) { return epfd; } else {// errexit(5);}} static bool epollctl(int epfd , int oper , int sock , uint32_t events) { struct epoll_event ev; ev.data.fd = sock; ev.events = events; int ret = epoll_ctl(epfd , oper , sock , &ev); return ret == 0; }static int epollwait(int epfd , struct epoll_event res[] , int num , int timeout) { return epoll_wait(epfd , res , num , timeout); }
};
循环函数
我们服务器肯定不是只accept一次就完事了 所以说我们需要设计一个循环函数来重复执行accept的动作
我们分析下 首先我们每次循环肯定是要检测一次epoll就绪队列中有没有数据的 如果有的话 我们就直接从这个里面拿数据 并且把这个数据拿出来
特别注意 如果是listen套接字中的数据 我们还需要往 struct_events
中添加数据
每次循环的大概代码如下
int n = epoll_wait(_epfd, _revs, _num, timeout); switch (n) { case 0: logMessage(NORMAL, "timeout ..."); break; case -1: logMessage(WARNING, "epoll_wait failed, code: %d, errstring: %s", errno, strerror(errno)); break; default: logMessage(NORMAL, "have event ready"); //HandlerEvent(n); break;
我们将处理函数重新封装
HandlerEvent函数
在每次循环的时候我们成功使用epoll_wait拿到了就绪队列里的数据之后会走到这里
这里我们要进行判断 到底是listensock就绪了还是普通sock套接字就绪了
如果是listensock套接字就绪就代表我们要接收一个新的请求 如果是普通sock就绪就代表我们可以读取请求了
void HandlerEvent(int readyNum) { logMessage(DEBUG, "HandlerEvent in"); for (int i = 0; i < readyNum; i++) { uint32_t events = _revs[i].events; int sock = _revs[i].data.fd; if (sock == _listensock && (events & EPOLLIN)) { //_listensock读事件就绪, 获取新连接 std::string clientip; uint16_t clientport; int fd = Sock::Accept(sock, &clientip, &clientport); if (fd < 0) { logMessage(WARNING, "accept error"); continue; } // 获取fd成功,可以直接读取吗??不可以,放入epoll struct epoll_event ev; ev.events = EPOLLIN; ev.data.fd = fd; epoll_ctl(_epfd, EPOLL_CTL_ADD, fd, &ev); } else if (events & EPOLLIN) { // 普通的读事件就绪 // 依旧有问题 char buffer[1024];// 把本轮数据读完,就一定能够读到一个完整的请求吗??int n = recv(sock, buffer, sizeof(buffer), 0);if (n > 0){buffer[n] = 0;logMessage(DEBUG, "client# %s", buffer);// TODOstd::string response = func_(buffer);send(sock, response.c_str(), response.size(), 0);}else if (n == 0){// 建议先从epoll移除,才close fdepoll_ctl(_epfd, EPOLL_CTL_DEL, sock, nullptr);close(sock);logMessage(NORMAL, "client quit");} else{// 建议先从epoll移除,才close fdepoll_ctl(_epfd, EPOLL_CTL_DEL, sock, nullptr);close(sock);logMessage(ERROR, "recv error, code: %d, errstring: %s", errno, strerror(errno));}}else{}}logMessage(DEBUG, "HandlerEvent out");}
其实到这里 我们简单的epoll服务器就做完了
我们接下来还要学习下epoll服务器的工作模式
epoll的优缺点
相关文章:

多路转接之epoll
本篇博客介绍: 多路转接之epoll 多路转接之epoll 初识epollepoll相关系统调用epoll的工作原理epoll服务器编写成员变量构造函数 循环函数HandlerEvent函数epoll的优缺点 我们学习epoll分为四部分 快速理解部分概念 快速的看一下部分接口讲解epoll的工作原理手写epo…...

删除排序链表中的重复节点II(C++解法)
题目 给定一个已排序的链表的头 head , 删除原始链表中所有重复数字的节点,只留下不同的数字 。返回 已排序的链表 。 示例 1: 输入:head [1,2,3,3,4,4,5] 输出:[1,2,5]示例 2: 输入:head [1…...

uniapp自定义tab切换css样式、uni-forms中input下拉等标签字体、过宽、溢出样式一系列调整(附加实战举例)
一、uniapp自定义tab切换css样式 <view class="tabs-container"><view class="tabs-list">...

windows server 2016-IIS静态服务器-设置详细过程
文章目录 1.打开仪表盘新建角色2.iis功能模块3.启动服务器4.优点 1.打开仪表盘新建角色 2.iis功能模块 能选上的尽量选上,除非知道自己用不上。 然后确认,下一步,安装。 3.启动服务器 搜索IIS,启动IIS管理器。 启动网站。 右…...

不一样的编程方式 —— 协程(设计原理与汇编实现)
主要通过以下9个方面来了解协程的原理: 目录 1、为什么使用协程 1.3、协程的适用场景 2、协程的原语操作 3、协程的切换 3.1、汇编实现 4.协程的运行流程 5.协程的结构体定义(我们其实可以参照线程或者进程的状态来设计) 5.1、多状态集合设计 6.协程的调度…...

Thinkphp6项目在虚拟机无法指向pulic的目录访问的方法
以阿里云虚拟主机为例,服务器环境为 LAMP,Apache2.4 php7.2 mysql5.7 1.根目录新建 index.php 文件,将以下内容放入文件中 <?php include ./public/index.php;2.将 public 目录下的 admin.php、backend 文件夹、static 文件夹、tinymc…...

数据结构(超详细讲解!!)第十八节 串(堆串)
1.定义 假设以一维数组heap [MAXSIZE] 表示可供字符串进行动态分配的存储空间,并设 int start 指向heap 中未分配区域的开始地址(初始化时start 0) 。在程序执行过程中,当生成一个新串时,就从start指示的位置起&#…...

idea集成测试插件替代postman
idea集成测试插件替代postman 兄弟萌,你再测试接口是否无bug是否流畅的时候是否还在使用“postman”来回切换进行测试呢? 页面切换进行测试,有没有感觉很麻烦呢? 打开postman,输入接口地址,有没有感觉很麻烦…...

clusterprolifer go kegg msigdbr 富集分析应该使用哪个数据集,GO?KEGG?Hallmark?
关注微信:生信小博士 5 Overview of enrichment analysis Chapter 5 Overview of enrichment analysis | Biomedical Knowledge Mining using GOSemSim and clusterProfiler 5.1.2 Gene Ontology (GO) Gene Ontology defines concepts/classes used to describ…...
Linux学习笔记1-入门
前言:之前的基于单片机的闭环控制步进电机项目其实已经完成了,但很多时间都花在调试和生产上,实在没时间去做总结笔记,现在又开始做新项目了,从单片机到了Linux,想用这个平台来督促自己继续学习,…...
怎样更有效的运营Etsy店铺?
大家都知道,Etsy作为一个重要的电商平台,给很多人提供了不少机会。但是如何取得etsy店铺运营的成功呢?第一步就是选好辅助工具。 什么是指纹浏览器? VMLogin指纹浏览器(www.vmlogin.com.cn) 是一种工具,通过伪装用户…...

Vue 项目中如何使用Bootstrap5(简单易懂)
Vue 项目中如何使用Bootstrap5(简单易懂) 安装在 src/main.js 文件下引入包在vue文件中使用 Bootstrap官网(中文):https://www.bootcss.com/ Bootstrap5文档:https://v5.bootcss.com/docs/getting-started/…...

k8s 资源预留
KUBERNETES资源管理之–资源预留 Kubernetes 的节点可以按照 Capacity 调度。node节点本身除了运行不少驱动 OS 和 Kubernetes 的系统守护进程,默认情况下 pod 能够使用节点全部可用容量, 除非为这些系统守护进程留出资源,否则它们将与 pod 争…...

微信小程序自定义弹窗阻止滑动冒泡catchtouchmove之后弹窗内部内容无法滑动
自定义弹窗 如图所示: 自定义弹窗内部有带滚动条的盒子区域 问题: 在盒子上滑动,页面如果超出一屏的话,也会跟着一起上下滚动 解决方案:给自定义弹窗 添加 catchtouchmove 事件,阻止冒泡即可 网上不少…...

Linux 命令速查
Network ping ping -c 3 -i 0.01 127.0.0.1 # -c 指定次数 # -i 指定时间间隔 日志 一般存放位置: /var/log,包含:系统连接日志 进程统计 错误日志 常见日志文件说明 日志功能access-logweb服务访问日志acct/pacct用户命令btmp记录失…...

第22期 | GPTSecurity周报
GPTSecurity是一个涵盖了前沿学术研究和实践经验分享的社区,集成了生成预训练 Transformer(GPT)、人工智能生成内容(AIGC)以及大型语言模型(LLM)等安全领域应用的知识。在这里,您可以…...
JavaScript前端 console 控制台详细解析与代码实例
JavaScript Console(控制台)是一个重要的工具,可以用于调试和测试 JavaScript 代码。在浏览器中,你可以使用控制台来查看 JavaScript 输出、测试代码、调试错误等。在本文中,我们将详细介绍控制台的常用功能和代码实例…...

idea中启动多例项目配置
多实例启动 日常本地开发微服务项目时,博主想要验证一下网关的负载均衡以及感知服务上下线能力时,需要用到多实例启动。 那么什么是多实例启动嘞?简单说就是能在本地同时启动多个同一服务。打个比方项目中有一个 MobileApplication 服务&…...

Activiti7流程结束监听事件中,抛出的异常无法被spring全局异常捕捉
ProcessRuntimeEventListener activiti7中,提供了ProcessRuntimeEventListener监听器,用于监听流程实例的结束事件 /*** 流程完成监听器*/ Slf4j Component public class ProcessCompleteListener implements ProcessRuntimeEventListener<ProcessC…...
Android 默认关闭自动旋转屏幕功能
Android 默认关闭自动旋转屏幕功能 接到客户邮件想要默认关闭设备的自动旋转屏幕功能,具体修改参照如下: /vendor/mediatek/proprietary/packages/apps/SettingsProvider/res/values/defaults.xml - <bool name"def_accelerometer_rotati…...

pycharm终端遇不显示虚拟环境的问题
大部分我们用pycharm会配合我们的anaconda来使用,但是配置好后,可能会出现pycharm终端不显示虚拟环境的问题。 首先是确定不显示环境,下图中如果没有这个方框,就是不显示虚拟环境。此时用pip或者conda的命令是会提示不是 “不是内…...
C++ TCP程序增加TLS加密认证
TCP为什么要增加TLS TCP程序添加TLS主要是为了解决TCP协议本身的安全缺陷。TCP作为传输层协议,虽然提供了可靠的数据传输,但它是明文传输,存在几个关键的安全问题: 数据泄露风险:TCP传输的数据完全暴露在网络中,任何能够监听网络流量的人都可以直接读取传输内容。这对于…...
Hive在实际应用中,如何选择合适的JOIN优化策略?
在实际应用中选择Hive JOIN优化策略时,需综合考虑数据规模、分布特征、表结构设计、集群资源及业务需求。以下是具体的决策流程和参考标准: 一、数据特征分析 1. 统计数据规模 通过DESCRIBE FORMATTED table_name查看表大小和分区信息。使用SELECT CO…...

单细胞注释前沿:CASSIA——无参考、可解释、自动化细胞注释的大语言模型
细胞类型注释是单细胞RNA-seq分析的重要步骤,目前有许多注释方法。大多数注释方法都需要计算和特定领域专业知识的结合,而且经常产生不一致的结果,难以解释。大语言模型有可能在减少人工输入和提高准确性的同时扩大可访问性,但现有…...

node入门:安装和npm使用
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、安装npm命令nvm 前言 因为学习vue接触的,一直以为node是和vue绑定的,还以为vue跑起来必须要node,后续发现并不是。 看…...

Apifox 5 月产品更新|数据模型支持查看「引用资源」、调试 AI 接口可实时预览 Markdown、性能优化
Apifox 新版本上线啦! 看看本次版本更新主要涵盖的重点内容,有没有你所关注的功能特性: 自动解析 JSON 参数名和参数值调试 AI 接口时,可预览 Markdown 格式的内容性能优化:新增「实验性功能」选项 使用独立进程执行…...
wsl2 docker重启后没了
参考这篇文章:wsl2 docker重启后没了_mob64ca12f55920的技术博客_51CTO博客...

【计算机网络】第2章:应用层—Web and HTTP
目录 一、Web 与 HTTP 二、总结 (一)Web 的定义与功能 (二)HTTP 协议的定义与功能 (三)HTTP 协议的核心机制 1. HTTP 请求与响应流程 2. HTTP 的连接类型 3. HTTP 的状态码 (四…...

密钥管理系统在存储加密场景中的深度实践:以TDE透明加密守护文件服务器安全
引言:数据泄露阴影下的存储加密革命 在数字化转型的深水区,企业数据资产正面临前所未有的安全挑战。据IBM《2025年数据泄露成本报告》显示,全球单次数据泄露事件平均成本已达465万美元,其中存储介质丢失或被盗导致的损失占比高达…...
arcgis字段计算器中计算矢量面的每个点坐标
python脚本 函数 def ExportCoordinates(feat):coors = []partnum = 0partcount = feat.partCountwhile partnum < partcount:part = feat.getPart(partnum)pnt = part.next()while pnt:coors.append("({}, {})".format(pnt.X,pnt.Y))pnt = part.next()if not p…...