0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

Epoll封装类实现

科技绿洲 来源:Linux开发架构之路 作者:Linux开发架构之路 2023-11-13 11:54 次阅读

关于epoll的原理,以及和poll、select、IOCP之间的比较,网上的资料很多,这些都属于I/O复用的实现方法,即可以同时监听发生在多个I/O端口(socket套接字描述符或文件描述符)的事件,并将事件从内核通知到用户区,实现对特定事件的响应处理,而epoll可认为是poll的改进版,在多个方面大幅度提高了性能(当然也是在监听描述符多、活跃描述符少的条件下)。

epoll的主要特点有以下几点:

  • 1.支持一个进程打开最大数目的socket描述符,通常数目只受限于系统内存;
  • 2.IO效率不随FD数目的增加而下降,它只对“活跃”的socket进行操作;
  • 3.使用内存映射加速内核与用户空间的消息传递。

这里只是简单介绍了epoll的几个重要特征,总之,epoll的高性能使其在服务器网络连接层开发中应用的很广泛,包括很多开源的服务器框架底层也采用了epoll。下面我们主要来设计实现一个epoll操作封装类。

首先说明一下,epoll主要三个操作函数:

  1. int epoll_create(int size);

创建一个epoll的句柄。自从linux2.6.8之后,size参数是被忽略的。需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。

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

epoll的事件注册函数,它不同于select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。

第一个参数是epoll_create()的返回值。

第二个参数表示动作,用三个宏来表示:

EPOLL_CTL_ADD:注册新的fd到epfd中;

EPOLL_CTL_MOD:修改已经注册的fd的监听事件;

EPOLL_CTL_DEL:从epfd中删除一个fd;

第三个参数是需要监听的fd。

第四个参数是告诉内核需要监听什么事件,struct epoll_event结构如下:

typedef union epoll_data
{
pointer ptr;
int fd;
uint u32;
uint64 u64;
} epoll_data_t;

struct epoll_event
{
uint events; /* Epoll events */
epoll_data_t data; /* User data variable */
};

events可以是以下几个宏的集合:

  • EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
  • EPOLLOUT:表示对应的文件描述符可以写;
  • EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
  • EPOLLERR:表示对应的文件描述符发生错误;
  • EPOLLHUP:表示对应的文件描述符被挂断;
  • EPOLLET:将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
  • EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里。

epoll_data_t是一个联合结构,64位大小,可以存fd。这里具体实现中我们存一个CEpollObject对象的指针,以确保epoll_wait从网络中接收到的消息确实是我们通过一个CEpollObject对象监听到的。

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

收集在epoll监控的事件中已经发送的事件。参数events是分配好的epoll_event结构体数组,epoll将会把发生的事件赋值到events数组中(events不可以是空指针,内核只负责把数据复制到这个events数组中,不会去帮助我们在用户态中分配内存)。maxevents告之内核这个events有多大,这个 maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。如果函数调用成功,返回对应I/O上已准备好的文件描述符数目,如返回0表示已超时。

Epoll封装类实现

设计思想:通过一个模板类实现向Epoll注册、修改和删除事件等操作,需要使用epoll类都必须走这个模块类,类似一种委托的功能回调模板类实例化对象的epoll监听事件响应处理操作,主要实现类:CEpollObjectInf、CEpoll和CEpollObject模板类。

CEpollObjectInf类的实现

主要功能:表达epoll_data_t的存储内容,以及提供对epoll_wait监听到的事件提供响应处理接口

实现代码:

#include < sys/socket.h >
#include < netinet/in.h >
#include < arpa/inet.h >
#include < sys/epoll.h >
#define INVAILD_SOKET (~0)
class CEpoll;
class CEpollObjectInf
{
    friend class CEpoll;
    protected:
        //这两个变量为CEpoll的WaitAndEvent中做对象合法检验,
        //如果是64位系统,则不要SOCKET变量
        CEpoll *m_pstEpoll;
        SOCKET m_iSocket;
    public:
        CEpollObjectInf()
            :m_pstEpoll(NULL),
            m_iSocket(INVAILD_SOKET )
            {}
        virtual CEpollObjectInf(){}
    protected:
        virtual void OnEpollEvent(int iEvent) = 0;
}

