Linux:IO多路转接之epoll

news2025/2/7 20:45:34

文章目录

  • epoll历史
  • epoll的接口
    • epoll_create
    • epoll_wait
    • epoll_ctl
  • epoll原理
  • 代码实验

前面的内容介绍了select多路转接,也分析了其利弊,后面用poll改良了select,解决了部分的缺点,但是对于一些核心的缺点还是不能保证,比如说遍历等,于是有了epoll,epoll是目前来说在多路转接版块使用最广泛,最好用的一种多路转接,所以本篇我们就重点分析一下epoll

epoll历史

epoll是后面才发展出来的,在最开始进行内核设计的时候,其实没有增加像epoll这样的内容,但是在后面的不断更新学习中,不断地对于效率有了更加高的要求,所以就诞生出了epoll这样的多路转接方案,而这个方案实际上和前面的select还有poll已经完全不一样了,因此下面对于epoll进行介绍

epoll的接口

epoll_create

在这里插入图片描述
这个接口就是创建一个epoll模型,并且会返回的也是一个文件描述符,具体的原理后面会谈

epoll_wait

在这里插入图片描述
这个接口是用来拿取数据的,第一个参数epfd就是前面创建模型时的文件返回值,通过这个文件描述符来找到这个epoll模型,后面的两个参数主要是用来返回的是已经就绪的fd和event,而最后一个参数则是超时时间,这里也不多赘述,和前面一样,具体是如何拿取的,后面会结合原理继续谈

在这当中存在一个epoll_event的结构体,看一下这个结构体的内容:

在这里插入图片描述
首先在这个结构体中包含一个32位的位图events,这个当中的内容是用来传递标记位的,第二个参数是一个新的结构体,而这个新的结构体本质上是联合,说明可以选择使用上面的任意一个字段来保持,比如说可以是用户级的数据,或者是一个事件等等,这里结合原理再说

epoll_ctl

在这里插入图片描述
这个函数是用来对于epoll模型做出对应的修改的,第一个参数是创建epoll模型的返回值

第二个参数是op操作,可以对于模型进行增删改,具体的选项如下

在这里插入图片描述
第三个参数fd是对于特定的某个fd指向的文件,而第四个参数表示的是某个特定的事件

epoll原理

先画出下面的这个原理图

在这里插入图片描述
对于底层的硬件设备来说,我们这里只关心网卡,在这个网卡上面会存在一个网卡驱动,然后是操作系统和各种的系统调用的接口,而在我们之前进行select和poll的时候,对于遍历的这个操作,本质上就是去查看这个文件描述符对应的某个资源和数据是否已经就绪了,如果没有就绪就让继续进行阻塞等待,此时这个进程就会被挂起到等待队列当中,而在底层操作系统进行定期的唤醒和调度的时候,轮到这个进程了,那么就继续对于这个内容进行检测对应的文件描述符,这就意味着,操作系统需要主动的去检查这个内容到底有没有就绪,相当于是把操作系统绑定在了这里

而在后来,系统的这种模式就发生了一定的变化

在这里插入图片描述

首先我们要明确的概念是,对于网卡来说,它就是一个外设,从纯硬件的角度来讲,对应的数据会先进入数据链路层,然后从数据链路层再一系列的向上交付,然后再进行解包等等,那在硬件层面上,操作系统会通过中断来知道资源有没有就绪,然后资源就被操作系统读取上去了

所以操作系统为了便于进行检测,就在内部维护了一颗红黑树,而在这个红黑树的节点当中,第一个就是对应的需要进行多路转接的文件描述符,其次是需要关心的事件,比如说有读事件,写事件,异常事件这些事件,整体上是采用了一个位图来进行描述表示,还有就是一些连接字段

其次,操作系统还会维护一个就绪队列,在这个就绪队列当中维护的节点其实和红黑树维护的节点基本相同,可以理解为这个节点既在红黑树当中,也在就绪队列当中,在就绪队列当中的节点必然是这个事件已经就绪了,那么可以理解为就是在红黑树当中有一个文件描述符3,并且这个3号文件关心的是写事件,而当这个写事件就绪的时候,就把这个节点从红黑树中转移到就绪队列当中,那么就有了新的节点入队列,后续就可以取出来了

