IO多路转接之epoll

目录

一. epoll的实现原理

二.  epoll的相关接口

2.1 epoll_create -- 创建epoll模型

2.2 epoll_ctl -- 对epoll模型进行控制

2.3 epoll_wait -- 等待epoll所关注的事件就绪

2.4 epoll相关接口的使用方法

三. Epoll服务器的模拟实现

3.1 EpollServer类的声明

3.2 EpollServer类的实现

四. 总结


一. epoll的实现原理

在通过select和poll实现IO多路转接时,都需要程序员来维护一个数组,用来随时控制要被关心的事件(fd)。同时,使用select和poll实现IO多路转接,都需要对用户维护的数组进行遍历,遍历操作的时间复杂度为O(N),这会很大程度上消耗计算资源,降低效率。

为了解决poll和select的这些缺陷,epoll被提了出来,相比于select和poll,epoll在有事件就绪时,不需要逐个遍历检查每个被关注的文件描述符是否就绪,希望被关注的事件不需要程序员自己维护数组来控制,这提高了效率,降低了程序员的代码编写成本。

在使用epoll实现IO多路转接之前,必须要先在OS内核中建立epoll模型。如图1.1所示,epoll模型主要有两部分组成:

  • 一颗红黑树:用来维护用户所关注的事件(fd),一个红黑树节点对应一个被关注的事件,节点中要记录包括文件描述符,所关注事件的类型(读/写/异常)等。
  • 就绪队列:当有事件就绪的时候,会将已经就绪的事件添加到就绪队列中去,从队头拿走就绪事件及其相关的属性信息交给用户层,就能对已经就绪的事件进行响应。

相比于直接遍历数组查找某个节点O(N)的时间复杂度,使用红黑树查找的时间复杂度为O(logN),这样就提高了OS内核管理事件的效率。同时,通过就绪队列维护已经就绪的事件,避免了在wait成功之后在遍历数组的过程中确定具体是哪个事件就绪,这进一步降低了资源的消耗,提高效率。

图1.1 epoll模型

二.  epoll的相关接口

2.1 epoll_create -- 创建epoll模型

函数原型:int epoll_create(size_t size)

头文件:#include <sys/epoll.h>

函数参数:在Linux 2.6.8版本之后参数size就已经被弃用,这里是为了向前兼容,在调用接口时只需要传一个大于0的参数即可。

返回值:如果创建成功,返回新创建的epoll模型的文件描述符epfd,如果创建失败返回-1。

epoll_create函数所执行的工作,就是在操作系统内核中,创建一个如图1.1所示的epoll模型,即:一颗红黑树和一个就绪队列。epoll_create接口返回值表示被创建的epoll模型的对应fd值,可见OS是将epoll模型当做文件来处理的,这符合Linux下一切接文件的观点。

2.2 epoll_ctl -- 对epoll模型进行控制

函数原型:int epoll_create(int epfd, int op, int fd, struct epoll_event *event)

头文件:#include <sys/epoll.h>

函数参数:

  • epfd -- 所要进行操作的epoll模型对应的文件描述符。
  • op -- 用于指定所要进行的操作。
  • fd -- 用于对epoll进行操作的文件描述符,如指明要添加关注的事件。
  • event -- 输入型参数,告知OS要关注的事件的属性信息。

返回值:如果函数执行成功返回0,失败返回-1。

在该接口函数的参数中,epfd为通过epoll_create创建epoll模型获取的文件描述符,op用于指定所进行的操作的类型,表2.1为op的可选值及其对应的意义。

表2.1 op的可选值及其意义
op意义
EPOLL_CTL_ADD将指定事件添加到epoll模型中进行关注
EPOLL_CTL_DEL删除epoll模型中对某个事件的关注
EPOLL_CTL_MOD改变epoll模型中某个事件被关注的状态(event)

fd用于指定对epoll进行操作的文件描述符,可以为listen文件描述符,也可为普通文件描述符,假设op传EPOLL_CTL_ADD,那么所epoll_ctl所进行的操作就是将fd加入到epoll模型中进行关注。

struct epoll_event 类型数据定义如下,该类型包含两个成员,其中一个为uint32_t类型成员events,用于控制该事件的属性是可读、可写还是异常等。events可以为表示4.2中宏的集合,还有一个为联合自定义类型数据,其中该联合类型可以传四种不同的数据表达不同的意义,但一般使用fd来指定文件描述符。

struct epoll_event类型的定义: 

