基于epoll的io复用管理,一种文件监听方案 1

        本文将介绍一个基于 epoll 的封装库(仓库链接:https://github.com/12379biu/epoll_manager.git),此封装旨在简化 epoll 监听文件描述符的操作流程。通过封装,将 epoll 的底层细节与业务逻辑解耦,更专注于文件(或 socket 等)的实际操作。

以下代码将先介绍 epoll 的核心特性,再结合这个封装库的使用示例(监听标准输入、实现多人复用服务器),展示如何通过封装快速实现 epoll 功能。

阅读本文需要一定的 Linux IO 编程和网络编程基础。

本章节以监听标准输入流为例,下一章以io复用多人服务器为例子介绍,之后会介绍源码。

目录

epoll介绍

struct epoll_event

API介绍

文件监听例子


epoll介绍

struct epoll_event

struct epoll_event是epoll的核心--------既可以作为事件注册的容器,也可以以数组的形式作为储存事件的容器。
成员events储存着事件的性质与触发方式。成员data是一个 联合体,内部有一个fd句柄和一个万能指针 void* ptr,这意味着我们可以传入任意类型的数据类型,注意,data是联合体,故而ptr和fd最好只使用其中一个,修改其中一个数据必然会破环另一个数据的结构,只监控文件建议使用fd,如果要传递其他数据则使用ptr。

struct epoll_event {

    __uint32_t events; // 事件类型

    epoll_data_t data; // 用户数据

};



typedef union epoll_data {

    void *ptr; // 用户自定义指针

    int fd; // 文件描述符

    __uint32_t u32; // 用户自定义 32 位数据
    
    __uint64_t u64; // 用户自定义 64 位数据

} epoll_data_t;

接下来,我会先介绍这个封装库提供的 API 函数,再通过两个实例(监听标准输入、实现多人复用网络服务器),展示如何利用此封装快速实现 epoll 监听逻辑,减少重复的底层调用,让开发者更专注于文件事件与数据本身的操作。

API介绍

 // 客户端信息
 typedef struct{
    //此成员由用户自定义,这里作为网络ip地址结构体使用,修改不影响文件操作
    struct sockaddr_in address;//存放网络ip,分配端口
    int fd;//<----存放监听文件的句柄
    int epoll_fd;//<-----存放监听者epoll的句柄
 }epoll_info;  

//epoll封装的类
 class EpollManager
 {
private:
    int epoll_fd; //epoll注册的fd句柄
    plist_t list; //储存信息索引的链表
    epoll_info *sock_ptr; //专门为网络套接字准备的指向epoll_event.data.ptr的指针
public:

    //创建链表与epoll组件
    EpollManager(){
        list = list_create();
        epoll_fd = epoll_create1(0);
    }

    //获取epoll组件句柄
    int get_epoll_fd(){return epoll_fd;}
    //注册文件信息
    void info_register(epoll_info *pclient_info,bool is_sockt = false);
    //变参回调函数,由用户自定义数据处理的方案
    template<class F,class... Args>
    void Epoll_EventHandle(epoll_info *pclient_info,std::string &buf,
                           F &&f,Args&&...  args);

    //释放索引链表与套接字的资源                       
    ~EpollManager(){
        if (sock_ptr)
            delete sock_ptr;
        
        list_delete(list); 
    }
 };

在这里的结构体epoll_info作为信息体贮存于万能指针 epoll_event.data.ptr 中,epoll_event.data.fd被舍弃,故而fd信息都存储在epoll_info.fd中。struct sockaddr_in address是用户自定义结构体,可以任意修改,这里作为网络地址结构体储存网络信息。事实上,除了fd,和epoll_fd会在内部用到以外,其他所有的结构体都是可以自定义的。

 // 客户端信息
 typedef struct{
    //此成员由用户自定义,这里作为网络ip地址结构体使用,修改不影响文件操作
    struct sockaddr_in address;//存放网络ip,分配端口
    int fd;//<----存放监听文件的句柄
    int epoll_fd;//<-----存放监听者epoll的句柄
 }epoll_info;

在构造函数中创建epoll与信息索引链表list。list作为底层调用处理信息,并不出现于应用层,使用无需了解它,我会在后面的源码解析中介绍它。

void info_register(epoll_info *pclient_info,bool is_sockt = false);将数据信息注册到epoll,第一个参数为epoll_info*,数据由此结构体传递到epoll,第二个参数只有当监听数文件是网络服务器套接字时需要填true,其他时候不需要填。

接下来的API函数我会通过监听一个标准输入的简单例子介绍他们。

文件监听例子

#include "epoll_manager.h"

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <string>


/*自定义信息处理函数---------------------------------------------------------*/
void fun_cb(epoll_info* info, std::string& buf)
{
    std::cout << info->fd << ":" << buf << std::endl;
}

/*-------------------------------------------------------------------------*/
int main(int argc,char** argv)
{
    system("clear");//清理一下端口
    std::string buf(1024,0);//初始化缓冲区,注意需要给定长度
    int nfds = 0;

    EpollManager epoll1;//创建epoll
    struct epoll_event events[3];//epoll事件容器

    epoll_info temp_info = {0};//用于储存中间信息的容器

    temp_info.epoll_fd = epoll1.get_epoll_fd();//获取epoll句柄

    temp_info.fd = 0;//监听标准输入流
    epoll1.info_register(&temp_info);//录入客户端文件信息并注册到epoll
    std::cout << "正在监听标准输入" << std::endl;

    while(1)
    {
        nfds = epoll_wait(epoll1.get_epoll_fd(),events,3,-1);//监听的事件数量
        for (int i = 0;i < nfds;i ++)
        {
            //数据处理,参数(epoll_info*,string,fun,...[任意可变参])
            epoll1.Epoll_EventHandle(static_cast<epoll_info*>(events[i].data.ptr),buf,fun_cb);
        }
    }

    return 0;
}

在这个例子中,创建epoll组件,事件容器events[3]:一次能处理3个事件,信息结构体:temp_info ,作为参数传给API使用。

首先,注册事件到epoll,要监听的文件temp_info.fd = 0,标准输入流文件句柄为0,输出1,标准错误输出2。info_register会将事件注册为边沿触发,输入事件。并为epoll_event.data.ptr开辟一个epoll_info大小的空间。定义缓冲区,注意,缓冲区buf必须初始化明确长度,这里是1024。

当我们输入从屏幕输入数据时,epoll_wait()取消阻塞,调用

void Epoll_EventHandle(epoll_info *pclient_info,
                       std::string &buf, F &&f,Args&&...  args);

第一个参数,消息体的指针,而events[i].data,ptr本身就是我们开辟的消息体,将其强转为(epoll_info*)类型的指针即可,参数2:buf,缓冲区,参数3:由你自定义的函数fun,在这个函数中,由你自己决定要如何去处理数据,参数4,是你传递给你的自定义函数fun的参数,数量是可变的,可以是任意参数,在上面的例子中没有传参。

自定义函数的第一个参数必须是信息体epoll_info *,第二个参数是std::tring& ,后面的是自定义参数,由用户自己决定。在这里,我们直接将监听的文件句柄和输入的信息输出,来看结果。

/*自定义信息处理函数---------------------------------------------------------*/
void fun_cb(epoll_info* info, std::string& buf)
{
    std::cout << info->fd << ":" << buf << std::endl;
}

输入与输出一致,文件句柄0,当标准输入按下ctrl + d,会使文件读取到0个数据,触发结束信号,回收,回收文件资源。ctrl + c结束进程。

如此,便让epoll专注于监听文件本身,简化使用流程,将数据处理分离出来。下一章使用以此封装实现io复用多人网络服务器。如果对你有帮助的话就点个赞吧。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值