最后还有一种机制,那就是在操作系统中会设置一些回调函数,这些回调函数是做什么的呢?比如当网卡用中断的方式把数据转移到了网卡驱动层,那么一旦网卡驱动层有数据了,那么这个数据链路层就会自动调用对应的callback回调,可以理解为是要进行向上交付,其次还可以理解为是数据来了,要进行解包,然后把这个解包的数据放到TCP的文件缓冲区当中让它进行解析等等,它还可以查找红黑树的一些信息,比如说用文件描述符作为键值进行关联,借助红黑树就可以查找到所关心的事件有没有就绪等等,如果查到了,就说明这事件已经就绪了,那么就构建节点插入到就绪队列当中

所以在操作系统当中,在使用epoll的时候,它会使用一些回调函数到底层,底层的资源就绪的时候,就进行硬件中断,然后交付给操作系统,然后就把数据放在了就绪队列当中,之后对于用户来说直接从就绪队列当中去拿就可以了

那在操作系统中是如何管理这个epoll模型的?从create接口就能看出来,本质上其实是用一个文件来进行管理的,所以下面的话题就是,对照着这个内容,再次看epoll的接口

在这里插入图片描述

代码实验

下面就用上面的原理,实现一份简单的epoll代码

首先在epoll的类中要包含有对应的一个epollfd和对应的timeout,还有对应的三个接口,创建epoll模型,进行epoll模型中获取消息队列的内容,修改epoll模型当中的字段,也就是修改红黑树:

class Epoller
{
public:
    Epoller()
    {}
    ~Epoller()
    {}
    int EpollerWait(struct epoll_event* revents, int num)
    {}
    int EpollerUpdate(int op, int sock, uint32_t enent)
    {}
private:
    int _epfd;
    int _timeout;
};

如上就搭建出了一个比较基础版本的epoll,那么下面对于这当中的字段进行填充

#pragma once

#include "Log.hpp"
#include <cerrno>
#include <cstring>
#include <sys/epoll.h>

class Epoller
{
    static const int size = 128;
public:
    Epoller()
    {
        _epfd = epoll_create(size);
        if(_epfd == -1)
            lg(Error, "epoll create error : %s", strerror(errno));
        else
            lg(Info, "epoll create success : %d", _epfd);
    }
    // 获取指定fd下的revents字段,并返回有多少是就绪的
    int EpollerWait(struct epoll_event* revents, int num)
    {
        int n = epoll_wait(_epfd, revents, num, -1);
        return n;
    }
    // 更新指定sock下的事件
    int EpollerUpdate(int op, int sock, uint32_t event)
    {
        int n = 0;
        // 如果是删除操作,就置空即可
        if(op == EPOLL_CTL_DEL)
        {
            n = epoll_ctl(_epfd, op, sock, nullptr);
            if(n)
                lg(Error, "epoll delete error");
        }
        // 如果是其他的操作,需要填写对应的字段
        else
        {
            struct epoll_event ev;
            ev.events = event;
            ev.data.fd = sock;
            n = epoll_ctl(_epfd, op, sock, &ev);
            if(n)
                lg(Error, "epoll ctl error");
        }
        return n;
    }
    ~Epoller()
    {
        if(_epfd > 0)
            close(_epfd);
    }
private:
    int _epfd;
    int _timeout; // 没有使用这个字段
};

有了对于上述内容的封装,就可以填写对于epoll服务器的内容了,主要包括初始化,把listensock填写到epoll当中,然后再对其进行一系列操作即可

#pragma once

#include <iostream>
#include <memory>
#include <sys/epoll.h>
#include "Socket.hpp"
#include "Log.hpp"
#include "Epoller.hpp"
using namespace std;

