[Linux]:IO多路转接之epoll

1. IO 多路转接之epoll

1.1 epoll概述

epoll是Linux内核为处理大规模并发网络连接而设计的高效I/O多路转接技术。它基于事件驱动模型,通过在内核中维护一个事件表,能够快速响应多个文件描述符上的I/O事件,如可读、可写、异常等,避免了像selectpoll那样频繁地遍历文件描述符集合,从而大大提高了系统的性能和响应速度。epoll主要适用于需要处理大量并发连接的服务器应用场景,如Web服务器、邮件服务器、实时通信服务器等,能够轻松应对高并发情况下的I/O处理需求,为用户提供高效、稳定的服务。

1.2 epoll相关函数

1.2.1 epoll_create
  1. 函数原型:int epoll_create(int size);
  2. 参数说明:
    • size:从Linux 2.6.8版本开始,该参数已被忽略,但必须大于0。
  3. 返回值说明:
    • 成功时返回一个非负的文件描述符,用于后续的epoll操作。这个文件描述符将指向内核中的epoll实例。
    • 失败时返回 -1,并设置相应的错误码。可能的错误码包括ENOMEM(内存不足)等。
1.2.2 epoll_ctl函数
  1. 函数原型:int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
  2. 参数说明:
    • epfdepoll_create返回的epoll文件描述符。
    • op:操作类型,有以下几种取值:
      • EPOLL_CTL_ADD:向epoll实例中添加一个文件描述符及其关注的事件。
      • EPOLL_CTL_MOD:修改已注册文件描述符关注的事件。
      • EPOLL_CTL_DEL:从epoll实例中删除一个文件描述符。
    • fd:要操作的文件描述符。
    • event:指向struct epoll_event结构体的指针,用于指定要关注的事件类型和相关数据。
  3. 返回值说明:
    • 成功时返回0。
    • 失败时返回 -1,并设置相应的错误码。可能的错误码包括EBADF(文件描述符无效)、EEXIST(添加已存在的文件描述符)、ENOENT(删除不存在的文件描述符)、ENOMEM(内存不足)等。

struct epoll_event结构体用于描述epoll事件,其定义如下:

struct epoll_event {uint32_t events;  // 表示关注的事件类型,如EPOLLIN(可读)、EPOLLOUT(可写)等epoll_data_t data;  // 用于存储与文件描述符相关的用户数据
};// epoll_data_t结构体定义
typedef union epoll_data {void *ptr;int fd;uint32_t u32;uint64_t u64;
} epoll_data_t;

其中struct epoll_event 结构中有两个成员,第一个成员 events 表示的是需要监视的事件,第二个成员 data 是一个联合体结构,一般选择使用该结构当中的 fd,表示需要监听的文件描述符。

其中 events 的常用取值如下:

事件取值含义
EPOLLIN表示对应的文件描述符可以读(包括对端SOCKET正常关闭)
EPOLLOUT表示对应的文件描述符可以写
EPOLLPRI表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来)
EPOLLERR表示对应的文件描述符发送错误
EPOLLHUP表示对应的文件描述符被挂断,即对端将文件描述符关闭了
EPOLLET将epoll的工作方式设置为边缘触发(Edge Triggered)模式
EPOLLONESHOT只监听一次事件,当监听完这次事件之后,如果还需要继续监听该文件描述符的话,需要重新将该文件描述符添加到epoll模型中

这些取值实际都是以宏的方式进行定义的,它们的二进制序列当中有且只有一个比特位是1,且为1的比特位是各不相同的。所以可以通过按位或操作符实现同时关心两种方式。

