当前位置: 首页 > news >正文

多路转接之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 它们都需要我们做下面的操作

  1. 让我们维护一个第三方的数组
  2. 都需要遍历整个数组
  3. 都需要经历用户到内核 内核到用户的事件通知

而我们的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为什么高效呢

  1. 因为epoll底层维护的是红黑树结构 对比数组来说增删改查有着天然的优势
  2. 我们不需要主动去询问哪些文件是否就绪 os会自动将其添加到就绪队列中
  3. 在寻找就绪文件的时候 由于我们使用的是就绪队列 时间复杂度是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

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

删除排序链表中的重复节点II(C++解法)

题目 给定一个已排序的链表的头 head &#xff0c; 删除原始链表中所有重复数字的节点&#xff0c;只留下不同的数字 。返回 已排序的链表 。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,3,4,4,5] 输出&#xff1a;[1,2,5]示例 2&#xff1a; 输入&#xff1a;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功能模块 能选上的尽量选上&#xff0c;除非知道自己用不上。 然后确认&#xff0c;下一步&#xff0c;安装。 3.启动服务器 搜索IIS&#xff0c;启动IIS管理器。 启动网站。 右…...

不一样的编程方式 —— 协程(设计原理与汇编实现)

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

Thinkphp6项目在虚拟机无法指向pulic的目录访问的方法

以阿里云虚拟主机为例&#xff0c;服务器环境为 LAMP&#xff0c;Apache2.4 php7.2 mysql5.7 1.根目录新建 index.php 文件&#xff0c;将以下内容放入文件中 <?php include ./public/index.php;2.将 public 目录下的 admin.php、backend 文件夹、static 文件夹、tinymc…...

数据结构(超详细讲解!!)第十八节 串(堆串)

1.定义 假设以一维数组heap &#xff3b;MAXSIZE&#xff3d; 表示可供字符串进行动态分配的存储空间&#xff0c;并设 int start 指向heap 中未分配区域的开始地址(初始化时start 0) 。在程序执行过程中&#xff0c;当生成一个新串时&#xff0c;就从start指示的位置起&#…...

idea集成测试插件替代postman

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

clusterprolifer go kegg msigdbr 富集分析应该使用哪个数据集,GO?KEGG?Hallmark?

关注微信&#xff1a;生信小博士 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-入门

前言&#xff1a;之前的基于单片机的闭环控制步进电机项目其实已经完成了&#xff0c;但很多时间都花在调试和生产上&#xff0c;实在没时间去做总结笔记&#xff0c;现在又开始做新项目了&#xff0c;从单片机到了Linux&#xff0c;想用这个平台来督促自己继续学习&#xff0c…...

怎样更有效的运营Etsy店铺?

大家都知道&#xff0c;Etsy作为一个重要的电商平台&#xff0c;给很多人提供了不少机会。但是如何取得etsy店铺运营的成功呢&#xff1f;第一步就是选好辅助工具。 什么是指纹浏览器&#xff1f; VMLogin指纹浏览器(www.vmlogin.com.cn) 是一种工具&#xff0c;通过伪装用户…...

Vue 项目中如何使用Bootstrap5(简单易懂)

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

k8s 资源预留

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

微信小程序自定义弹窗阻止滑动冒泡catchtouchmove之后弹窗内部内容无法滑动

自定义弹窗 如图所示&#xff1a; 自定义弹窗内部有带滚动条的盒子区域 问题&#xff1a; 在盒子上滑动&#xff0c;页面如果超出一屏的话&#xff0c;也会跟着一起上下滚动 解决方案&#xff1a;给自定义弹窗 添加 catchtouchmove 事件&#xff0c;阻止冒泡即可 网上不少…...

Linux 命令速查

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

第22期 | GPTSecurity周报

GPTSecurity是一个涵盖了前沿学术研究和实践经验分享的社区&#xff0c;集成了生成预训练 Transformer&#xff08;GPT&#xff09;、人工智能生成内容&#xff08;AIGC&#xff09;以及大型语言模型&#xff08;LLM&#xff09;等安全领域应用的知识。在这里&#xff0c;您可以…...

JavaScript前端 console 控制台详细解析与代码实例

JavaScript Console&#xff08;控制台&#xff09;是一个重要的工具&#xff0c;可以用于调试和测试 JavaScript 代码。在浏览器中&#xff0c;你可以使用控制台来查看 JavaScript 输出、测试代码、调试错误等。在本文中&#xff0c;我们将详细介绍控制台的常用功能和代码实例…...

idea中启动多例项目配置

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

Activiti7流程结束监听事件中,抛出的异常无法被spring全局异常捕捉

ProcessRuntimeEventListener activiti7中&#xff0c;提供了ProcessRuntimeEventListener监听器&#xff0c;用于监听流程实例的结束事件 /*** 流程完成监听器*/ Slf4j Component public class ProcessCompleteListener implements ProcessRuntimeEventListener<ProcessC…...