typedef union epoll_data 
{void      *ptr;int        fd;uint32_t   u32;uint64_t   u64;
} epoll_data_t;struct epoll_event 
{uint32_t   events;   /* Epoll events */epoll_data_t data;   /* User data variable */
};
表4.2 events的可选宏及对应含义
events可选宏含义
EPOLLIN对应文件描述符可读
EPOLLOUT对应文件描述符可写
EPOLLPRI对应文件描述符带有紧急数据可读(TCP紧急指针置1的报文)
EPOLLERR对应文件描述符异常
EPOLLHUP对应文件描述符被挂起
EPOLLSHOT对应文件描述符只被监视一次,监视完一次后会移出epoll模型

2.3 epoll_wait -- 等待epoll所关注的事件就绪

函数原型:int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)

头文件:#include <sys/epoll.h>

函数参数:

  • epfd -- 进行等待的epoll模型文件描述符。
  • events -- 输出型参数,用于存放已经就绪了的事件相关的属性信息,如文件描述符fd以及就绪事件的类型(可读/可写/...)等。
  • maxevents -- 一次wait所能获取到的最大的就绪事件数量。
  • timeout -- 最长阻塞等待时间,传-1表示一直阻塞等待,传0表示完全非阻塞。

返回值:如果等待成功,返回已经就绪的事件的数量,返回0表示在设定的阻塞时间内没有事件就绪,返回-1表示等待失败。

相对于select和poll在等待成功后需要遍历整个数组来确定具体哪一个文件描述符就绪,epoll_wait等待到的就绪事件相关属性信息会被放置在events所指向的空间的前n个位置,其中n为就绪事件数量,即epoll_wait的返回值。因此,只需要遍历events[0] ~ events[n-1]即可,events[0] ~ events[n-1]中记录的事件一定是已经就绪了的

如果已经就绪了的事件多于maxevents会发生什么情况呢?这时会先拿取maxevents个已经就绪的事件,剩余的等到下一轮epoll_wait再进行提取处理,并不会造成任何错误。

2.4 epoll相关接口的使用方法

通过epoll实现IO多路转接,需要按照以下三步操作执行:

  1. 通过epoll_create创建epoll模型。
  2. 通过epoll_ctl添加对特定事件的关注。
  3. 通过epoll_wait等待所关注的事件的一个或多个就绪。

为了方便调用epoll相关的接口函数,代码2.1将epoll的3个相关接口函数进行了封装。在代码2.1中包含了log.hpp头文件,里面是日志打印函数的声明和实现,详见代码2.2。

代码2.1:对epoll接口的封装(Epoll.hpp头文件)

#pragma once#include "log.hpp"
#include <cstring>
#include <cerrno>
#include <sys/epoll.h>namespace Epoll
{static const int default_size = 1024;// 创建Epoll模型函数static int EpollCreate(int size = default_size){int epfd = epoll_create(size);if(epfd < 0)logMessage(FATAL, "Epoll create fail, %d:%s\n", errno, strerror(errno));elselogMessage(NORMAL, "Epoll create success, epfd:%d\n", epfd);return epfd;}// 进行Epoll控制函数static int EpollCtl(int epfd, int op, int fd, uint32_t events){struct epoll_event event;event.events = EPOLLIN;event.data.fd = fd;int ret = epoll_ctl(epfd, op, fd, &event);if(ret < 0) logMessage(ERROR, "Epoll control fail, %d:%s\n", errno, strerror(errno));else logMessage(NORMAL, "Epoll control success, ret:%d\n", ret);return ret;}// Epoll等待函数static int EpollWait(int epfd, struct epoll_event* events, int maxevents, int timeout){return epoll_wait(epfd, events, maxevents, timeout);}
}

代码2.2:日志打印函数的声明和实现(log.hpp头文件)

#pragma once
#include <iostream>
#include <cstdio>
#include <cstdarg>
#include <ctime>#define DEBUG  0
#define NORMAL 1
#define WARING 2
#define ERROR  3
#define FATAL  4static const char* g_levelMap[5] = 
{"DEBUG","NORMAL","WARING","ERROR","FATAL"
};static void logMessage(int level, const char *format, ...)
{// 1. 输出常规部分time_t timeStamp = time(nullptr);struct tm *localTime = localtime(&timeStamp);printf("[%s]  %d-%d-%d, %02d:%02d:%02d\n", g_levelMap[level], localTime->tm_year, localTime->tm_mon, \localTime->tm_mday, localTime->tm_hour, localTime->tm_min, localTime->tm_sec);// 2. 输出用户自定义部分va_list args;va_start(args, format);vprintf(format, args);va_end(args);
}

三. Epoll服务器的模拟实现

3.1 EpollServer类的声明