CEpoll类的实现

主要功能:封装epoll的各项操作

代码实现:

#define UINT64_MAKE(high, low) ((uint64)(((unsigned int)((low) & 0xFFFFFFFF)) | ((uint64)((unsigned int)((high) & 0xFFFFFFFF))) < < 32))
#define UINT64_LOW(i) ((unsigned int)((uint64)(i) & 0xFFFFFFFF))
#define UINT64_HIGH(i) ((unsigned int)((uint64)(i) > > 32))
class CEpoll
{
    public:
        CEpoll()
            :m_kdpfd(0),
             m_size(0),
             m_iWaitSize(0),
             m_astEvents(0)
        {}
        virtual ~CEpoll()
        {
            Exit();
        }
    public:
        //初始化
        int Init(int iEpollSize, int iWaitSize)
        {
            m_size = iEpollSize;
            m_iWaitSize = iWaitSize;
            m_astEvents = new epoll_event[m_iWaitSize];
            if(!m_astEvents)
                return -1;
            m_kdpfd = epoll_create(m_size);
            if(m_kdpfd < 0)
                return -2;
            return 0;
        }
        /**
        *等待时间发生或超时
        *iTimeout 等待的超时时限单位毫秒
        *return < 0 表示出错 =0表示超过时间 >0 表示收到并处理的事件个数
        **/
        int Wait(int iTimeOut)
        {
            return epoll_wait(m_kdpfd,m_astEvents,m_iWaitSize,iTimeOut);
        }
        /**
        *等待事件发生或超时,并调用方法
        *iTimeOut 等待超时时限,单位毫秒
        *return < 0 表示出错 =0 表示没有 >0 表示收到并处理的事件个数
        */
        int WaitAndEvent(int iTimeOut)
        {
            int iEventCount = Wait(iTimeOut);
            if(iEventCount < 0)
            {
                return iEventCount;
            }
            else if(iEventCount == 0) // 超时
            {
                return 0;
            }

            //一次最多处理1000个事件
            for(int i = 0;i < iEventCount && i < 1000; ++i)
            {
                //在64位系统下uData只能存放一个指针
                uint64 uData = GetData(i);
            #ifdef BIT64
                CEpollObjectInf *pstObjectPos = (CEpollObjectInf *)uData;
            #else
                CEpollObjectInf *pstObjectPos = (CEpollObjectInf *)(UINT64_LOW(uData));
            #endif

                uint uiEvent = GetEvent(i); //  event
                //判断对象是否合法
                if(pstObjectPos == NULL || pstObjectPos- >m_pstEpoll != this)
                {
                    //不处理本次事件,继续处理下一个事件
                    continue;
                }
                pstObjectPos- >OnEpollEvent(uiEvent);
            }
            return iEventCount;
        }

        uint64 GetData(int i) const
        {
            ASSERT(i < m_iWaitSize)
            return m_astEvents[i].data.u64;
        }

        uint GetEvent(int i) const
        {
            ASSERT(i < m_iWaitSize)
            return m_astEvents[i].events;
        }

        static bool IsInputEvent(int iEvent)
        {
            return (iEvent & EPOLLIN) != 0;
        }
        static bool IsOutputEvent(int iEvent) 
        { 
            return (iEvent & EPOLLOUT) != 0; 
        }
        static bool IsCloseEvent(int iEvent) 
        { 
            return (iEvent & (EPOLLHUP|EPOLLERR)) != 0; 
        }

        int Add(SOCKET s, uint64 data, uint event)
        {
            m_stEvent.events = event|EPOLLERR|EPOLLHUP;
            m_stEvent.data.u64 = data;
            int iRet = epoll_ctl(m_kdpfd,EPOLL_CTL_ADD,s,&m_stEvent);
            return iRet;
        }
        int Del(SOCKET s, uint64 data = 0, uint event = EPOLLIN)
        {
            m_stEvent.events = 0;
            m_stEvent.data.u64 = data;
            int iRet = epoll_ctl(m_kdpfd,EPOLL_CTL_DEL,s,&m_stEvent);
            return iRet;
        }
        int Mod(SOCKET s, uint64 data, uint event)
        {
            m_stEvent.events   = event|EPOLLERR|EPOLLHUP;
            m_stEvent.data.u64 = data;
            int iRet = epoll_ctl(m_kdpfd,EPOLL_CTL_MOD,s,&m_stEvent);
            return iRet;
        }

