Linux:IO多路转接之epoll

文章目录

  • 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));elselg(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){// demochar 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.hqwc.cn/news/600206.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

顺序统计量

一、顺序统计量 定义&#xff1a;将长度为 n 的数组按升序排序后&#xff0c;第 i 个位置的数字是该数组的第 i 小的量&#xff0c;称之为第 i 顺序统计量。 则一个数组中的最小值是第1顺序统计量&#xff0c;最大值是第n顺序统计量&#xff0c;中位数是第 (n1)/2 顺序统计量 …

VueDraggablePlus 支持 Vue2 和 Vue3 的拖拽组件

官网&#xff1a;https://alfred-skyblue.github.io/vue-draggable-plus/

倒反天罡的ssh后门 | Linux 后门系列

0x00 简介 今天看见有安全研究员发了一篇 ssh 后门的文章&#xff0c;复现思考后分享给大家 https://blog.thc.org/infecting-ssh-public-keys-with-backdoors 0x01 ssh密钥登录 参考 https://www.commandlinux.com/man-page/man5/authorized_keys.5.html 运维人员管理 Linux …

性能优化 - 你能说一说,如果服务端一次性给前端返回1万条数据,前端该如何处理吗

难度级别:中高级及以上 提问概率:65% 在真实工作中,如果遇到服务端一次性返回给前端1万条数据的场景,是非常不应该的。如果服务端可以给前端一次性返回1万条数据的话,那说不准哪次的接口请求数据就会更多。海量的响应数据无疑会使接口响应…

【r-tree算法】一篇文章讲透~

目录 一、引言 二、R-tree算法的基本原理 1 数据结构 2 插入操作 3 删除操作 4 查询操作 5 代码事例 三、R-tree算法的性能分析 1 时间复杂度 2 空间复杂度 3 影响因素 四、R-tree算法的变体和改进 1 R*-tree算法 2 X-tree算法 3 QR-tree算法 五、R-tree算法的…

2024年最新渗透测试工具,看完赶紧存了!(工具包限时分享)

前言&#xff1a; 为了保护网络及国家安全&#xff0c;国家增强了对网络安全人才培养与建设的投入力度。网络安全相关职位的薪资待遇颇为丰厚&#xff0c;相关资格证书的补贴也相当可观&#xff0c;吸引了大批网安爱好者前来学习。但网络安全领域并不缺乏从业者&#xff0c;而…

使用 C++ 和 Eigen 库理解 IMU 数据处理与可视化

使用 C 和 Eigen 库理解 IMU 数据处理与可视化 在本文中&#xff0c;我们将探讨如何使用 C 和 Eigen 库处理和可视化惯性测量单元&#xff08;IMU&#xff09;数据。IMU 数据在各种应用中至关重要&#xff0c;包括机器人技术、导航系统和虚拟现实。我们将探讨如何读取 IMU 数据…

千视携 NDI 6 轻量化媒体方案亮相北京CCBN展会

展会简介 第30届中国国际广播电视网络技术展览会&#xff08;CCBN&#xff09;将于4月24至26日在北京首钢会展中心举行。此次展会将汇集全球各大数字媒体、广播电视单位以及IT、通信技术厂商。展会重点关注数字化转型、智能媒体、融媒体等主题&#xff0c;并展示最新的5G、4K/8…

基于starganvc2的变声器论文原理解读

数据与代码见文末 论文地址&#xff1a;https://arxiv.org/pdf/1907.12279.pdf 1.概述 什么是变声器&#xff0c;变声器就是将语音特征进行转换&#xff0c;而语音内容不改变 那么我们如何构建一个变声器呢&#xff1f; 首先&#xff0c;我们肯定不能为转换的每一种风格的声…

vue项目入门——index.html和App.vue

vue项目中的index.html文件 在Vue项目中&#xff0c;index.html文件通常作为项目的入口文件&#xff0c;它包含了Vue应用程序的基础结构和配置。 该文件的主要作用是引入Vue框架和其他必要的库&#xff0c;以及定义Vue应用程序的启动配置。 import Vue from vue import App …

HBase详解(2)

HBase 结构 HRegion 概述 在HBase中&#xff0c;会从行键方向上对表来进行切分&#xff0c;切分出来的每一个结构称之为是一个HRegion 切分之后&#xff0c;每一个HRegion会交给某一个HRegionServer来进行管理。HRegionServer是HBase的从节点&#xff0c;每一个HRegionServ…

谷歌浏览器插件开发速成指南:弹窗

诸神缄默不语-个人CSDN博文目录 本文介绍谷歌浏览器插件开发的入门教程&#xff0c;阅读完本文后应该就能开发一个简单的“hello world”插件&#xff0c;效果是出现写有“Hello Extensions”的弹窗。 作为系列文章的第一篇&#xff0c;本文还希望读者阅读后能够简要了解在此基…