在EpollServer中,要包含以下成员变量:epoll模型文件描述符、listen套接字、端口号、ip地址以及指向存放就绪事件相关属性信息的空间的指针。

要有以下成员函数:构造函数和析构函数、服务器运行函数Start、就绪事件处理函数Handler、接收客户端连接请求函数Accepter、对端数据读取函数Reciever。

#pragma once#include "Sock.hpp"
#include "Epoll.hpp"
#include <unistd.h>static const int maxevents = 64;class EpollServer
{
public:EpollServer(uint16_t port = 8080, const std::string& ip = "");   // 构造函数void Start();    // 启动运行函数~EpollServer();  // 析构函数private:void Handler(int n);            // 就绪事件处理函数void Accepter(int listenSock);  // 连接接收函数void Reciever(int fd);          // 信息读取函数int _listenSock;   // 监听套接字int _epfd;         // epoll套接字uint16_t _port;    // 服务进程端口号std::string _ip;   // 服务器ip struct epoll_event* _ptr_events;   // 指向就绪队列的指针
};

3.2 EpollServer类的实现

  • 构造函数:首先要进行关于tcp通信的常规准备工作,获取listen套接字、绑定端口号、设置监听状态,之后要创建epoll模型、开辟一块内存空间用于存放wait到的就绪事件并将listen套接字添加到epoll模型中去。
  • 析构函数:判断listen套接字和epoll模型文件描述符是否>=0,如果是,就close掉它们。再判断_ptr_events是否为nullptr,如果否,要delete[]释放动态申请的内存空间。
  • 服务器运行函数Start:常驻进程,执行while死循环,每层while循环都调用epoll_wait检测事件就绪的情况,如果epoll_wait返回值大于0,那么调用Handler函数处理就绪事件。
  • 就绪事件处理函数Handler:接收一个参数n表示已就绪事件的数量,遍历_ptr_events[0] ~ ptr_events[n-1],根据就绪的是listen文件描述符还是普通文件描述符,分类进行后续处理,listen文件描述符调用Accepter函数接收对端连接,普通文件描述符调用Reciever函数接收数据。
  • 接收对端连接函数Accepter:获取对端连接,分配文件描述符fd,并将fd添加到epoll模型中去。
  • 数据读取函数Reciever:调用read函数读取客户端发送的数据,如果read返回值>0,那么就执行对应操作处理读取到的数据,如果read返回值为0,那么表示客户端关闭,要将对应的文件描述符fd从epoll模型中删除。

代码3.2:EpollServer的实现(EpollServer.cc源文件)