     protected:
        void Exit()
        {
            if(m_astEvents)
            {
               delete []m_astEvents;
               m_astEvents = 0;
            }
            if(m_kdpfd > 0)
            {
               close(m_kdpfd);
               mkdpfd = 0;
            }
       }
    protected:
        int                m_kdpfd;
        int                m_size;
        int                 m_iWaitSize;
        struct epoll_event *m_astEvents;
        struct epoll_event m_stEvent;

};

CEpollObject模版类的实现

主要功能:托管CEpoll类的具体操作,注册事件到Epoll,必须实例化CEpollObject模版类,并覆盖实现具体的事件处理函数,以回调不同对象对事件的处理函数。

代码实现:

template< typename Owner >        
class CEpollObject: public CEpollObjectInf
{
    friend class CEpoll;
    public:
        typedef void (Owner::*PF_EPOLL_EVENT)(CEpollObject *pstObject,Socket iSocket, int iEvnet);
    protected:
        Owner                  *m_pstOwner;
        PF_EPOLL_EVENT         m_pfEvent;
        unsigned int        m_iRegEvent;
    public:
        CEpollObject()
            :m_pstOwner(NULL),
             m_pfEvent(NULL),
             m_iRegEvent(0)
        {}
        virtual ~CEpollObject() {Unregister();}

        /**
        *注册到Epoll中
        **/
        int Register(Owner &stOwner, PF_EPOLL_EVENT pfEvent,CEpoll &stEpoll,SOCKET iSocket, unsigned int iRegEvent)
{
            ASSERT(iSocket != INVALID_SOCKET && iRegEvent > 0 && pfEvent != NULL);
            int iRet = Unregister();
            if(iRet)
                return iRet;
            m_pstOwner = &stOwner;
            m_pstEpoll = &stEpoll;
            m_pfEvent  = pfEvent;
            m_iRegEvent = iRegEvent;
            m_iSocket   = iSocket;

            uint64 uData = CreateData(m_iSocket);
            iRet = m_pstEpoll- >Add(m_iSocket,uData,m_iRegEvent);
            return iRet;
        }

        /**
        *更改关注的事件
        **/
        int ModRegEvent(int iRegEvent)
{
            m_iRegEvent = iRegEvent;
            if(m_pstEpoll)
            {
                uint64 uData = CreateData(m_iSocket);
                return m_pstEpoll- >Mod(m_iSocket,uData,m_iRegEvent);
            }
            return 0;
        }

    protected:
        virtual void OnEpollEvent(int iEvent)
{
            ASSERT(m_pstOwner != NULL && m_pfEvent != NULL);
            (m_pstOwner- >*m_pfEvent)(this,m_iSocket,iEvent);
        }

        int Unregister()
{
            int iRet = 0;
            if(m_pstEpoll)
            {
                iRet = m_pstEpoll- >Del(m_iSocket);
                m_pstEpoll = NULL;
            }
            m_pstOwner = NULL;
            m_pfEvent = NULL;
            m_iRegEvent = 0;
            m_iSocket = INVALID_SOCKET;
            return iRet;
        }
        uint64 CreateData(SOCKET iSocket)
{
        #ifdef BIT64
            return (uint64)this;
        #else
            return UINT64_MAKE(iSocket, (unsigned int)this);
        #endif
        }
};
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 封装
    +关注

    关注

    124

    文章

    7286

    浏览量

    141111
  • 服务器
    +关注

    关注

    12

    文章

    8129

    浏览量

    82572
  • 端口
    +关注

    关注

    4

    文章

    822

    浏览量

    31612
  • epoll
    +关注

    关注

    0

    文章

    28

    浏览量

    2915
