epoll用法详解与编程实例

2023-10-31

1 epoll使用的三个函数

使用epoll时会用到三个函数,因此把这个三个函数弄明白了,也就明白了epoll的用法。要明白这个三个函数,最重要的就是要明白函数的参数,明白需要什么样的参数以及每一个参数的含义。

1.1 epoll_create函数

【作用】:
这个函数的作用就是创建一个epoll对象,然后就可以它来监测多个I/O事件。
【入参】:
int size : 表示可以监听的I/O事件的最大个数
【返回值】:
int epfd:创建成功,返回一个大于0的值,代表epoll句柄,失败返回值小于0
当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。

//头文件
// 使用epoll需要包含下面的头文件
        #include <sys/epoll.h>
       
		int epoll_create(int size); 
	
	一般用法:
		epfd = epoll_create(1);

1.2 epoll_ctl函数

【作用】:
向1.1中创建的epoll对象中添加/删除/修改需要监听的I/O事件。
【入参】:
int epfd:创建的epoll句柄
int op:表示操作类型。有三个宏表示:

/* 
EPOLL_CTL_ADD:注册新的fd到epfd中;
EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
EPOLL_CTL_DEL:从epfd中删除一个fd。
*/

int fd:要监听的I/O事件(socket、实名管道)的对应的fd
struct epoll_event *event:表示已经事件的类型(读写等)(后面细讲)
【返回值】:
如果操作成功,返回值大于等于0,失败返回值小于0

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

一般用法:	
	epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, (struct epoll_event)&event);

1.3 epoll_wait函数

【作用】:
【入参】:
int epfd:1.1从创景的epoll对象的fd
events:用来存放从内核得到的就绪事件的集合;存放接收到的事件数组。
int maxevents:表示每次能处理的最大事件数,告之内核这个events有多大;这个maxevents的值不能大于创建epoll_create()时的size;
int timeout:是超时时间(填一个 t > 0:表示等待 t 毫秒ms;0:就会立即返回;-1: 阻塞)
【返回值】:
返回值ret小于0


	int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

一般用法:	
	struct epoll_event events[NUM];//事件数组
	int maxevents = NUM;
	ret = epoll_wait(epfd, events, maxevents, -1);//-1阻塞
	

2 epoll_event 结构体

在函数epoll_ctl以及函数epoll_wait的入参中都出现了这个结构体,这个结构体也是这3个函数中最复杂的参数类型。下面详细说明一下。

struct epoll_event {
	__uint32_t events; /* Epoll events */
	epoll_data_t data; /* User data variable */
};
/* *** 注意****
epoll_data 是一个联合体结构成员,所有的成员公用一段内存,因此实际上只能保留一个成员
它只会保存最后一个被赋值的成员,所以不要data.fd,data.ptr都赋值
*/
typedef union epoll_data {
	void *ptr;
	int fd;
	__uint32_t u32;
	__uint64_t u64;
} epoll_data_t;

epoll_event 结构体中有两个数据成员:events和data,其中events代表要监听的事件类型,data用来保存一写额外的数据类型,如监听时间的fd、回调函数等。

2.2 监听的事件类型

前面提到epoll_event 中events代表要监听的时间类型。
events可以是以下几个宏的集合:
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);

EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;

EPOLLET: 将EPOLL设为边缘触发(Edge Triggered:上升沿/下降沿)模式,即缓冲区数据从有(无)到无(有)。这是相对于水平触发(Level Triggered:高、低电平触发):缓冲区数据读没有读完,写没有写满。select()函数只支持水平触发。

EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里。
实际上不同的监听事件是用不同的bit位来表示的,如果对应的bit位为1,表示代表了监听某事件,例如:

EPOLLIN 的值实际是为1,二进制为:0001 (最低位置1)
EPOLLOUT 的值实际是4,二进制为:0100  (3位置1)
因此如果要同时监听读取事件,可以表示为
EPOLLIN | EPOLLOUT   -> 按位或的结果为 0101

2.3 epoll_wait如何返回就绪事件

注意到:int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
上面这个函数中涉及到epoll_event 这个结构体,这个结构体的作用就是用来保存返回的就绪事件

struct epoll_event p_events[NUM];//事件数组
int maxevents = NUM;
ret = epoll_wait(epfd, p_events, maxevents, -1);//-1阻塞
// 假如有5个就绪事件返回,即ret=5,那么返回的就绪事件信息就会保存在p_events数组的前5个位置,即
p_events[0] -- p_events[4]