Android 默认关闭自动旋转屏幕功能

Android 默认关闭自动旋转屏幕功能 接到客户邮件想要默认关闭设备的自动旋转屏幕功能&#xff0c;具体修改参照如下&#xff1a; /vendor/mediatek/proprietary/packages/apps/SettingsProvider/res/values/defaults.xml - <bool name"def_accelerometer_rotati…...

智能诊断指南:5步实现浏览器扩展资源嗅探优化

智能诊断指南&#xff1a;5步实现浏览器扩展资源嗅探优化 【免费下载链接】cat-catch 猫抓 浏览器资源嗅探扩展 / cat-catch Browser Resource Sniffing Extension 项目地址: https://gitcode.com/GitHub_Trending/ca/cat-catch 想要轻松捕获在线视频资源却不知从何下手…...

SVR模型可视化对比:RBF、线性、多项式核,哪个对你的数据更有效?(Python+Matplotlib实战)

SVR模型可视化对比&#xff1a;RBF、线性、多项式核&#xff0c;哪个对你的数据更有效&#xff1f;&#xff08;PythonMatplotlib实战&#xff09;当面对一份新的回归数据集时&#xff0c;选择合适的核函数往往成为支持向量回归&#xff08;SVR&#xff09;应用中的关键决策点。…...

RFECV特征选择在勒索软件分类中的实战:API与网络流量特征对比

1. 项目概述&#xff1a;当勒索软件分类遇上RFECV特征选择在网络安全攻防的战场上&#xff0c;勒索软件无疑是最具破坏性和经济威胁的对手之一。它不再仅仅是技术宅的恶作剧&#xff0c;而是演变成了组织化、产业化的犯罪工具&#xff0c;其变种迭代速度之快&#xff0c;让传统…...

模块化AI:从大脑启示到工程实践,构建高效智能系统的核心范式

1. 引言&#xff1a;为什么我们需要重新审视“模块化”&#xff1f;在人工智能领域&#xff0c;我们正处在一个看似矛盾的时代。一方面&#xff0c;以大型语言模型&#xff08;LLM&#xff09;和深度神经网络&#xff08;DNN&#xff09;为代表的“单体巨兽”展现出了前所未有的…...

AI正在重构工程师岗位:被替代的不是“人”,而是低维度能力

过去很多人认为,AI更适合写文案、做客服、生成图片,而真正复杂的工程领域——尤其是工业、制造、自动化系统——依然离不开工程师。 但最近一个劳动仲裁案例,让越来越多工程技术人员开始重新思考这个问题: 一位从事测绘工作15年的工程师,因为企业全面导入AI自动化测绘系…...

微信抓包全链路实战:Proxifier+Fiddler+Burp协同排障指南

1. 为什么微信抓包成了“玄学”&#xff0c;而你总在重装系统&#xff1f; 微信抓包这件事&#xff0c;我干了七年&#xff0c;从2017年用Charles配iOS证书开始&#xff0c;到今天手头常备三套环境&#xff1a;Mac上跑Fiddler EverywhereProxifier组合应对企业微信定制版&…...

5分钟掌握NoFences:告别杂乱桌面的免费桌面整理终极指南

5分钟掌握NoFences&#xff1a;告别杂乱桌面的免费桌面整理终极指南 【免费下载链接】NoFences &#x1f6a7; Open Source Stardock Fences alternative 项目地址: https://gitcode.com/gh_mirrors/no/NoFences 你是否每天都要面对一个布满杂乱图标的Windows桌面&#…...

CANN ops-transformer:MC2 通信融合算子怎么加速 MoE 的 All-to-All

MoE 的 Expert Parallel 需要全互连通信——每个 token 发给它路由到的专家所在的卡&#xff0c;再收回来。这个 All-to-All 通信在 8 卡 MoE 上能占 30% 的推理时间。MC2&#xff08;Merge-Communicate-Split&#xff09;把通信和计算融合在一起&#xff0c;在等数据的时候不闲…...

QLoRA:4-bit 量化微调的完整链路

本文基于昇腾CANN和昇腾NPU&#xff0c;围绕 cann-recipes-train 仓库的相关技术展开。 QLoRA 不是简单的 LoRA 量化。它在 LoRA 的冻结权重上做了 NF4 量化&#xff0c;同时保留了 LoRA 适配器的 FP16 精度。CANN 上部署 QLoRA 模型时&#xff0c;NF4 的反量化要在 NPU 上做&…...

Arm开发中DSTREAM调试探针无法识别的排查指南

1. DSTREAM调试探针在Arm开发环境中不可选的排查指南当使用Arm Development Studio&#xff08;Arm DS&#xff09;进行嵌入式开发时&#xff0c;DSTREAM系列调试探针&#xff08;包括DSTREAM-ST、DSTREAM-PT、DSTREAM-HT和DSTREAM-XT&#xff09;偶尔会出现无法在开发环境中被…...