zoukankan      html  css  js  c++  java
  • 服务器开发-epoll的封装类实现

          关于epoll编程的介绍,网上看到一个很棒的博客,链接:<url src="http://blog.chinaunix.net/uid-24517549-id-4051156.html"></url>

          而关于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被耗尽。

    2. 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对象监听到的。

    3. 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监听事件响应处理操作,主要实现类:CEpollObjectInfCEpollCEpollObject模板类。

    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
            }
    };
  • 相关阅读:
    移位运算符<<与>>
    在线颜色选择器
    CSS鼠标指针cursor样式
    JavaScript实现自定义右键菜单
    如何去掉ul和li前面的小黑点
    转载:利用本地存储实现记录滚动条的位置
    CSS中样式覆盖优先顺序
    断言类
    MQ发送定时消息
    看代码所学3
  • 原文地址:https://www.cnblogs.com/neyer/p/4528479.html
Copyright © 2011-2022 走看看