再回过头来仔细想想,返回的events中为什么需要包含监听事件类型、fd等信息:
events成员:代表监听事件类型,因为需要就绪的事件是读还是写,以便分别采取不同的动作
data成员中fd:因为不同的事件采取的操作肯定是不同的,很多操作,例如读函数read中也会用到fd这个参数。因此fd是必要的。
那为什么data成员中有时还需要添加一些别的数据呢?
假如p_events里面值包含两个参数,那么在处理返回的就绪事件时可能需要向下面这么做:

struct epoll_event p_events[NUM];//事件数组
int maxevents = NUM;
ret = epoll_wait(epfd, p_events, maxevents, -1);//-1阻塞
for (int i = 0; i < ret; ++i) {
	int fd = p_events[i].data.fd; // 获取就绪事件的fd
	// 下面会根据不同的fd来进行不同的处理
	if (fd == fd_A) {
		// 按照fd_A 的处理方法处理
	}
	else if (fd == fd_B) {
		// 按照fd_B 的处理方法处理
	}
	...
	// 如果fd的类型太多的话,就会导致上面的判断语句十分冗余... 显然这不好
}
/* 那么有没有一个好的解决方法呢?   -- 提前为每个fd绑定一个回调函数
可以自定义一个结构体,这个结构体里面包含fd、回调函数、回调函数的参数等,
然后让data的中的ptr指针指向这个结构体,例如:
*/
struct my_events {
	int fd;
	void* arg;
	void (*call_back)(int fd,void *arg);
};
// 然后设置data中ptr指针指向my_events结构体:
struct my_events myEvents;
myEvents.fd = listenfd;               // 设置需要监听的fd
myEvents.call_back = call_func; // 设置对应的回调函数
myEvents.arg = arg;             //  设置回调函数参数
struct epoll_event epv = {0,{0}};
epv.data.ptr = &myEvents;
// 注册添加需要监听的事件
epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, (struct epoll_event)&epv );
// 然后调用epoll_wait函数获取就绪事件之后就可以按照下面处理事件了:
struct epoll_event p_events[NUM];//事件数组
int maxevents = NUM;
ret = epoll_wait(epfd, p_events, maxevents, -1);//-1阻塞
for (int i = 0; i < ret; ++i) {
	my_events* p_myevents = p_events[i].data.ptr;
	int fd = p_myevents->fd;    // 获取就绪事件的fd
	void *arg = p_myevents->arg; // 获取就绪事件对应回调函数的参数
	// 直接调用回调函数处理就绪事件
	p_myevents->call_back(fd, arg);
}

通过上面的示例就可以发现,epoll_data 中data成员中ptr指针可以指向我们自定义的数据结构,在这个数据结构中,我们可以指定回调函数、回调函数参数等内容,这样我们获取就绪事件之后,就能够很方便的进行处理了。

【参考文章】
1、网络编程-epoll的相关API函数, epoll并发服务器
2、linux内核event原理,linux epoll epoll的原理;struct epoll_event 为什么要这样设计

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

epoll用法详解与编程实例 的相关文章