收藏 人收藏

    评论

    相关推荐

    epoll的使用

    以下内容是参考华清远见《linux/unix系统编程手册》对epoll的一个个人总结,是我在华清远见比较全面的总结。一、epoll的优点同I/O多路复用和信号驱动I/O一样,linux的epoll
    发表于 05-11 13:22

    我读过的最好的epoll讲解

    认为O(1)的[更新 1]) 在讨论epoll实现细节之前,先把epoll的相关操作列出[更新 2]:·epoll_create 创建一个epol
    发表于 05-12 15:30

    epoll使用方法与poll的区别

    因为epoll的触发机制是在内核中直接完成整个功能 那个事件准备就绪我就直接返回这个IO事件
    发表于 07-31 10:03

    epoll_wait的事件返回的fd为错误是怎么回事?

    netlink 的 socket 连接 的 fd 为18,但是添加到epollepoll_wait()返回的fd 为 0为什么会出现这样的现象?补充 说明:1、 epoll_wait返回
    发表于 06-12 09:03

    揭示EPOLL一些原理性的东西

    我们对这些流的操作都是有意义的。(复杂度降低到了O(1))在讨论epoll实现细节之前,先把epoll的相关操作列出:epoll_create 创建一个
    发表于 08-24 16:32

    【米尔王牌产品MYD-Y6ULX-V2开发板试用体验】socket通信和epoll

    。如果客端连接断开后,主服务端也就断开。学习了博客园的@liangf27的帖子来实现单线程服务多个客户端。修改main.c代码如下:#include <stdio.h>
    发表于 11-10 15:31

    关于Epoll,你应该知道的那些细节

    Epoll,位于头文件sys/epoll.h,是Linux系统上的I/O事件通知基础设施。epoll API为Linux系统专有,于内核2.5.44中首次引入,glibc于2.3.2版本加入支持。其它提供类似的功能的系统,包括F
    发表于 05-12 09:25 1028次阅读

    poll&&epollepoll实现

    poll&&epollepoll实现
    发表于 05-14 14:34 2646次阅读
    poll&&<b class='flag-5'>epoll</b>之<b class='flag-5'>epoll</b><b class='flag-5'>实现</b>

    Linux中epoll IO多路复用机制

    epoll 是Linux内核中的一种可扩展IO事件处理机制,最早在 Linux 2.5.44内核中引入,可被用于代替POSIX select 和 poll 系统调用,并且在具有大量应用程序请求时能够
    发表于 05-16 16:07 591次阅读
    Linux中<b class='flag-5'>epoll</b> IO多路复用机制

    深度剖析Linux的epoll机制

    在 Linux 系统之中有一个核心武器:epoll 池,在高并发的,高吞吐的 IO 系统中常常见到 epoll 的身影。 IO 多路复用 在 Go 里最核心的是 Goroutine ,也就是所谓
    的头像 发表于 07-29 10:52 1228次阅读

    一文详解epoll实现原理

    本文以四个方面介绍epoll实现原理,1.epoll的数据结构;2.协议栈如何与epoll通信;3.epoll线程安全如何加锁;4.ET与
    的头像 发表于 08-01 13:28 3485次阅读

    epoll实现多路复用

    本人用epoll实现多路复用,epoll触发模式有两种: ET(边缘模式) LT(水平模式) LT模式 是标准模式,意味着每次epoll_wait()返回后,事件处理后,如果之后还有
    的头像 发表于 11-09 10:15 217次阅读
    用<b class='flag-5'>epoll</b>来<b class='flag-5'>实现</b>多路复用

    epoll实现原理

    今儿我们就从源码入手,来帮助大家简单理解一下 epoll实现原理,并在后边分析一下,大家都说 epoll 性能好,那到底是好在哪里。 epoll 简介 1、
    的头像 发表于 11-09 11:14 237次阅读
    <b class='flag-5'>epoll</b> 的<b class='flag-5'>实现</b>原理

    epoll的基础数据结构

    一、epoll的基础数据结构 在开始研究源代码之前,我们先看一下 epoll 中使用的数据结构,分别是 eventpoll、epitem 和 eppoll_entry。 1、eventpoll 我们
    的头像 发表于 11-10 10:20 340次阅读
    <b class='flag-5'>epoll</b>的基础数据结构

    epoll源码分析

    对上述4个函数进行源码分析。 源码来源 由于epoll实现内嵌在内核中,直接查看内核源码的话会有一些无关代码影响阅读。为此在GitHub上写的简化版TCP/IP协议栈,里面实现epoll
    的头像 发表于 11-13 11:49 527次阅读
    <b class='flag-5'>epoll</b>源码分析