class EpollServer
{
public:
    static const int num = 64;
    EpollServer(uint16_t port)
        : _port(port), _listensocket_ptr(new Sock()), _epoller_ptr(new Epoller())
    {
    }
    void Init()
    {
        _listensocket_ptr->Socket();
        _listensocket_ptr->Bind(_port);
        _listensocket_ptr->Listen();

        lg(Info, "create socket success: %d", _listensocket_ptr->Fd());
    }
    // 处理链接
    void Accepter()
    {
        // 当获取了新链接之后,就准备进行读取的事件等待了
        string clientip;
        uint16_t clientport;
        int sock = _listensocket_ptr->Accept(&clientip, &clientport);
        if (sock > 0)
        {
            // 如果想要读取,就继续加入到epoll的事件中进行等待,当就绪后会提醒的
            _epoller_ptr->EpollerUpdate(EPOLL_CTL_ADD, sock, EPOLLIN);
            lg(Info, "get a new link, sock is %d", sock);
        }
    }
    // 对于接受到可以读取的事件提醒后,就可以直接进行读取了
    void Recver(int fd)
    {
        // demo
        char buffer[1024];
        ssize_t n = read(fd, buffer, sizeof(buffer) - 1); // bug?
        if (n > 0)
        {
            buffer[n] = 0;
            cout << "get a messge: " << buffer << endl;
            string echo_str = "server echo $ ";
            echo_str += buffer;
            write(fd, echo_str.c_str(), echo_str.size());
        }
        else if (n == 0)
        {
            lg(Info, "client quit, me too, close fd is : %d", fd);
            _epoller_ptr->EpollerUpdate(EPOLL_CTL_DEL, fd, 0);
            close(fd);
        }
        else
        {
            lg(Warning, "recv error: fd is : %d", fd);
            _epoller_ptr->EpollerUpdate(EPOLL_CTL_DEL, fd, 0);
            close(fd);
        }
    }
    void Dispatcher(struct epoll_event *revs, int num)
    {
        // 和select一样,遍历fd,然后区分是listen的请求还是read的请求
        for (int i = 0; i < num; i++)
        {
            uint32_t events = revs[i].events;
            int fd = revs[i].data.fd;
            // 我们这里只关心读事件
            if (events & EPOLLIN)
            {
                // 如果是listensocket的事件,就调用listensocket的处理方式
                if (fd == _listensocket_ptr->Fd())
                {
                    Accepter();
                }
                // 如果是读事件,就调用读事件对应的处理方式
                else
                {
                    Recver(fd);
                }
            }
            // 其他的事件暂时不关心
            else
            {
            }
        }
    }
    void Start()
    {
        // 把listensocket添加到epoll当中
        _epoller_ptr->EpollerUpdate(EPOLL_CTL_ADD, _listensocket_ptr->Fd(), EPOLLIN);
        struct epoll_event revs[num];
        for (;;)
        {
            // 等待epoll返回就绪的信息
            int n = _epoller_ptr->EpollerWait(revs, num);
            // 如果有事件就绪了就去处理
            if (n > 0)
            {
                lg(Debug, "event happened, fd is : %d", revs[0].data.fd);
                Dispatcher(revs, n);
            }
            else if (n == 0)
            {
                lg(Info, "time out");
            }
            else
            {
                lg(Error, "epoll wait error");
            }
        }
    }
    ~EpollServer()
    {
        _listensocket_ptr->Close();
    }

private:
    shared_ptr<Sock> _listensocket_ptr;
    shared_ptr<Epoller> _epoller_ptr;
    uint16_t _port;
};

经过测试这是可行的:

在这里插入图片描述
至此就完成了一个比较基础的epoll实现策略

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

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

相关文章

Langchain教程 | langchain+OpenAI+PostgreSQL(PGVector) 实现全链路教程,简单易懂入门

前提&#xff1a; 在阅读本文前&#xff0c;建议要有一定的langchain基础&#xff0c;以及langchain中document loader和text spliter有相关的认知&#xff0c;不然会比较难理解文本内容。 如果是没有任何基础的同学建议看下这个专栏&#xff1a;人工智能 | 大模型 | 实战与教程…