随机推荐

  • 端口转发及穿透内网

    文章目录 portmap nc 正向连接 内网主机出网 反向连接 内网主机不出网 netsh端口转发 端口转发 netsh端口转发监听meterpreter frp 例子 参考 portmap https github com Brucet
  • Linux中日志管理和常见故障

    作者 小刘在C站 个人主页 小刘主页 每天分享云计算网络运维课堂笔记 努力不一定有回报 但一定会有收获加油 一起努力 共赴美好人生 夕阳下 是最美的绽放 树高千尺 落叶归根人生不易 人间真情 目录 一 关于日志及管理 1 日志的功能
  • OD华为机试 25

    按索引范围翻转文章片段 描述 输入一个英文文章片段 翻转指定区域的单词顺序 标点符号和普通字母一样处理 例如输入字符串 I am a developer 0 3 则输出 developer a am I 输入描述 使用换行隔开3个参数 第一
  • @Cacheable使用详解

    1 功能说明 Cacheable 注解在方法上 表示该方法的返回结果是可以缓存的 也就是说 该方法的返回结果会放在缓存中 以便于以后使用相同的参数调用该方法时 会返回缓存中的值 而不会实际执行该方法 注意 这里强调了一点 参数相同 这一点应
  • QT开发之QLineEdit

    1 设置输入限制 QLineEdit lineedit new QLineEdit this QRegExp rx a zA Z0 9 0 设置为只能输入数字和英文 QRegExpValidator pRevalidotor new QRe
  • vue使用echarts中tooltip自定义显示——使用值params参数详解

    tooltip trigger item formatter a br b c d formatter function params 在此处直接用 formatter 属性 console log params 打印数据 debugger
  • CAN接口芯片MCP2515的波特率和滤波器设置问题

    配置波特率代码 MCP2515ByteWrite CNF1 3 MCP2515ByteWrite CNF2 0x80 PHSEG1 3TQ PRSEG 1TQ MCP2515ByteWrite CNF3 PHSEG2 3TQ 功能 波特率的
  • opencv调用yolov7 yolov7 c++ yolov7转onnx opencv调用yolov7 onnx

    一 YOLOV7主要贡献 主要是现有的一些trick的集合以及模块重参化和动态标签分配策略 最终在 5 FPS 到 160 FPS 范围内的速度和准确度都超过了所有已知的目标检测器 当前目标检测主要的优化方向 更快更强的网络架构 更有效的特
  • 【零基础学QT】第一章 QT安装与工程创建

    作者主页 凉开水白菜 作者简介 共同学习 互相监督 热于分享 多加讨论 一起进步 专栏目录 零基础学QT 文章导航篇 专栏资料 https pan baidu com s 192A28BTIYFHmixRcQwmaHw 提取码 qtqt 点
  • 详解数据库基本概念

    数据库 DataBase 简称 DB 是一个长期存储在计算机内的 有组织的 可共享的 统一管理的大量数据的集合 数据库管理系统 DataBase Management System 简称 DBMS 是一种操纵和管理数据库的大型软件 用于建立
  • C#基础详解

    Excerpt C 是微软公司发布的一种面向对象的 运行于 NET Framework和 NET Core 完全开源 跨平台 之上的高级程序设计语言 C 是一种安全的 稳定的 简单的 优雅的 由C和C 衍生出来的面向对象的编程语言 它在继承
  • 使用 Python 实现斐波那契数列

    介绍 斐波那契数列是一个非常经典的数列 其前两项为 1 从第三项开始 每一项都等于前两项之和 这个数列很有意思 因为它的每一项都是前两项的和 而且越往后 数列中的数字就越大 以至于数列中的第 100 项就是黄金分割比例的倒数 斐波那契数列的
  • 关键词共现分析_结巴分词5--关键词抽取

    1 简介 关键词抽取就是从文本里面把跟这篇文档意义最相关的一些词抽取出来 这个可以追溯到文献检索初期 当时还不支持全文搜索的时候 关键词就可以作为搜索这篇论文的词语 因此 目前依然可以在论文中看到关键词这一项 除了这些 关键词还可以在文本聚
  • 三分钟教你如何写代码,构建自己的“思维帝国”

    很多人都在抱怨科技行业的知识更新的太快 他们疯狂的追逐新技术 当掌握一门新技术后突然发现 又有更新的技术来了 于是就想逃离这个行业 逃离这些不堪重负的压力 互联网的世界使用代码构建起来的 从事互联网行业就免不了时刻与代码交流 但是 技术永远
  • linux查看端口对应的应用

    根据端口查看对应进程 lsof i 端口 根据进程号PID查看对应应用 ps axu grep 进程号 根据进程号PID查看对应端口 netstat anp grep 进程号
  • 配置maven指定的JDK版本

    配置maven指定的JDK版本 第一种 最省事 在我们安装maven环境的conf目录下修改settings xml文件 如下 在profiles中加入以下配置
  • debian终端tab键无法补全命令,apt install 无法补全

    debian终端无法补全命令 输入apt i 按tab键无法补全install 这很不方便 解决方法 1 安装bash completion 包 2 编辑 etc bash bashrc 3 编辑 etc profile 4 重新登录系统即
  • Mysql和Oracle的语法区别?

    Mysql和Oracle是两种不同的关系型数据库 MySQL通常在中小型应用程序 Web应用程序和小型企业中广泛使用 因为它易于学习和部署 而且成本较低 Oracle数据库通常用于大型企业和复杂的企业级应用程序 因为它提供了高度可扩展性 高
  • DDL数据定义语言

    文章目录 DDL Data Definition Language 数据定义语言 数据库的管理 表的管理 表的创建 表的删除 表的复制 表的修改 DDL Data Definition Language 数据定义语言 DDL其实是Data
  • epoll用法详解与编程实例

    1 epoll使用的三个函数 使用epoll时会用到三个函数 因此把这个三个函数弄明白了 也就明白了epoll的用法 要明白这个三个函数 最重要的就是要明白函数的参数 明白需要什么样的参数以及每一个参数的含义 1 1 epoll creat