基于epoll的io复用管理,一种文件监听方案 2 - 教程

news/2025/11/18 21:33:54/文章来源:https://www.cnblogs.com/slgkaifa/p/19239390

基于epoll的io复用管理,一种文件监听方案 2 - 教程

(仓库链接:https://github.com/12379biu/epoll_manager.git)

目录

epoll的一些性质

通过epoll_manager封装写一个多人服务器

注册

处理

事件处理与自定义函数

可变参处理函数原型

实验结果


此封装旨在将epoll从事件处理中解耦出来,更专注与对文件本身的监听,仅作为事件唤醒的手段,将事件处理与之解耦。
上一篇介绍了此封装API函数的使用,以及监听标准输入流的例子。这一篇接着介绍epoll组件的特性,以及此封装源码。并使用它创建一个多人服务器。

注册epoll

epoll的一些性质

epoll的主要函数epoll_create()创建epoll,epoll_ctl()注册/修改/注销,epoll_wait()等待事件

    struct epoll_event epoll_e;epoll_fd = epoll_create1(0);epoll_e.events = EPOLLIN | EPOLLET;//边缘触发,输入事件epoll_e.data.ptr = static_cast(p);//假设指针p中存放有文件句柄和其他信息/*注册epoll成员*/temp_fd = epoll_ctl(epoll_fd,EPOLL_CTL_ADD,p->fd,&epoll_e);

通过epoll_ctl,参数epoll_fd:创建的epoll句柄,EPOLL_CTL_ADD:行为(注册监听文件),

p->fd:要监听的文件句柄,&epoll_e:存放属性与内容信息的容器(一次性用品,注册完被linux内核记录,就可以销毁epoll_e了)将要监听的属性:events注册到epoll;剩下的epoll成员,无论是data.ptr还是data.fd都属于内容,epoll机制不关心内容是什么,因此无论epoll_.data存什么都不会干扰到它的唤醒机制,排序机制,这给了我们极大的操作空间,我们可以在epoll_event.data.ptr中存储任意我们希望存储的信息。

struct epoll_event events[10];
int nfds = 0;
nfds = epoll_wait(epoll1_fd,events,Max_Event,-1);
for (int i = 0;i < nfds;i ++)
{...(对events[i]的处理)
}

这段代码中,文件注册到epoll后epoll_wait()的会将注册的的监听文件,events[10]作为储存监听文件产生事件的容器等待处理,nfds:产生的事件数量。注意events[10]并非是最大能等待的事件的数目。这是一次性能能处理的最大事件数量。什么意思呢?举个例子,通过epoll_ctl()注册了100个文件,epoll会监听这一百个文件而不是10个,加入一次性产生了13个可读事件发生,那么前10个会在for()循环被处理,剩下3个不会被丢弃或者覆盖,而是会等待前10个文件处理完后在第二轮循环中被处理,nfds会立刻返回3,进入第二轮循环。

通过epoll_manager封装写一个多人服务器

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "epoll_manager.h"
constexpr uint16_t Server_port = 8080;
constexpr uint32_t Max_Event = 10;
constexpr size_t Buf_Size = 1024;
void fun_cb(epoll_info* info, std::string& buf)
{std::cout << info->fd << ":" << buf << std::endl;write(info->fd,"OK",3);
}
/*初始化网络资源*/
void sock_init(int &sock_fd)
{//初始化套接字struct sockaddr_in address;int temp_fd = 0;int opt = 1;address.sin_addr.s_addr = INADDR_ANY;address.sin_family = AF_INET;address.sin_port = htons(Server_port);//创建套接字sock_fd = socket(AF_INET,SOCK_STREAM,0);ErrHandle("socket",sock_fd);//绑定端口temp_fd = bind(sock_fd,(struct sockaddr*)&address,sizeof(address));ErrHandle("bind",temp_fd);std::cout << "绑定监听端口:" << Server_port << std::endl;//监听端口temp_fd = listen(sock_fd,64);ErrHandle("listen",temp_fd);//优化断开重连:"Address already in use" 错误temp_fd = setsockopt(sock_fd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));ErrHandle("setsockopt",temp_fd);
}
/*main----------------------------------------------------------------
------------------------------------------------------------------------*/
int main(int argc,char** argv)
{
/*创建资源*/system("clear");EpollManager epoll1;struct epoll_event events[Max_Event];epoll_info temp_info = {0};temp_info.epoll_fd = epoll1.get_epoll_fd();socklen_t len = sizeof(temp_info.address);//创建流int listen_socket_fd,nfds,temp;std::string buf(Buf_Size,0);
/*服务器网络初始化----------------------------------------------------------
-------------------------------------------------------------------------*//*套接字初始化:地址族初始化---绑定---监听---连接*/sock_init(listen_socket_fd);temp_info.fd = listen_socket_fd;epoll1.info_register(&temp_info,true);//录入客户端文件信息std::cout << "监听中..." << std::endl;
/*----------------------------------------------------------------------------*/while(1){//等待事件nfds = epoll_wait(epoll1.get_epoll_fd(),events,Max_Event,-1);ErrHandle("epoll_wait",nfds);for (int i = 0;i < nfds;i ++){
/*来自监听文件的客户端连接--------------------------------------------------------------
-------------------------------------------------------------------------*///服务器有可读事件-->有新客户端连接if ((static_cast(events[i].data.ptr))->fd == listen_socket_fd){std::cout << "新连接" << std::endl;temp_info.fd = accept(listen_socket_fd,(struct sockaddr*)&(temp_info.address),&len);epoll1.info_register(&temp_info);//录入客户端文件信息std::cout << temp_info.fd << ":已连接" << std::endl;}
/*客户端发送数据--------------------------------------------------------------
-------------------------------------------------------------------------*/else //已有的连接{//传入一个epoll_info*指针,一个缓冲区,一个自定义函数,后面填参数epoll1.Epoll_EventHandle(static_cast(events[i].data.ptr),buf,fun_cb);}}}//释放资源close(listen_socket_fd);return 0;
}

注册

在上面这段代码中,我们来着重分析epoll的机制和epoll_manager封装,重点放到主函数

int main(int argc,char** argv)
{
/*创建资源*/system("clear");EpollManager epoll1;struct epoll_event events[Max_Event];epoll_info temp_info = {0};temp_info.epoll_fd = epoll1.get_epoll_fd();socklen_t len = sizeof(temp_info.address);//创建流int listen_socket_fd,nfds,temp;std::string buf(Buf_Size,0);
/*服务器网络初始化----------------------------------------------------------
-------------------------------------------------------------------------*//*套接字初始化:地址族初始化---绑定---监听---连接*/sock_init(listen_socket_fd);temp_info.fd = listen_socket_fd;epoll1.info_register(&temp_info,true);//录入客户端文件信息std::cout << "监听中..." << std::endl;
/*----------------------------------------------------------------------------*/while(1){//等待事件nfds = epoll_wait(epoll1.get_epoll_fd(),events,Max_Event,-1);ErrHandle("epoll_wait",nfds);for (int i = 0;i < nfds;i ++){
/*来自监听文件的客户端连接--------------------------------------------------------------
-------------------------------------------------------------------------*///服务器有可读事件-->有新客户端连接if ((static_cast(events[i].data.ptr))->fd == listen_socket_fd){std::cout << "新连接" << std::endl;temp_info.fd = accept(listen_socket_fd,(struct sockaddr*)&(temp_info.address),&len);epoll1.info_register(&temp_info);//录入客户端文件信息std::cout << temp_info.fd << ":已连接" << std::endl;}
/*客户端发送数据--------------------------------------------------------------
-------------------------------------------------------------------------*/else //已有的连接{//传入一个epoll_info*指针,一个缓冲区,一个自定义函数,后面填参数epoll1.Epoll_EventHandle(static_cast(events[i].data.ptr),buf,fun_cb);}}}//释放资源close(listen_socket_fd);return 0;
}

在sock_init()中完成对网络的绑定,监听,等待连接。epoll1.info_register(&temp_info,true);

注册套接字文件,将套接字信息存入创建中间信息体temp_info中,将其作为参数传入,其他文件不需要填第二个参数,只有网络监听套接字需要填true。在下面的源码中,我们将信息传入指针epoll_info* p,并申请data.ptr的空间,在这里void *epoll_event data.ptr存储自定义结构体epoll_info的数据。通过epoll_ctl()完成注册。监听的文件句柄与开辟的空间指针会被记录到链表中。

除了fd,epoll_fd外,其他结构体成员是可以用户自定义的,比如struct sockaddr_in,用于辅助处理网络地址信息,但实际上,此成员并不参与内部逻辑,由用户自己来决定是否使用它。

 // 客户端信息typedef struct{struct sockaddr_in address;//存放网络ip,分配端口int fd;int epoll_fd;}epoll_info;
 epoll1.info_register(&temp_info,true);//录入客户端文件信息
//源文件
void EpollManager::info_register(epoll_info *pclient_info,bool is_sockt)
{//信息录入内核int temp_fd = 0;struct epoll_event epoll_e;epoll_info* p;p = new epoll_info;//为epoll文件申请空间if (p == nullptr)std::cout << "register:memory error" << std::endl;/*记录epoll信息*/*p = *pclient_info;epoll_e.events = EPOLLIN | EPOLLET;//边缘触发,输入事件epoll_e.data.ptr = static_cast(p);/*注册epoll成员*/temp_fd = epoll_ctl(epoll_fd,EPOLL_CTL_ADD,p->fd,&epoll_e);if (temp_fd < 0){perror("epoll_e:ctl");close(epoll_fd);delete p;delete sock_ptr;exit(1);}list_append(list,p->fd,(void*)p);//索引信息录入链表if (is_sockt)//此地址为套接字申请,在析构时释放sock_ptr = p;
}

处理

在注册步骤中,data.ptr->fd存储了我们的文件句柄,将events[i].data.ptr转一下数据类型,判断fd是否是套接字文件,如果是则说明有新的客户端连接,否则说明是已经注册的客户端发来了数据等待处理,看下一步。

            //服务器有可读事件-->有新客户端连接if ((static_cast(events[i].data.ptr))->fd == listen_socket_fd){std::cout << "新连接" << std::endl;temp_info.fd = accept(listen_socket_fd,(struct sockaddr*)&(temp_info.address),&len);epoll1.info_register(&temp_info);//录入客户端文件信息std::cout << temp_info.fd << ":已连接" << std::endl;}
/*客户端发送数据--------------------------------------------------------------
-------------------------------------------------------------------------*/else //已有的连接{//传入一个epoll_info*指针,一个缓冲区,一个自定义函数,后面填参数epoll1.Epoll_EventHandle(static_cast(events[i].data.ptr),buf,fun_cb);}

事件处理与自定义函数

数据处理函数Epoll_EventHandle(),它是一个变参函数,第一个参数是epoll_info*,它会从中获取信息,第二个参数是主函数中定义的缓冲区buf,注意,缓冲区需要初始化长度 buf(Buf_Size,0);第三个参数是自定义的函数指针fun_cb,由使用者更具自己的实际需求自己实现,后面还能填不限数量类型的参数传递给自定函数,在这个例子中不需要传参。自定义函数(名字自定义)fun_cb(epoll_info* info, std::string& buf),前两个参数定义必须是epoll_info*  ;std::string& ;后续参数可自定义。

在这里,buf中存储者客户端,或者是监听文件写入的数据,在我的处理中,将显示:文件句柄:数据,并向监听文件也就是客户端发送“OK”。用户可以通过自定义结构体 epoll_info 来做一些其他的操作,比如回显,输出地址ip等。

//自定义数据处理函数
void fun_cb(epoll_info* info, std::string& buf)
{std::cout << info->fd << ":" << buf << std::endl;write(info->fd,"OK",3);
}
//数据处理函数
epoll1.Epoll_EventHandle(static_cast(events[i].data.ptr),buf,fun_cb);

可变参处理函数原型

这是一个变参函数,通过 万能引用 可以传入左值或右值,以 完美转发 确保保留参数原本的性质。

首先通过信息体读取文件信息,并作出错误处理。在else{}中调用使用者传入的函数指针,第一个参数pclient_info:用户通过此参数调用一下自定义的性质,data:存储着文件接收到的信息。因为这两个参数传入了f(),所以,第一个和第二个参数固定为epoll_info*,std::string &buf。

std::forward<Args>(args)...:通过完美转发保存 参数的性质,通过此功能,用户在上面的fun_cb()的例子中可定义多种参数使用。

template
void EpollManager::Epoll_EventHandle(epoll_info *pclient_info,std::string &buf,F &&f,Args&&... args)
{int count = read(pclient_info->fd,&buf[0],buf.capacity()-1);if (count < 0){perror("Epoll_EventHandle");epoll_ctl(pclient_info->epoll_fd, EPOLL_CTL_DEL, pclient_info->fd, NULL);//移出等待序列close(pclient_info->fd);//关闭客户端文件node_free(list,pclient_info->fd);//释放记录链表节点delete pclient_info;//释放内存pclient_info = nullptr;std::cout << "读取错误" << std::endl;}else if (count == 0){std::cout << pclient_info->fd << ":已下线。资源已回收" << std::endl;epoll_ctl(pclient_info->epoll_fd, EPOLL_CTL_DEL, pclient_info->fd, NULL);//移出等待序列close(pclient_info->fd);node_free(list,pclient_info->fd);//释放记录链表节点delete pclient_info;//释放资源pclient_info = nullptr;}else{// 创建一个只包含有效数据的子字符串std::string data = buf.substr(0, count);f(pclient_info, data, std::forward(args)...);}
}

实验结果

在这里,Linux本地使用一个客户端连接,win下使用一个客户端连接,在自定义消息处理中,服务器输出:fd:内容并回发“OK”,这里是符合自定义处理函数fun_cb()的。断开连接,资源正确回收。

这里就是此epoll封装的介绍了,如果对你有用的话点个赞吧。

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

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

相关文章

Token快过期的三种续期方案 - 详解

Token快过期的三种续期方案 - 详解pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco&…

重组蛋白科研试剂技术综述:结构特性、功能机制与实验体系应用

重组蛋白作为生命科学研究的核心科研试剂,已成为现代实验体系中不可或缺的基础材料。它们通过基因工程技术表达、纯化而成,具有结构明确、批次稳定、功能可控等特点,使科研人员能够在体外模拟体内信号环境,研究细胞…

linux c 命令

当然可以!你提到的是“Linux C 命令”,这可能是指在 Linux 系统中使用 C 语言编写的命令或脚本,或者是使用 C 语言进行系统调用、进程管理、文件操作等。以下是一些常见的 Linux C 命令和相关功能的解释,帮助你更好…

日总结 28

Java C/S 架构的开发与打包为.exe: 一、Java C/S 架构开发核心 架构拆分 客户端:负责 UI 交互(Swing/JavaFX)、本地逻辑处理、网络请求发送。 服务器端:处理核心业务逻辑(Spring Boot/Netty)、数据存储(MySQL/…

游戏联运模式与统一包模式

游戏联运模式与统一包模式是移动游戏发行中两种常见的分发和合作方式,它们在合作机制、技术实现、收益分配、运营控制等方面存在显著差异。 下面是对这两种模式的详细对比: 一、游戏联运模式(联合运营) 1. 定义 联…

游戏统一包模式下活动营销系统后续的发展方向

在游戏统一包模式(即使用单一官方安装包、不依赖传统联运渠道SDK)日益普及的背景下,渠道侧的活动营销系统正面临深刻转型。虽然统一包削弱了渠道对用户账号、支付和数据的直接控制,但渠道仍可通过新的方式参与游戏…

taptap以官包模式下如何开展营销活动

在 TapTap 以官包(官方包)模式 下开展营销活动,是当前许多重视用户资产、追求高毛利和品牌自主权的游戏厂商(如米哈游、鹰角、库洛等)的首选策略。TapTap 本身定位为“不联运、不分成、去渠道化”的游戏分发与社区…

实用指南:AI: 生成Android自我学习路线规划与实战

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

Jupyter/IPython 魔法命令列表

Jupyter/IPython 魔法命令列表安装依赖命令# 性能分析工具 %pip install line_profiler %pip install memory_profiler# 加载扩展 %load_ext line_profiler %load_ext memory_profiler常用组合# 性能测试组合 %timeit …

《算法设计与分析》第三章学习记录

1.按照动态规划法的求解步骤分析作业题目“数字三角形”: 1.1设a[][]=三角形第i行第j列的值(0<=i<n,0<=j<=i) 定义:dp[i][j]=从(i,j)出发到底边的最大路径和 递归方程式:dp[i][j] = a[i][j] + max(dp[i…

第29天(中等题 二分查找)

打卡第二十九天 2道中等题题目:思路:二分查找 代码: class Solution { public:int smallestDivisor(vector<int>& nums, int threshold) {auto check = [&] (int m) -> bool{int sum = 0;for(int x :…

#题解#洛谷 P3029 Cow Lineup S #双指针#离散化#

P3029 [USACO11NOV] Cow Lineup S - 洛谷 分析离散化,双指针代码实现 #include<bits/stdc++.h> using namespace std; const int N = 1e5+10; struct cow {int pos, x; } a[N]; bool cmp(cow x, cow y) {return…

题解:AtCoder ARC192D Fraction Line

一些记号 下文中令 \(d_p(x)=\max\limits_{k\in\mathbb{N},p^k\mid x}k\)。 题意 对于 \(x\in\mathbb{Q}^{+}\),设 \(x=\dfrac{p}{q}\),其中 \(p,q\) 为互质正整数,令 \(f(x)=pq\)。给定长度为 \(n-1\) 的序列 \(a\…

Linux如何安装利用Rust指南

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

tryhackme-网络安全基础-网络- 网络概念-24

tryhackme-Cyber Security 101-Networking-Networking Concepts 房间地址:https://tryhackme.com/room/networkingconcepts 这是网络安全入门的基础模块的计算机科学基础知识:网络概念,序号 01 表示第一篇文章,当你…

省赛前记不住的数学知识

裴蜀定理 对于 \(S=\sum\limits_{i=1}^{n}a_ix_i\) 有解的充要条件为 \(\gcd(a_1,a_2,\cdots,a_n)\mid S\)。 拉格朗日插值 \[f(x)=\sum_i\big( \prod_{j\ne i}\dfrac{x-x_j}{x_i-x_j}\big)y_i \]扩展欧拉定理 \[a^b \…

如何创建你的百Google度!!(实现双搜索引擎页面)

创建双搜索引擎页面 百Google度的网站被封了,但!!!这不影响我们创建属于自己的双搜索引擎页面! 提前准备 找到你想添加的俩个搜索引擎对应的URI 和 它预先定义用于存储搜索关键词的参数名。打开你想要的搜索引擎的…

P7152 [USACO20DEC] Bovine Genetics G

首先有一个 \(O(n^2)\) 的 dp。 设计状态 \(f_{i,0/1/2/3}\) 表示前 \(i\) 个字符以 \(A,C,G,T\) 中哪一个结尾的方案数。 \(f_{i,x}=\sum_j \sum_y f_{j,y}w(i,j,x,y)\)

如何在ISA-95体系中采用Apache Camel + MQTT Broker衔接L3与L4 Legacy应用

如何在ISA-95体系中采用Apache Camel + MQTT Broker衔接L3与L4 Legacy应用2025-11-18 21:07 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: …

11月18日日记

1.今天上工程实训课做方形盒子 2.明天学习马哲 3.Tomcat 10 与 Tomcat 9 的核心区别?Servlet API 版本如何适配?