品牌定位升级|飞雕开关如何从家庭作坊走上国际之路?

飞雕电器,这个名字在中国开关插座行业中如同一面旗帜,自1987年起就扬帆在电工领域的大海中。它不仅见证了这个行业的起起伏伏,还始终以其创新的姿态站在浪尖之上。 飞雕的产品线丰富多彩,除主营的墙壁开关插座领域外,飞雕电器还涉足了与墙壁开关紧密相关的其它领域,现已推出移…

vmware 中的Ubuntu系统虚拟机忘记root密码强制重置操作

忘记密码情况下&#xff0c;vmware虚拟机重置Ubuntu的root密码 在企业使用的vmware ESXI中重置Ubuntu系统root密码 1-本地电脑安装个人版的vmware workstation&#xff0c;目的&#xff1a;vmware ESXI自带的远程控制台无法输入指定的键盘按键&#xff0c;需要借助外部的远程辅…

Ceph学习 -3.存储简介

文章目录 1.存储简介1.1 存储类型1.1.1 储备知识1.1.2 三种存储1.1.3 块存储1.1.4 文件存储1.1.5 对象存储1.1.6 三种存储之间的关系1.1.7 总结 1.2 Ceph简介1.2.1 官方介绍1.2.2 软件特点1.2.3 基本结构1.2.4 应用场景 1.3 小结 1.存储简介 学习目标&#xff1a;这一节&#x…

免疫检查点信号转导和癌症免疫治疗(文献)

目录 基础 介绍 免疫检查点的表面调控&#xff08;细胞膜层面&#xff09; ​编辑 PD-1调节 PD-L1调节 CTLA-4 调节 检查点信号通路 关于靶点研究 展望 Immune checkpoint signaling and cancer immunotherapy - PubMed (nih.gov) 基础 【中英字幕】肿瘤免疫疗法之免…

docker安装nacos,单例模式(standalone),使用mysql数据库

文章目录 前言安装创建文件夹"假装"安装一下nacos拷贝文件夹删除“假装”安装的nacos容器生成nacos所需的mysql表获取mysql-schema.sql文件创建一个mysql的schema 重新生成新的nacos容器 制作docker-compose.yaml文件查看网站 前言 此处有本人写得简易版本安装&…

【GameFi】 Brilliantcrypto点火活动

活动&#xff1a;https://app.galxe.com/quest/brilliantcrypto/GCt8wthq2J Brilliantcrypto点火活动正在Galxe上进行 &#x1f389; 活动时间&#xff1a;2024/04/06 12:00 ~ 2024/05/04 12:00 奖励总价值1200美元的MATIC 完成任务並在Brilliantcrypto Galxe Space上赚取积分。…

《科技创业月刊》是什么级别的期刊?是正规期刊吗?能评职称吗?

问题解答&#xff1a;问&#xff1a;《科技创业月刊》是什么级别的刊物&#xff1f; 答&#xff1a;省级&#xff0c;主管单位&#xff1a; 湖北省科学技术厅 &#xff1b;主办单位&#xff1a;湖北省科技信息研究院 问&#xff1a;《科技创业月刊》是c刊吗&#xff1f; 答&…

JUC:手写实现一个简易的线程池(Java)

目录 ​编辑 先上完整代码&#xff1a; 解析&#xff1a; 任务队列&#xff1a; 线程池类&#xff1a; 拒绝策略&#xff1a; 先上完整代码&#xff1a; public class MyThreadPool {public static void main(String[] args) {ThreadPool threadPool new ThreadPool(2, …

基于javassm实现的旅游景点线路网站

开发语言&#xff1a;Java 框架&#xff1a;ssm 技术&#xff1a;JSP JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.3.…

Q1公测手游复盘:大作很多,爆款没有