#include "EpollServer.hpp"EpollServer::EpollServer(uint16_t port, const std::string& ip): _listenSock(-1), _epfd(-1), _port(port), _ip(ip), _ptr_events(nullptr)
{// 1.获取监听套接字_listenSock = Sock::Socket();if(_listenSock < 0) exit(1);// 2.绑定端口号if(Sock::Bind(_listenSock, _ip, _port) < 0) exit(2);// 3.设置监听状态if(Sock::Listen(_listenSock) < 0) exit(3);// 4.创建epoll模型_epfd = Epoll::EpollCreate();if(_epfd < 0) exit(4);// 5.为_events开辟内存空间并进行初始化_ptr_events = new epoll_event[maxevents];// 6.将listenSock添加到epoll模型中if(Epoll::EpollCtl(_epfd, EPOLL_CTL_ADD, _listenSock, EPOLLIN) < 0) exit(5);logMessage(NORMAL, "EpollServer init success!\n");
}// Epoll服务器启动运行函数
void EpollServer::Start()
{while(1){// 对epoll进行等待int n = Epoll::EpollWait(_epfd, _ptr_events, maxevents, -1);switch(n){case 0:logMessage(DEBUG, "epoll wait time out!\n");break;case -1:logMessage(ERROR, "epoll wait error, %d:%s\n", errno, strerror(errno));break;default:Handler(n);break;}}
}// 析构函数
EpollServer::~EpollServer()
{if(_listenSock >= 0) close(_listenSock);if(_epfd >= 0) close(_epfd);if(_ptr_events) delete[] _ptr_events;
}// 就绪事件处理函数
void EpollServer::Handler(int n)
{// 遍历_events,查找已经就绪的事件for(int i = 0; i < n; ++i){// 分listen套接字和普通套接字两种情况讨论if(_ptr_events[i].data.fd == _listenSock) Accepter(_listenSock);else Reciever(_ptr_events[i].data.fd);}
}// 连接接收函数
void EpollServer::Accepter(int listenSock)
{std::string cli_ip;   // 发起连接的客户端ipuint16_t cli_port;    // 客户端端口号int fd = Sock::Accept(listenSock, cli_ip, cli_port);if(fd < 0) return;// 将新增的fd添加到epoll模型中去Epoll::EpollCtl(_epfd, EPOLL_CTL_ADD, fd, EPOLLIN);logMessage(NORMAL, "Add a new fd to epoll success, fd:%d\n", fd);
}// 信息读取函数
void EpollServer::Reciever(int fd)
{char buffer[1024];ssize_t n = read(fd, buffer, 1023);// 如果读取成功if(n > 0){buffer[n - 1] = '\0';printf("Client# %s\n", buffer);}else if(n == 0){// 对端关闭,将fd从epoll模型中拿走Epoll::EpollCtl(_epfd, EPOLL_CTL_DEL, fd, EPOLLIN);if(n == 0){// logMessage(NORMAL, "Remove fd from epoll success, fd:%d\n", fd);printf("Remove fd from epoll success, fd:%d\n", fd);close(fd);}}else  // 读取数据失败{logMessage(ERROR, "Read message fail, %d:%s\n", errno, strerror(errno));}
}

四. 总结

  • 相比于通过select和poll实现IO多路转接,epoll不需要程序员维护数组来控制关注的事件,在有事件就绪后也不需要遍历数组查找具体哪个事件就绪,效率较高。
  • epoll底层维护一颗红黑树存储要关心的事件,维护一个就绪队列表示已经就绪的事件。
  • epoll实现IO多路转接,要进行的操作为:epoll_create创建epoll模型 -> epoll_ctl向epoll模型中添加受到关注的文件描述符 -> epoll_wait等待事件就绪。

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

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

相关文章

网工内推 | 美的、得力集团,包吃包住,IE认证优先,14薪

01 美的 招聘岗位&#xff1a;网络工程师 职责描述&#xff1a; 1.负责IT网络设备、IDC机房的日常维护巡检、监控和管理&#xff1b; 2.负责路由、交换、防火墙、无线控制器、AP等网络设备的开通、调整、优化升级&#xff1b; 3.负责公司OT、IT网络规划&#xff0c;项目实施以…

路由VRRP配置例子

拓朴如下&#xff1a; 主要配置如下&#xff1a; [R1] interface GigabitEthernet0/0/0ip address 10.1.1.1 255.255.255.0 vrrp vrid 1 virtual-ip 10.1.1.254vrrp vrid 1 priority 200vrrp vrid 1 preempt-mode timer delay 20 # interface GigabitEthernet0/0/1ip address …

【10套模拟】【10】

关键字&#xff1a; 线性探测次数、冒泡交换性质、排序次数最值、bst查找关键字最多比较次数、m叉树空指针域 链表合并、二叉排序树查找x、堆排序

css给盒子写四个角

如图&#xff1a;之前一直用定位 现在发现可以用css写 background: linear-gradient(to top, #306eef, #306eef) left top no-repeat, /*上左*/ linear-gradient(to right, #306eef, #386eef) left top no-repeat, /*左上*/ linear-gradient(to left, #386eef, #306eef) righ…

python opencv -模板匹配

python opencv -模板匹配 模板匹配就是&#xff0c;我们现有一个模板和一个图片&#xff0c;然后&#xff0c;在这个图片中寻找和模板近似的部分。 在opencv 中主要通过cv2.matchTemplate这个函数去实现。 下面我们先看一下&#xff0c;模板图片和需要匹配的图片&#xff1a…

(Matalb时序预测)GA-BP遗传算法优化BP神经网络的多维时序回归预测

目录 一、程序及算法内容介绍&#xff1a; 基本内容&#xff1a; 亮点与优势&#xff1a; 二、实际运行效果&#xff1a; 三、部分代码 四、本文代码数据说明手册分享&#xff1a; 一、程序及算法内容介绍&#xff1a; 基本内容&#xff1a; 本代码基于Matalb平台编译&am…

Spring IOC 和 AOP

Spring IOC 什么是 IoC ? IoC &#xff08;Inversion of Control 控制反转&#xff09;是一种设计思想&#xff0c;而不是一个具体的技术实现。IoC 的思想就是将原本在程序中手动创建对象的控制权&#xff0c;交由给 Spring 框架来管理。 为什么叫控制反转&#xff1f; 控制…

unsigned详讲(干货满满)

前言&#xff1a;过年偷懒了(●ˇ∀ˇ●)&#xff0c;但是年后开学了一定要恢复学习状态&#xff0c;在复习加继续学习的途中&#xff0c;我发现对于unsigned关键字的掌握并不是很熟练&#xff0c;于是翻阅了各个大佬的博客以及书籍&#xff0c;总结了对于unsigned的一些知识点…

P9 C++类

目录 01 类是什么 02 如何创建类 03 方法 后话 本期我们要讲的是 C 中的类。 我们终于讲到了面向对象编程&#xff0c;这是一种非常流行的编程方式&#xff0c;面向对象编程实际上只是一种你可以采用的编写代码的方式&#xff0c;其他语言例如 C#、Java 这些主要是面向对象…

白嫖CTG4.0

大家好&#xff0c;到点了我来给各位大佬献策CTG&#xff0c;不是花钱买不起&#xff0c;而是免费更有性价比&#xff0c;哈哈哈不调侃了我们自此开始正文&#xff0c;咱们主打的就是一个分享是一种态度 当然我更希望大家支持国产对国产有自己的信心&#xff08;文心一言&…

多模态——使用stable-video-diffusion将图片生成视频

多模态——使用stable-video-diffusion将图片生成视频 0. 内容简介1. 运行环境2. 模型下载3. 代码梳理3.1 修改yaml文件中的svd路径3.2 修改DeepFloyDataFiltering的vit路径3.3 修改open_clip的clip路径3.4 代码总体结构 4. 资源消耗5. 效果预览 0. 内容简介 近期&#xff0c;…

理解CLIP模型

1.简介 学习深度学习必看CLIP&#xff01;论文链接arxiv.org/pdf/2103.00020v1.pdf。 简单来说就是传统的分类任务被用来预测指定的类别&#xff0c;有监督训练限制了模型的通用性和可用性&#xff0c;并且需要带有标签的数据来训练&#xff0c;该篇论文就想直接从原始文本中…

Navicat 技术指引 | 适用于 GaussDB 的用户权限设置

Navicat Premium&#xff08;16.2.8 Windows版或以上&#xff09; 已支持对 GaussDB 主备版的管理和开发功能。它不仅具备轻松、便捷的可视化数据查看和编辑功能&#xff0c;还提供强大的高阶功能&#xff08;如模型、结构同步、协同合作、数据迁移等&#xff09;&#xff0c;这…

Spring 七大组件

文章目录 Spring 七大组件 Spring 七大组件 核心容器(Spring core) 核心容器提供Spring框架的基本功能。Spring以bean的方式组织和管理Java应用中的各个组件及其关系。Spring使用BeanFactory来产生和管理Bean&#xff0c;它是工厂模式的实现。BeanFactory使用控制反转(IOC)模式…

(Matalb分类预测)GA-BP遗传算法优化BP神经网络的多维分类预测

目录 一、程序及算法内容介绍&#xff1a; 基本内容&#xff1a; 亮点与优势&#xff1a; 二、实际运行效果&#xff1a; 三、部分代码&#xff1a; 四、本文代码数据说明手册分享 一、程序及算法内容介绍&#xff1a; 基本内容&#xff1a; 本代码基于Matalb平台编译&am…

Flink Flink中的分流

一、什么是分流 所谓“分流”&#xff0c;就是将一条数据流拆分成完全独立的两条、甚至多条流。也就是基于一个DataStream&#xff0c;定义一些筛选条件&#xff0c;将符合条件的数据拣选出来放到对应的流里。 二、基于filter算子的简单实现分流 其实根据条件筛选数据的需求…

面了一个4年经验的测试工程师,自动化都不会也要15k,我也是醉了····

&#x1f4e2;专注于分享软件测试干货内容&#xff0c;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01;&#x1f4e2;交流讨论&#xff1a;欢迎加入我们一起学习&#xff01;&#x1f4e2;资源分享&#xff1a;耗时200小时精选的「软件测试」资…

表单考勤签到作业周期打卡打分评价评分小程序开源版开发

表单考勤签到作业周期打卡打分评价评分小程序开源版开发 表单打卡评分 表单签到功能&#xff1a;学生可以通过扫描二维码或输入签到码进行签到&#xff0c;方便教师进行考勤管理。 考勤功能&#xff1a;可以记录学生的出勤情况&#xff0c;并自动生成出勤率和缺勤次数等统计数…

ruoyi-plus-vue部署

安装虚拟机 部署文档 安装docker 安装docker 安装docker-compose 可能遇到的错误 Failed to deploy ruoyi/ruoyi-server:5.1.0 Dockerfile: ruoyi-admin/Dockerfile: Cant retrieve im age ID from build stream 安装 vim 命令 yum install vim -y 修改文件 vim /etc/re…

基于Springboot的冬奥会科普平台(有报告),Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的冬奥会科普平台&#xff08;有报告&#xff09;&#xff0c;Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层…