1.2.3 epoll_wait函数
  1. 函数原型:int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
  2. 参数说明:
    • epfdepoll_create返回的epoll文件描述符。
    • events:指向struct epoll_event结构体数组的指针,用于接收就绪事件的信息。
    • maxeventsevents数组的大小,即最多能接收的就绪事件数量,该值不能大于创建 epoll 模型时传入的 ``size` 值。
    • timeout:超时时间,单位为毫秒。取值如下:
      • -1:阻塞等待,直到有事件就绪或捕获到信号。
      • 0:非阻塞等待,立即返回,无论是否有事件就绪。
      • 大于0:在指定时间内阻塞等待,超时后返回。
  3. 返回值说明:
    • 成功时返回就绪事件的数量。
    • 超时返回0。
    • 失败返回 -1,并设置相应的错误码。可能的错误码包括EBADF(文件描述符无效)、EFAULTevents数组指针无效)、EINTR(被信号中断)等。

1.3 epoll 的工作原理

epoll 的工作原理是基于eventpoll结构体。当进程调用epoll_create函数时,内核创建eventpoll结构体,其中有红黑树rbr和就绪队列rdlist

struct eventpoll{//...//红黑树的根节点,这棵树中存储着所有添加到epoll中的需要监视的事件struct rb_root rbr;//就绪队列中则存放着将要通过epoll_wait返回给用户的满足条件的事件struct list_head rdlist;//...
}

红黑树存储要监视的事件对应的文件描述符和事件类型,epoll_ctl函数用于对红黑树进行增删改操作。文件描述符可作为红黑树的键值,设置EPOLLONESHOT选项的事件就绪后会从红黑树自动删除,没设置则一直存在,除非手动删除。

就绪队列存放已就绪的事件,epoll_wait函数用于从就绪队列获取这些事件。每个事件对应一个epitem结构体,红黑树和就绪队列的节点分别基于epitem中的rbnrdllink成员,ffd记录文件描述符,event记录事件类型。

struct epitem{struct rb_node rbn; //红黑树节点struct list_head rdllink; //双向链表节点struct epoll_filefd ffd; //事件句柄信息struct eventpoll *ep; //指向其所属的eventpoll对象struct epoll_event event; //期待发生的事件类型
}

在回调机制方面,添加到红黑树的事件会和设备驱动程序建立ep_poll_callback回调方法。与selectpoll不同,epoll不需要操作系统主动轮询检测事件是否就绪。当监视的事件就绪时,自动调用回调方法,将事件添加到就绪队列。

总结来说,epoll的使用过程就是三步:首先用epoll_create创建epoll模型;接着用epoll_ctl注册要监控的文件描述符;最后用epoll_wait等待文件描述符就绪。而且因为就绪队列可能被多个执行流访问,eventpoll结构中有用于保护的锁,本身是线程安全的,还涉及等待队列wq来处理多个执行流访问同一epoll模型的情况。

1.4 服务端代码

然后我们就可以编写一个基于 epoll多路转接的 TCP 服务端,并且编写流程也与 select服务端类似:

#include "Sock.hpp"
#include <sys/epoll.h>#define NUM 1024// EpollServer类,用于实现基于epoll的服务器功能
class EpollServer
{
public:// 构造函数,初始化服务器监听端口EpollServer(int port): _port(port){}// 初始化服务器相关设置,包括创建、绑定和监听套接字,同时创建epoll模型void InitPollServer(){// 创建套接字_listensock.Socket();// 将套接字绑定到指定端口_listensock.Bind(_port);// 开始监听套接字_listensock.Listen();// 创建epoll模型,参数NUM用于指定监听事件数量的上限_epfd = epoll_create(NUM);if (_epfd < 0){// 如果创建失败,输出错误信息并退出程序std::cerr << "epoll_create error" << std::endl;exit(4);}}// 运行服务器,处理客户端连接和数据读取等操作void Run(){// 将监听套接字添加到epoll模型中,监听可读事件AddEvent(_listensock.Fd(), EPOLLIN);while (true){struct epoll_event revs[NUM];int num;// 调用epoll_wait等待事件发生,根据返回值进行不同处理switch (num = epoll_wait(_epfd, revs, NUM, -1)){case 0:// epoll_wait返回0,表示超时std::cout << "time out..." << std::endl;break;case -1:// epoll_wait返回-1,表示发生错误std::cerr << "epoll error" << std::endl;break;default:// epoll_wait返回其他值,表示有就绪事件,调用处理函数进行处理HandlerEvent(revs, num);break;}}}// 析构函数,关闭监听套接字和epoll模型对应的文件描述符~EpollServer(){if (_listensock.Fd() >= 0){_listensock.Close();}if (_epfd >= 0){close(_epfd);}}private:// 处理就绪事件的函数void HandlerEvent(struct epoll_event revs[], int num){for (int i = 0; i < num; i++){int fd = revs[i].data.fd;// 如果是监听套接字且有可读事件,表示有新的客户端连接if (fd == _listensock.Fd() && revs[i].events & EPOLLIN){struct sockaddr_in peer;socklen_t len = sizeof(peer);memset(&peer, 0, len);std::string clientip;uint16_t clientport;// 接受新的客户端连接,获取客户端的套接字描述符、IP地址和端口号int sock = _listensock.Accept(&clientip, &clientport);std::cout << "get a new link[" << clientip << ":" << clientport << "]" << std::endl;// 将新连接的套接字添加到epoll模型中,监听可读事件AddEvent(sock, EPOLLIN);}// 如果不是监听套接字且有可读事件,表示有数据可读,进行数据读取和处理else if (revs[i].events & EPOLLIN){char buffer[1024];ssize_t n = read(fd, buffer, sizeof(buffer) - 1);if (n > 0){// 如果读取到数据,添加字符串结束符并输出数据内容buffer[n] = 0;std::cout << "echo# " << buffer << std::endl;}else if (n == 0){// 如果读取到的字节数为0,表示客户端已断开连接,关闭对应的套接字并从epoll模型中删除该事件std::cout << "client quit..." << std::endl;close(fd);epoll_ctl(_epfd, EPOLL_CTL_DEL, fd, nullptr);}else{// 如果读取发生错误,输出错误信息,关闭对应的套接字并从epoll模型中删除该事件std::cerr << "read error" << std::endl;close(fd);epoll_ctl(_epfd, EPOLL_CTL_DEL, fd, nullptr);}}}}// 将指定套接字添加到epoll模型中,设置要监听的事件类型void AddEvent(int sockfd, uint32_t event){struct epoll_event ev;ev.events = event;ev.data.fd = sockfd;// 使用epoll_ctl函数将套接字和事件添加到epoll模型中,操作类型为添加epoll_ctl(_epfd, EPOLL_CTL_ADD, sockfd, &ev);}private:Sock _listensock;int _port;int _epfd; // epoll模型对应的文件描述符
};

我们同样可以使用 telnet进行测试。

1.5 epoll工作模式

epoll有水平触发(LT,Level Triggered)和边缘触发(ET,Edge Triggered)两种工作方式,这和数字电路中的高电平触发、上升沿触发类似。

epoll 默认工作模式是水平触发(LT)模式。

1.5.1 水平触发模式

当文件描述符就绪时,epoll_wait会返回该文件描述符,并且如果应用程序没有对该文件描述符进行完全的读写操作(即缓冲区数据没有被全部处理完),那么下次epoll_wait调用时仍然会立即返回该文件描述符,直到应用程序完成对该文件描述符的I/O操作,使得其状态不再就绪。

例如,一个套接字有可读数据(接收缓冲区中有数据),在水平触发模式下,epoll_wait会一直通知应用程序该套接字可读,直到应用程序将接收缓冲区中的数据全部读取完。

1.5.2 边缘触发模式

如果想将 epoll 改为ET工作模式,则需要在添加事件时设置EPOLLET选项。

当文件描述符的状态发生变化(如从不可读到可读,或从不可写到可写)时,epoll_wait会返回该文件描述符。之后,只有当该文件描述符的状态再次发生变化时,epoll_wait才会再次通知应用程序。这意味着应用程序在处理事件时必须一次性将数据读取完或写入完,否则可能会错过后续的I/O事件。

对于一个监听套接字,当有新的连接请求到来时(从无连接请求到有连接请求,状态变化),epoll_wait会返回该监听套接字的就绪事件。如果应用程序接受了连接并得到新的连接套接字,那么这个新的连接套接字在有数据可读(从无数据到有数据,状态变化)时,epoll_wait会通知应用程序。但如果应用程序在处理可读事件时没有将接收缓冲区中的数据全部读取完,下次epoll_wait不会再次通知,直到有新的数据到达导致状态再次变化。

epoll 的 ET(边缘触发)工作模式中,仅当底层就绪事件从无到有或从有到更多时才通知用户。这就要求用户在读事件就绪时需一次性读完所有数据,写事件就绪时要一次性写满发送缓冲区,否则可能因为此后底层再也没有事件就绪而失去读写机会。

对于读操作,要循环调用 recv 函数。当底层读事件就绪,持续循环调用 recv,直至某次调用时实际读取字节数小于期望读取字节数,这表明底层数据已读完。不过,存在一种情况,即最后一次调用 recv 时实际读取字节数和期望读取字节数相等,但此时底层数据恰好也读完了,若再次调用 recv,由于底层无数据,recv 函数将阻塞。这种阻塞问题严重,以单进程服务器为例,若 recv 阻塞且数据不再就绪,服务器就相当于瘫痪了。因此,在 ET 模式下循环调用 recv 读取数据时,必须将对应的文件描述符设置为非阻塞状态。

写操作同理,写数据时要循环调用 send 函数,并且也要将对应的文件描述符设置为非阻塞状态。总之,在 ET 工作模式下,recvsend 操作的文件描述符必须设置为非阻塞状态,这是强制要求。

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

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

相关文章

从0开始学习Linux——用户管理

往期目录&#xff1a; 从0开始学习Linux——简介&安装 从0开始学习Linux——搭建属于自己的Linux虚拟机 从0开始学习Linux——文本编辑器 从0开始学习Linux——Yum工具 从0开始学习Linux——远程连接工具 从0开始学习Linux——文件目录 从0开始学习Linux——网络配置 从0开…

Docker 安装Immich教程

Immich 是一个开源的自托管照片和视频管理平台,专为帮助用户存储、管理、和分享个人媒体库而设计。Immich 的目标是提供一个类似 Google Photos 的替代方案,但不依赖于第三方服务,用户可以完全控制自己的数据。 本章教程,记录如何用Docker部署安装Immich,使用的操作系统的…

回首遥望-C++内存对齐的思考

这一章节主要巩固一下学习C/C时内存对齐相关的内容&#xff01; 文章目录 什么是内存对齐&#xff1f;为什么要有内存对齐&#xff1f;如何进行内存对齐&#xff1f;致谢&#xff1a; 什么是内存对齐&#xff1f; 这里不提及一堆啰嗦概念&#xff0c;就结合实际出发&#xff0…

从代码到云端:使用PyCharm打包Python项目并部署至服务器的完整指南

从代码到云端&#xff1a;使用PyCharm打包Python项目并部署至服务器的完整指南 引言 随着云计算技术的发展&#xff0c;越来越多的开发者选择将自己的应用部署到云服务器上。对于Python开发人员来说&#xff0c;如何高效地将本地开发完成的应用程序迁移到远程服务器成为了一个…

【ComfyUI +BrushNet+PowerPaint】图像修复(根据题词填充目标)——ComfyUI-BrushNet

运行代码&#xff1a;https://github.com/nullquant/ComfyUI-BrushNet 源码1&#xff1a;https://github.com/TencentARC/BrushNet 源码2&#xff1a;https://github.com/open-mmlab/PowerPaint 上图&#xff0c;中间未 random_mask.safetensors结果&#xff0c;最右边图未segm…

(时序论文阅读)TimeMixer: Decomposable Multiscale Mixing for Time Series Forecasting

来源论文iclr2024 论文地址&#xff1a;https://arxiv.org/abs/2405.14616 源码地址&#xff1a; https://github.com/kwuking/TimeMixer 背景 数据是有连续性&#xff0c;周期性&#xff0c;趋势性的。我们这篇文章主要围绕的是用MLP结构来预测数据的周期性具体为&#xff…

大数据新视界 -- 大数据大厂之 Impala 性能优化:优化数据加载的实战技巧(下)(16/30)

&#x1f496;&#x1f496;&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎你们来到 青云交的博客&#xff01;能与你们在此邂逅&#xff0c;我满心欢喜&#xff0c;深感无比荣幸。在这个瞬息万变的时代&#xff0c;我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…

3196. I’m stuck!-13年12月CCF计算机软件能力认证

关键词 图通路&#xff0c;DFS/BFS 题目 思路 几点想说明的&#xff1a; 为什么要两个DFS&#xff1b;dfs1表示的是求从S出发能到达的所有的点&#xff1b;dfs2是考虑从T出发回溯&#xff0c;能到达的所有点&#xff0c;回溯去走&#xff0c;相当于此时T才是起点check函数&…

若依笔记(九):商城系统apk打包与调试

目录 env修改 nginx修改 apk打包 按照前文所述app版本保持2.2&#xff0c;使用uniapp作为生成工具&#xff0c;因为app版本涉及到登录、商品、支付&#xff0c;微信小程序打包需要企业主权限&#xff0c;所以使用apk打包&#xff0c;使用uniapp自带的云打包&#xff0c;本地…

算法练习记录

dp&#xff08;完全&#xff0c;线性&#xff0c;树状&#xff0c;状压&#xff0c;数位&#xff09;> 搜索 基础算法&#xff08;二分&#xff0c;前缀和&#xff0c;高精度&#xff0c;公约数&#xff0c;公倍数&#xff0c;质数&#xff0c;排序&#xff09; > 数据结…

深入浅出 ChatGPT 底层原理:Transformer

Transformer 在人工智能和自然语言处理领域,Transformer架构的出现无疑是一个里程碑式的突破。它不仅推动了GPT(Generative Pre-trained Transformer)等大型语言模型的发展,还彻底改变了我们处理和理解自然语言的方式。 GPT与Transformer GPT 近几年&#xff0c;人工智能技…

【C语言】js写一个冒泡顺序

用js写一个冒泡排序 当然可以。在JavaScript中&#xff0c;你可以使用递归的方式来实现冒泡排序算法。这是一个简单的示例&#xff1a; function bubbleSort(arr) { let len arr.length; if(len < 1){ return arr; // 如果数组只有一个元素或为空&#xff0c;直接返回 } //…

Leetcode3345. 最小可整除数位乘积 I

Every day a Leetcode 题目来源&#xff1a;3345. 最小可整除数位乘积 I 解法1&#xff1a;枚举 至多循环 10 次&#xff0c;一定会遇到个位数为 0 的数字&#xff0c;数位乘积是 0&#xff0c;一定是 t 的倍数。 所以暴力枚举即可。 代码&#xff1a; /** lc appleetcod…

element的from表单校验问题 —— 校验内容正确、但触发错误提示

问题&#xff1a;二次封装了el-radio&#xff0c;在选择后触发了form的校验&#xff0c;并提示了错误。 分析&#xff1a;输出radio选择后的value值是正确&#xff0c;但还是触发了错误校验提示&#xff0c;可能纯在以下几个问题 1. v-model 绑定的form参数和rules不一致 2. e…

工业相机选取

1.相机分类&#xff1a; 1.1 在相机曝光方式中&#xff0c;全局曝光和卷帘曝光是两种主流技术。CCD相机通常采用全局曝光方式&#xff0c;而CMOS相机则可能采用卷帘曝光。 面阵相机与全局曝光关联与区别 关联&#xff1a;面阵相机可以使用全局曝光作为曝光方式&#xff0c;但…

使用Windows自带的IIS搭建FTP服务端

1、启用IIS功能 2、打开IIS 3、将默认的站点删除 4、创建FTP服务端 &#xff08;1&#xff09;选中站点&#xff0c;然后点击鼠标邮件&#xff0c;点击添加FTP站点 &#xff08;2&#xff09;指定站点名称和物理路径 物理路径&#xff1a;FTP服务端数据的路径&#xff0c;F…

研界的福尔摩斯——扩增子+qPCR

微生物在生物地球化学循环、动植物健康等多种领域发挥作用&#xff0c;因此&#xff0c;精确测量微生物绝对丰度对理解其与人类健康、植物生长等的关系至关重要。 常规扩增子测序分析只能解析样本中的物种组成和其相对丰度信息&#xff0c;并不能反映样本每种微生物的真实数量…

期权懂|期权到期了,可以不行权吗?

期权小懂每日分享期权知识&#xff0c;帮助期权新手及时有效地掌握即市趋势与新资讯&#xff01; 期权到期了&#xff0c;可以不行权吗&#xff1f; 期权到期后&#xff0c;投资者并非必须行权。如果行权无利可图或不符合预期收益&#xff0c;可以选择放弃行权&#xff0c;让期…

SL1571B 输入5V2A或单节锂电池,升压12V 10W 升压恒压芯片

一、概述 SL1571B是一款高功率密度的异步升压转换器&#xff0c;专为便携式系统提供高效且小尺寸的解决方案。它内置MOS管&#xff0c;具有120mΩ功率开关&#xff0c;支持宽输入电压范围&#xff0c;并具备多种保护功能。 二、主要特性 输入电压范围&#xff1a;SL1571B的输…

接口测试vs功能测试

接口测试和功能测试的区别&#xff1a; 本文主要分为两个部分&#xff1a; 第一部分&#xff1a;主要从问题出发&#xff0c;引入接口测试的相关内容并与前端测试进行简单对比&#xff0c;总结两者之前的区别与联系。但该部分只交代了怎么做和如何做&#xff1f;并没有解释为什…