2024年第一季度过去&#xff0c;整个国内手游市场爆冷连连。 据眸娱不完全统计&#xff0c;一季度累计上新22部颇具影响力的手游&#xff08;买断制手游仅留鹰角网络《来自星尘》作为代表&#xff09;。许多备受期待的大作在一季度抢先完成了上线或测试&#xff0c;却在市场端…

IDEA中的Debug功能介绍

说明&#xff1a;本文介绍IDEA中的Debug功能&#xff0c;基于2023.2&#xff08;Ultimate Edition&#xff09;版本 简单介绍 首先&#xff0c;在程序需要停止的所在行号上&#xff0c;鼠标左键&#xff0c;可设置一个断点&#xff0c;是一个红色圆点标志&#xff0c;表示程序…

python入门到精通

本文基于《python3从入门到精通》进行编写 python是什么 是一种简单易学的计算机编程语言&#xff0c;有配套的软件工具和库。 是一种开源的语言。因其有许多强大的开源库使得python对与计算、大数据、人工智能都有很强的支持能力。 是一种解释型语言。其代码不需要编译就可…

基于 OpenHarmony ActiveOhos_sqllite 组件开发指南

1. ActiveOhos 功能介绍 1.1.组件介绍 基于鸿蒙系统连接数据库进行 sqlite 数据库操作的时候&#xff0c;创建连接的时候是有些繁琐的&#xff0c;本组件就是为了简化 sqlite 的连接&#xff0c;并且对鸿蒙原生的 API 进行封装加强&#xff0c;使得读写 sqlite 数据库的时候更…

Java8新特性 (jdk1.8)

目录 一、Lamdba表达式&#xff1f; 二、函数式接口 三、方法引用和构造引用 四、Stream API流 五、接口中的新增 默认方法和静态方法 六、新时间日期API 七、Optional 八、其他特性 一、Lamdba表达式&#xff1f; 为什么使用Lambda表达式&#xff1f; Lambda 是一个 匿…

[挖坟]如何安装Shizuku和LSPatch并安装模块(不需要Root,非Magisk)

2023年12月13日&#xff0c;LSPatch 停止维护 2024年1月8日&#xff0c;LSPosed 停止维护 2024年1月8日&#xff0c;ZygiskNext 停止维护 2024年1月9日&#xff0c;KernelSU 停止维护 这里使用 ColorOS 14 演示&#xff0c;其他品牌手机类似 安装 Shizuku 官网: https://shiz…

JQuery(二)---【使用JQuery对HTML、CSS进行操作】

零.前言 JQuery(一)---【JQuery简介、安装、初步使用、各种事件】-CSDN博客 一.使用JQuery对HTML操作 1.1获取元素内容、属性 使用JQ可以操作元素的“内容” text()&#xff1a;设置或返回元素的文本内容html()&#xff1a;设置或返回元素的内容(包括HTML标记)val()&#…

每天一个注解之@DataSource、 @DS

在Java中&#xff0c;DataSource 注解通常用于标记数据源&#xff08;DataSource&#xff09;相关的信息。数据源是一个用于获取数据库连接的对象&#xff0c;它通常用于与数据库进行交互。DataSource 注解的详细说明可能会因不同的框架或库而有所不同&#xff0c;但通常用于配…

2024-04-07 作业

作业要求&#xff1a; 1> 思维导图 2> 自由发挥应用场景实现一个登录窗口界面。 【可以是QQ登录界面、也可以是自己发挥的登录界面】 要求&#xff1a;尽量每行代码都有注释 作业1&#xff1a; 作业2&#xff1a; 运行代码&#xff1a; #include "myqwidget.h&quo…

部署安装ElasticSearch、Kibana、IK

文章目录 1、部署单点es1.1、创建网络1.2、加载镜像1.3、运行 2、部署kibana2.1、部署2.2、DevTools 3、IK分词器3.1、在线安装3.2、离线安装1&#xff09;查看数据卷目录2&#xff09;解压缩分词器安装包3&#xff09;上传到es容器的插件数据卷中4&#xff09;重启容器5&#…