精华内容
下载资源
问答
  • epoll反应堆模型

    2017-09-20 19:49:17
    epoll c++ linux 技术探讨,新手必备 epoll c++ linux 技术探讨,新手必备 epoll c++ linux 技术探讨,新手必备
  • epoll原理详解及epoll反应堆模型

    万次阅读 多人点赞 2018-11-08 15:40:03
    三、epoll反应堆模型    设想一个场景:有100万用户同时与一个进程保持着TCP连接,而 每一时刻只有几十个或几百个TCP连接是活跃的(接收TCP包) ,也就是说在每一时刻进程只需要处理这100万连接中的一小部分连接。...

      设想一个场景:有100万用户同时与一个进程保持着TCP连接,而每一时刻只有几十个或几百个TCP连接是活跃的(接收TCP包),也就是说在每一时刻进程只需要处理这100万连接中的一小部分连接。那么,如何才能高效的处理这种场景呢?进程是否在每次询问操作系统收集有事件发生的TCP连接时,把这100万个连接告诉操作系统,然后由操作系统找出其中有事件发生的几百个连接呢?实际上,在Linux2.4版本以前,那时的select或者poll事件驱动方式是这样做的。

      这里有个非常明显的问题,即在某一时刻,进程收集有事件的连接时,其实这100万连接中的大部分都是没有事件发生的。因此如果每次收集事件时,都把100万连接的套接字传给操作系统(这首先是用户态内存到内核态内存的大量复制),而由操作系统内核寻找这些连接上有没有未处理的事件,将会是巨大的资源浪费,然后select和poll就是这样做的,因此它们最多只能处理几千个并发连接。而epoll不这样做,它在Linux内核中申请了一个简易的文件系统,把原先的一个select或poll调用分成了3部分:

    int epoll_create(int size);  
    int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);  
    int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);  
    

    1. 调用epoll_create建立一个epoll对象(在epoll文件系统中给这个句柄分配资源);

    2. 调用epoll_ctl向epoll对象中添加这100万个连接的套接字;

    3. 调用epoll_wait收集发生事件的连接。

      这样只需要在进程启动时建立1个epoll对象,并在需要的时候向它添加或删除连接就可以了,因此,在实际收集事件时,epoll_wait的效率就会非常高,因为调用epoll_wait时并没有向它传递这100万个连接,内核也不需要去遍历全部的连接。

    一、epoll原理详解

      当某一进程调用epoll_create方法时,Linux内核会创建一个eventpoll结构体,这个结构体中有两个成员与epoll的使用方式密切相关,如下所示:

    struct eventpoll {
      ...
      /*红黑树的根节点,这棵树中存储着所有添加到epoll中的事件,
      也就是这个epoll监控的事件*/
      struct rb_root rbr;
      /*双向链表rdllist保存着将要通过epoll_wait返回给用户的、满足条件的事件*/
      struct list_head rdllist;
      ...
    };
    

      我们在调用epoll_create时,内核除了帮我们在epoll文件系统里建了个file结点,在内核cache里建了个红黑树用于存储以后epoll_ctl传来的socket外,还会再建立一个rdllist双向链表,用于存储准备就绪的事件当epoll_wait调用时,仅仅观察这个rdllist双向链表里有没有数据即可。有数据就返回,没有数据就sleep,等到timeout时间到后即使链表没数据也返回。所以,epoll_wait非常高效。

      所有添加到epoll中的事件都会与设备(如网卡)驱动程序建立回调关系,也就是说相应事件的发生时会调用这里的回调方法。这个回调方法在内核中叫做ep_poll_callback,它会把这样的事件放到上面的rdllist双向链表中。

      在epoll中对于每一个事件都会建立一个epitem结构体,如下所示:

    struct epitem {
      ...
      //红黑树节点
      struct rb_node rbn;
      //双向链表节点
      struct list_head rdllink;
      //事件句柄等信息
      struct epoll_filefd ffd;
      //指向其所属的eventepoll对象
      struct eventpoll *ep;
      //期待的事件类型
      struct epoll_event event;
      ...
    }; // 这里包含每一个事件对应着的信息。
    

      当调用epoll_wait检查是否有发生事件的连接时,只是检查eventpoll对象中的rdllist双向链表是否有epitem元素而已,如果rdllist链表不为空,则这里的事件复制到用户态内存(使用共享内存提高效率)中,同时将事件数量返回给用户。因此epoll_waitx效率非常高。epoll_ctl在向epoll对象中添加、修改、删除事件时,从rbr红黑树中查找事件也非常快,也就是说epoll是非常高效的,它可以轻易地处理百万级别的并发连接。
    在这里插入图片描述

    总结】:

      一颗红黑树,一张准备就绪句柄链表,少量的内核cache,就帮我们解决了大并发下的socket处理问题。

    • 执行epoll_create()时,创建了红黑树和就绪链表;

    • 执行epoll_ctl()时,如果增加socket句柄,则检查在红黑树中是否存在,存在立即返回,不存在则添加到树干上,然后向内核注册回调函数,用于当中断事件来临时向准备就绪链表中插入数据;

    • 执行epoll_wait()时立刻返回准备就绪链表里的数据即可。
      在这里插入图片描述

    二、epoll的两种触发模式

      epoll有EPOLLLT和EPOLLET两种触发模式,LT是默认的模式,ET是“高速”模式。

    • LT(水平触发)模式下,只要这个文件描述符还有数据可读,每次 epoll_wait都会返回它的事件,提醒用户程序去操作;

    • ET(边缘触发)模式下,在它检测到有 I/O 事件时,通过 epoll_wait 调用会得到有事件通知的文件描述符,对于每一个被通知的文件描述符,如可读,则必须将该文件描述符一直读到空,让 errno 返回 EAGAIN 为止,否则下次的 epoll_wait 不会返回余下的数据,会丢掉事件。如果ET模式不是非阻塞的,那这个一直读或一直写势必会在最后一次阻塞。

      还有一个特点是,epoll使用“事件”的就绪通知方式,通过epoll_ctl注册fd,一旦该fd就绪,内核就会采用类似callback的回调机制来激活该fd,epoll_wait便可以收到通知。

    在这里插入图片描述

    【epoll为什么要有EPOLLET触发模式?】:

      如果采用EPOLLLT模式的话,系统中一旦有大量你不需要读写的就绪文件描述符,它们每次调用epoll_wait都会返回,这样会大大降低处理程序检索自己关心的就绪文件描述符的效率.。而采用EPOLLET这种边缘触发模式的话,当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据全部读写完(如读写缓冲区太小),那么下次调用epoll_wait()时,它不会通知你,也就是它只会通知你一次,直到该文件描述符上出现第二次可读写事件才会通知你!!!这种模式比水平触发效率高,系统不会充斥大量你不关心的就绪文件描述符。

    总结】:

    • ET模式(边缘触发)只有数据到来才触发不管缓存区中是否还有数据,缓冲区剩余未读尽的数据不会导致epoll_wait返回;

    • LT 模式(水平触发,默认)只要有数据都会触发,缓冲区剩余未读尽的数据会导致epoll_wait返回。

    三、epoll反应堆模型

    【epoll模型原来的流程】:

    epoll_create(); // 创建监听红黑树
    epoll_ctl(); // 向书上添加监听fd
    epoll_wait(); // 监听
    有监听fd事件发送--->返回监听满足数组--->判断返回数组元素--->
    lfd满足accept--->返回cfd---->read()读数据--->write()给客户端回应。
    

    【epoll反应堆模型的流程】:

    epoll_create(); // 创建监听红黑树
    epoll_ctl(); // 向书上添加监听fd
    epoll_wait(); // 监听
    有客户端连接上来--->lfd调用acceptconn()--->将cfd挂载到红黑树上监听其读事件--->
    epoll_wait()返回cfd--->cfd回调recvdata()--->将cfd摘下来监听写事件--->
    epoll_wait()返回cfd--->cfd回调senddata()--->将cfd摘下来监听读事件--->...--->
    

    在这里插入图片描述

    【Demo】:

    #include <stdio.h>
    #include <sys/socket.h>
    #include <sys/epoll.h>
    #include <arpa/inet.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <errno.h>
    #include <string.h>
    #include <stdlib.h>
    #include <time.h>
    
    #define MAX_EVENTS 1024 /*监听上限*/
    #define BUFLEN  4096    /*缓存区大小*/
    #define SERV_PORT 6666  /*端口号*/
    
    void recvdata(int fd,int events,void *arg);
    void senddata(int fd,int events,void *arg);
    
    /*描述就绪文件描述符的相关信息*/
    struct myevent_s
    {
        int fd;             //要监听的文件描述符
        int events;         //对应的监听事件,EPOLLIN和EPLLOUT
        void *arg;          //指向自己结构体指针
        void (*call_back)(int fd,int events,void *arg); //回调函数
        int status;         //是否在监听:1->在红黑树上(监听), 0->不在(不监听)
        char buf[BUFLEN];   
        int len;
        long last_active;   //记录每次加入红黑树 g_efd 的时间值
    };
    
    int g_efd;      //全局变量,作为红黑树根
    struct myevent_s g_events[MAX_EVENTS+1];    //自定义结构体类型数组. +1-->listen fd
    
    
    /*
     * 封装一个自定义事件,包括fd,这个fd的回调函数,还有一个额外的参数项
     * 注意:在封装这个事件的时候,为这个事件指明了回调函数,一般来说,一个fd只对一个特定的事件
     * 感兴趣,当这个事件发生的时候,就调用这个回调函数
     */
    void eventset(struct myevent_s *ev, int fd, void (*call_back)(int fd,int events,void *arg), void *arg)
    {
        ev->fd = fd;
        ev->call_back = call_back;
        ev->events = 0;
        ev->arg = arg;
        ev->status = 0;
        if(ev->len <= 0)
        {
            memset(ev->buf, 0, sizeof(ev->buf));
            ev->len = 0;
        }
        ev->last_active = time(NULL); //调用eventset函数的时间
        return;
    }
    
    /* 向 epoll监听的红黑树 添加一个文件描述符 */
    void eventadd(int efd, int events, struct myevent_s *ev)
    {
        struct epoll_event epv={0, {0}};
        int op = 0;
        epv.data.ptr = ev; // ptr指向一个结构体(之前的epoll模型红黑树上挂载的是文件描述符cfd和lfd,现在是ptr指针)
        epv.events = ev->events = events; //EPOLLIN 或 EPOLLOUT
        if(ev->status == 0)       //status 说明文件描述符是否在红黑树上 0不在,1 在
        {
            op = EPOLL_CTL_ADD; //将其加入红黑树 g_efd, 并将status置1
            ev->status = 1;
        }
        if(epoll_ctl(efd, op, ev->fd, &epv) < 0) // 添加一个节点
            printf("event add failed [fd=%d],events[%d]\n", ev->fd, events);
        else
            printf("event add OK [fd=%d],events[%0X]\n", ev->fd, events);
        return;
    }
    
    /* 从epoll 监听的 红黑树中删除一个文件描述符*/
    void eventdel(int efd,struct myevent_s *ev)
    {
        struct epoll_event epv = {0, {0}};
        if(ev->status != 1) //如果fd没有添加到监听树上,就不用删除,直接返回
            return;
        epv.data.ptr = NULL;
        ev->status = 0;
        epoll_ctl(efd, EPOLL_CTL_DEL, ev->fd, &epv);
        return;
    }
    
    /*  当有文件描述符就绪, epoll返回, 调用该函数与客户端建立链接 */
    void acceptconn(int lfd,int events,void *arg)
    {
        struct sockaddr_in cin;
        socklen_t len = sizeof(cin);
        int cfd, i;
        if((cfd = accept(lfd, (struct sockaddr *)&cin, &len)) == -1)
        {
            if(errno != EAGAIN && errno != EINTR)
            {
                sleep(1);
            }
            printf("%s:accept,%s\n",__func__, strerror(errno));
            return;
        }
        do
        {
            for(i = 0; i < MAX_EVENTS; i++) //从全局数组g_events中找一个空闲元素,类似于select中找值为-1的元素
            {
                if(g_events[i].status ==0)
                    break;
            }
            if(i == MAX_EVENTS) // 超出连接数上限
            {
                printf("%s: max connect limit[%d]\n", __func__, MAX_EVENTS);
                break;
            }
            int flag = 0;
            if((flag = fcntl(cfd, F_SETFL, O_NONBLOCK)) < 0) //将cfd也设置为非阻塞
            {
                printf("%s: fcntl nonblocking failed, %s\n", __func__, strerror(errno));
                break;
            }
            eventset(&g_events[i], cfd, recvdata, &g_events[i]); //找到合适的节点之后,将其添加到监听树中,并监听读事件
            eventadd(g_efd, EPOLLIN, &g_events[i]);
        }while(0);
    
        printf("new connect[%s:%d],[time:%ld],pos[%d]",inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), g_events[i].last_active, i);
        return;
    }
    
    /*读取客户端发过来的数据的函数*/
    void recvdata(int fd, int events, void *arg)
    {
        struct myevent_s *ev = (struct myevent_s *)arg;
        int len;
    
        len = recv(fd, ev->buf, sizeof(ev->buf), 0);    //读取客户端发过来的数据
    
        eventdel(g_efd, ev);                            //将该节点从红黑树上摘除
    
        if (len > 0) 
        {
            ev->len = len;
            ev->buf[len] = '\0';                        //手动添加字符串结束标记
            printf("C[%d]:%s\n", fd, ev->buf);                  
    
            eventset(ev, fd, senddata, ev);             //设置该fd对应的回调函数为senddata    
            eventadd(g_efd, EPOLLOUT, ev);              //将fd加入红黑树g_efd中,监听其写事件    
    
        } 
        else if (len == 0) 
        {
            close(ev->fd);
            /* ev-g_events 地址相减得到偏移元素位置 */
            printf("[fd=%d] pos[%ld], closed\n", fd, ev-g_events);
        } 
        else 
        {
            close(ev->fd);
            printf("recv[fd=%d] error[%d]:%s\n", fd, errno, strerror(errno));
        }   
        return;
    }
    
    /*发送给客户端数据*/
    void senddata(int fd, int events, void *arg)
    {
        struct myevent_s *ev = (struct myevent_s *)arg;
        int len;
    
        len = send(fd, ev->buf, ev->len, 0);    //直接将数据回射给客户端
    
        eventdel(g_efd, ev);                    //从红黑树g_efd中移除
    
        if (len > 0) 
        {
            printf("send[fd=%d], [%d]%s\n", fd, len, ev->buf);
            eventset(ev, fd, recvdata, ev);     //将该fd的回调函数改为recvdata
            eventadd(g_efd, EPOLLIN, ev);       //重新添加到红黑树上,设为监听读事件
        }
        else 
        {
            close(ev->fd);                      //关闭链接
            printf("send[fd=%d] error %s\n", fd, strerror(errno));
        }
        return ;
    }
    
    /*创建 socket, 初始化lfd */
    
    void initlistensocket(int efd, short port)
    {
        struct sockaddr_in sin;
    
        int lfd = socket(AF_INET, SOCK_STREAM, 0);
        fcntl(lfd, F_SETFL, O_NONBLOCK);                //将socket设为非阻塞
    
        memset(&sin, 0, sizeof(sin));               //bzero(&sin, sizeof(sin))
        sin.sin_family = AF_INET;
        sin.sin_addr.s_addr = INADDR_ANY;
        sin.sin_port = htons(port);
    
        bind(lfd, (struct sockaddr *)&sin, sizeof(sin));
    
        listen(lfd, 20);
    
        /* void eventset(struct myevent_s *ev, int fd, void (*call_back)(int, int, void *), void *arg);  */
        eventset(&g_events[MAX_EVENTS], lfd, acceptconn, &g_events[MAX_EVENTS]);    
    
        /* void eventadd(int efd, int events, struct myevent_s *ev) */
        eventadd(efd, EPOLLIN, &g_events[MAX_EVENTS]);  //将lfd添加到监听树上,监听读事件
    
        return;
    }
    
    int main()
    {
        int port=SERV_PORT;
    
        g_efd = epoll_create(MAX_EVENTS + 1); //创建红黑树,返回给全局 g_efd
        if(g_efd <= 0)
                printf("create efd in %s err %s\n", __func__, strerror(errno));
        
        initlistensocket(g_efd, port); //初始化监听socket
        
        struct epoll_event events[MAX_EVENTS + 1];  //定义这个结构体数组,用来接收epoll_wait传出的满足监听事件的fd结构体
        printf("server running:port[%d]\n", port);
    
        int checkpos = 0;
        int i;
        while(1)
        {
        /*    long now = time(NULL);
            for(i=0; i < 100; i++, checkpos++)
            {
                if(checkpos == MAX_EVENTS);
                    checkpos = 0;
                if(g_events[checkpos].status != 1)
                    continue;
                long duration = now -g_events[checkpos].last_active;
                if(duration >= 60)
                {
                    close(g_events[checkpos].fd);
                    printf("[fd=%d] timeout\n", g_events[checkpos].fd);
                    eventdel(g_efd, &g_events[checkpos]);
                }
            } */
            //调用eppoll_wait等待接入的客户端事件,epoll_wait传出的是满足监听条件的那些fd的struct epoll_event类型
            int nfd = epoll_wait(g_efd, events, MAX_EVENTS+1, 1000);
            if (nfd < 0)
            {
                printf("epoll_wait error, exit\n");
                exit(-1);
            }
            for(i = 0; i < nfd; i++)
            {
    		    //evtAdd()函数中,添加到监听树中监听事件的时候将myevents_t结构体类型给了ptr指针
                //这里epoll_wait返回的时候,同样会返回对应fd的myevents_t类型的指针
                struct myevent_s *ev = (struct myevent_s *)events[i].data.ptr;
                //如果监听的是读事件,并返回的是读事件
                if((events[i].events & EPOLLIN) &&(ev->events & EPOLLIN))
                {
                    ev->call_back(ev->fd, events[i].events, ev->arg);
                }
                //如果监听的是写事件,并返回的是写事件
                if((events[i].events & EPOLLOUT) && (ev->events & EPOLLOUT))
                {
                    ev->call_back(ev->fd, events[i].events, ev->arg);
                }
            }
        }
        return 0;
    }
    

    参考:https://blog.csdn.net/qq_36359022/article/details/81355897
    https://blog.csdn.net/weixin_40204595/article/details/83213332
    http://www.cnblogs.com/pluser/p/epoll_principles.html
    https://www.cnblogs.com/aspirant/p/9166944.html

    展开全文
  • **epoll接口**是为解决Linux内核处理大量文件描述符而提出的方案。该接口属于Linux下**多路I/O复用接口**中select/poll的增强。其经常应用于Linux下高并发服务型...(4) epoll反应堆模型 (重点,Libevent库的核心思想)

    epoll接口是为解决Linux内核处理大量文件描述符而提出的方案。该接口属于Linux下多路I/O复用接口中select/poll的增强。其经常应用于Linux下高并发服务型程序,特别是在大量并发连接中只有少部分连接处于活跃下的情况 (通常是这种情况),在该情况下能显著的提高程序的CPU利用率。

    epoll采用的是事件驱动,并且设计的十分高效。在用户空间获取事件时,不需要去遍历被监听描述符集合中所有的文件描述符,而是遍历那些被内核I/O事件异步唤醒之后加入到就绪队列并返回到用户空间的描述符集合。
    epoll提供了两种触发模式,水平触发(LT)和边沿触发(ET)。当然,涉及到I/O操作也必然会有阻塞和非阻塞两种方案。目前效率相对较高的是 epoll+ET+非阻塞I/O 模型,在具体情况下应该合理选用当前情形中最优的搭配方案。

    接下来的讲解顺序为:
    (1) epoll接口的一般使用
    (2) epoll接口 + 非阻塞
    (3) epoll接口 + 非阻塞 + 边沿触发
    (4) epoll反应堆模型 (重点,Libevent库的核心思想)

    一、epoll接口的基本思想概述

    epoll的设计:
    (1)epoll在Linux内核中构建了一个文件系统,该文件系统采用红黑树来构建,红黑树在增加和删除上面的效率极高,因此是epoll高效的原因之一。有兴趣可以百度红黑树了解,但在这里你只需知道其算法效率超高即可。
    (2)epoll红黑树上采用事件异步唤醒,内核监听I/O,事件发生后内核搜索红黑树并将对应节点数据放入异步唤醒的事件队列中
    (3)epoll的数据从用户空间到内核空间采用mmap存储I/O映射来加速。该方法是目前Linux进程间通信中传递最快,消耗最小,传递数据过程不涉及系统调用的方法。
    这里写图片描述

    epoll接口相对于传统的select/poll而言,有以下优点:
    (1)支持单个进程打开大数量的文件描述符。受进程最大打开的文件描述符数量限制,而不是受自身实现限制。而select单个进程能够打开的文件描述符的数量存在最大限制,这个限制是select自身实现的限制。通常是1024。poll采用链表,也是远超select的。
    (2)Linux的I/O效率不会随着文件描述符数量的增加而线性下降。较之于select/poll,当处于一个高并发时(例如10万,100万)。在如此庞大的socket集合中,任一时间里其实只有部分的socket是“活跃”的。select/poll的处理方式是,对用如此庞大的集合进行线性扫描并对有事件发生的socket进行处理,这将极大的浪费CPU资源。因此epoll的改进是,由于I/O事件发生,内核将活跃的socket放入队列并交给mmap加速到用户空间,程序拿到的集合是处于活跃的socket集合,而不是所有socket集合。
    (3)使用mmap加速内核与用户空间的消息传递。select/poll采用的方式是,将所有要监听的文件描述符集合拷贝到内核空间(用户态到内核态切换)。接着内核对集合进行轮询检测,当有事件发生时,内核从中集合并将集合复制到用户空间。 再看看epoll怎么做的,内核与程序共用一块内存,请看epoll总体描述01这幅图,用户与mmap加速区进行数据交互不涉及权限的切换(用户态到内核态,内核态到用户态)。内核对于处于非内核空间的内存有权限进行读取。
    更多关于mmap可以看看我的另一篇博客了进行了解。
    https://blog.csdn.net/qq_36359022/article/details/79992287

    接下来我们结合epoll总体描述01与上述的内容,将图示进行升级为epoll总体描述02
    这里写图片描述
    让我们来看看图的红黑树文件系统。以epfd为根,挂载了一个监听描述符和5个与客户端建立连接的cfd。fd的增删按照红黑树的操作方式,每一个文件描述符都有一个对应的结构,该结构为

    /*
     *  -[ epoll结构体描述01 ]-
     */
    struct epoll_event {
                __uint32_t events; /* Epoll events */
                epoll_data_t data; /* User data variable */
            };
    
    typedef union epoll_data {
                void *ptr;
                int fd;
                uint32_t u32;
                uint64_t u64;
            } epoll_data_t;

    epoll总体描述02中,每个fd上关联的即是结构体epoll_event。它由需要监听的事件类型和一个联合体构成。一般的epoll接口将传递自身fd到联合体。

    因此,使用epoll接口的一般操作流程为:
    (1)使用epoll_create()创建一个epoll对象,该对象与epfd关联,后续操作使用epfd来使用这个epoll对象,这个epoll对象才是红黑树,epfd作为描述符只是能关联而已。
    (2)调用epoll_ctl()向epoll对象中进行增加、删除等操作。
    (3)调用epoll_wait()可以阻塞(或非阻塞或定时) 返回待处理的事件集合。
    (3)处理事件。

    /*
     *  -[  一般epoll接口使用描述01  ]-
     */
    int main(void)
    {
     /* 
      *   此处省略网络编程常用初始化方式(从申请到最后listen)
      *   并且部分的错误处理省略,我会在后面放上所有的源码,这里只放重要步骤
      *   部分初始化也没写
      */ 
      // [1] 创建一个epoll对象
      ep_fd = epoll_create(OPEN_MAX);       /* 创建epoll模型,ep_fd指向红黑树根节点 */
      listen_ep_event.events  = EPOLLIN;    /* 指定监听读事件 注意:默认为水平触发LT */
      listen_ep_event.data.fd = listen_fd;  /* 注意:一般的epoll在这里放fd */ 
      // [2] 将listen_fd和对应的结构体设置到树上
      epoll_ctl(ep_fd, EPOLL_CTL_ADD, listen_fd, &listen_ep_event);
    
      while(1) { 
          // [3] 为server阻塞(默认)监听事件,ep_event是数组,装满足条件后的所有事件结构体
          n_ready = epoll_wait(ep_fd, ep_event, OPEN_MAX, -1); 
          for(i=0; i<n_ready; i++) {
             temp_fd = ep_event[i].data.fd;
    
             if(ep_event[i].events & EPOLLIN){
                if(temp_fd == listen_fd) {  //说明有新连接到来
                   connect_fd = accept(listen_fd, (struct sockaddr *)&client_socket_addr, &client_socket_len);
                   // 给即将上树的结构体初始化
                   temp_ep_event.events  = EPOLLIN;
                   temp_ep_event.data.fd = connect_fd;
                   // 上树
                   epoll_ctl(ep_fd, EPOLL_CTL_ADD, connect_fd, &temp_ep_event);
                 }
                 else {                      //cfd有数据到来
                   n_data = read(temp_fd , buf, sizeof(buf));
                   if(n_data == 0)  {        //客户端关闭
                       epoll_ctl(ep_fd, EPOLL_CTL_DEL, temp_fd, NULL) //下树
                       close(temp_fd);
                    }
                    else if(n_data < 0) {}
    
                    do {
                       //处理数据
                     }while( (n_data = read(temp_fd , buf, sizeof(buf))) >0 ) ;
                 }
              }
    
             else if(ep_event[i].events & EPOLLOUT){
                    //处理写事件
             }
             else if(ep_event[i].events & EPOLLERR) {
                    //处理异常事件
             }
          }      
       }
      close(listen_fd);
      close(ep_fd);
    }

    二、epoll水平触发(LT)、epoll边沿触发(ET)

    (1) epoll水平触发, 此方式为默认情况。
    当设置了水平触发以后,以可读事件为例,当有数据到来并且数据在缓冲区待读。即使我这一次没有读取完数据,只要缓冲区里还有数据就会触发第二次,直到缓冲区里没数据。
    (2) epoll边沿触发,此方式需要在设置

    listen_ep_event.events  = EPOLLIN | EPOLLET;   /*边沿触发 */

    当设置了边沿触发以后,以可读事件为例,对“有数据到来”这件事为触发。
    这里写图片描述
    总结:
    1.用高低电平举例子就是
    水平触发:0为无数据,1为有数据。缓冲区有数据则一直为1,则一直触发。
    边沿触发:0为无数据,1为有数据,只要在0变到1的上升沿才触发。
    2.
    缓冲区有数据可读,触发 ⇒ 水平触发
    缓冲区有数据到来,触发 ⇒ 边沿触发

    那么,为什么说边沿触发(ET) 的效率更高呢?
    (1) 边沿触发只在数据到来的一刻才触发,很多时候服务器在接受大量数据时会先接受数据头部(水平触发在此触发第一次,边沿触发第一次)。
    (2) 接着服务器通过解析头部决定要不要接这个数据。此时,如果不接受数据,水平触发需要手动清除,而边沿触发可以将清除工作交给一个定时的清除程序去做,自己立刻返回。
    (3) 如果接受,两种方式都可以用while接收完整数据。

    三、epoll + 非阻塞I/O

    在第一大部分中的 一般epoll接口使用描述01代码中,我们使用的读取数据函数为read函数,在默认情况下此类函数是阻塞式的,在没有数据时会一直阻塞等待数据到来。
    (1)数据到来100B,在epoll模式下调用read时,即使read()是阻塞式的也不会在这里等待,因为既然运行到read(),说明数据缓冲区已经有数据,因此这处无影响。
    (2)在服务器开发中,一般不会直接用采用类似read()函数这一类系统调用(只有内核缓冲区),会使用封装好的一些库函数(有内核缓冲区+用户缓冲区)或者自己封装的函数。
    例如:使用readn()函数,设置读取200B返回,假设数据到来100B,可读事件触发,而程序要使用readn()读200B,那么此时如果是阻塞式的,将在此处形成死锁
    流程是:100B ⇒ 触发可读事件 ⇒ readn()调用 ⇒ readn()都不够200B,阻塞 ⇒ cfd又到来200B ⇒ 此时程序在readn()处暂停,没有机会调用epoll_wait() ⇒ 完成死锁

    解决:
    将该cfd在上树前设置为非阻塞式

    /* 修改cfd为非阻塞读 */
    flag  = fcntl(cfd, F_GETFL);
    flag |= O_NONBLOCK;
    fcntl(connect_fd, F_SETFL, flag); 

    四、epoll + 非阻塞I/O + 边沿触发

    /*
     *  -[  epoll+非阻塞+边沿触发 使用描述01  ]-
     */
    
     /* 其他情况不变,与*一般epoll接口使用描述01*描述一致 
      * 变化的仅有第二大部分和第三大部分的这两段代码即可
      */ 
    
    /* ... ... */
    listen_ep_event.events  = EPOLLIN | EPOLLET;   /*边沿触发 */
    /* ... ... */
    /* 修改cfd为非阻塞读 */
    flag  = fcntl(cfd, F_GETFL);
    flag |= O_NONBLOCK;
    fcntl(connect_fd, F_SETFL, flag); 
    /* ... ...  */
    

    五、epoll反应堆模型 (Libevent库核心思想)

    1. 第一步,epoll反应堆模型雏形 —— epoll模型
      我们将epoll总体描述02进行升级为epoll反应堆模型总体描述01
      这里和一般的epoll接口不同的是,现在正式称之为epoll模型(epoll+ET+非阻塞+自定义结构体)
      (1) 还记得每一个在红黑树上的文件描述符所对应的结构体吗?它的结构描述在第一大部分的 epoll结构体描述01中。在epoll接口使用代码中,我们在该结构体中的联合体上传入的是文件描述符本身,那么epoll模型和epoll接口最本质的区别在于epoll模型中,传入联合体的是一个自定义结构体指针,该结构体的基本结构至少包括
    struct my_events {  
        int        m_fd;                             //监听的文件描述符
        void       *m_arg;                           //泛型参数
        void       (*call_back)(void *arg);          //回调函数
        /*
         *  你可以在此处封装更多的数据内容
         *  例如用户缓冲区、节点状态、节点上树时间等等
         */
    };
    /*
     * 注意:用户需要自行开辟空间存放my_events类型的数组,并在每次上树前用epoll_data_t里的  
     *      ptr指向一个my_events元素。
     */

    根据该模型,我们在程序中可以让所有的事件都拥有自己的处理函数,你只需要使用ptr传入即可。
    (2) epoll_wait()返回后,epoll模型将不会采用一般epoll接口使用描述01代码中的事件分类处理的办法,而是直接调用事件中对应的回调函数,就像这样

    /*
     *  -[ epoll模型使用描述01  ]-
     */
     while(1) {
          /* 监听红黑树, 1秒没事件满足则返回0 */ 
          int n_ready = epoll_wait(ep_fd, events, MAX_EVENTS, 1000);
          if (n_ready > 0) {
             for (i=0; i<n_ready; i++) 
                events[i].data.ptr->call_back(/* void *arg */);
           }
           else
              /*  
               * (3) 这里可以做很多很多其他的工作,例如定时清除没读完的不要的数据
               *     也可以做点和数据库有关的设置
               *     玩大点你在这里搞搞分布式的代码也可以
               */
     }

    这里写图片描述

    1. 第二步,epoll反应堆模型成型
      到了这里,也将是epoll的最终成型,如果从前面到这里你都明白了,epoll的知识你已经十之七八了
      让我们先回想以下epoll模型的那张图,我们来理一理思路。
      (1) 程序设置边沿触发以及每一个上树的文件描述符设置非阻塞
      (2) 调用epoll_create()创建一个epoll对象
      (3) 调用epoll_ctl()向epoll对象中进行增加、删除等操作
      上树的文件描述符与之对应的结构体,该结构体应该满足填充事件与自定义结构体ptr,此时,监听的事件与回调函数已经确定了对吧?
      (4) 调用epoll_wait()(定时检测) 返回待处理的事件集合。
      (5) 依次调用事件集合中的每一个元素中的ptr所指向那个结构体中的回调函数

    以上为雏形版本,那么epoll反应堆模型还要比这个雏形版本多了什么呢?
    请看第三步的粗体字,当我们把描述符和自定义结构体上树以后,如果放的是监听可读事件并做其对应的回调操作。也就是说,它将一直作为监听可读事件而存在。
    其流程是:
    监听可读事件(ET) ⇒ 数据到来 ⇒ 触发事件 ⇒ epoll_wait()返回 ⇒ 处理回调 ⇒ 继续epoll_wait() ⇒ 直到程序停止前都是这么循环

    那么接下来升级为成型版epoll反应堆模型
    其流程是:
    监听可读事件(ET) ⇒ 数据到来 ⇒ 触发事件 ⇒ epoll_wait()返回 ⇒
    读取完数据(可读事件回调函数内) ⇒ 将该节点从红黑树上摘下(可读事件回调函数内) ⇒ 设置可写事件和对应可写回调函数(可读事件回调函数内) ⇒ 挂上树(可读事件回调函数内) ⇒ 处理数据(可读事件回调函数内)
    ⇒ 监听可写事件(ET) ⇒ 对方可读 ⇒ 触发事件 ⇒ epoll_wait()返回 ⇒
    写完数据(可写事件回调函数内) ⇒ 将该节点从红黑树上摘下(可写事件回调函数内) ⇒ 设置可读事件和对应可读回调函数(可写读事件回调函数内) ⇒ 挂上树(可写事件回调函数内) ⇒ 处理收尾工作(可写事件回调函数内) ⇒ 直到程序停止前一直这么交替循环
    至此,结束

    (1) 如此频繁的增加删除不是浪费CPU资源吗?
    答:对于同一个socket而言,完成收发至少占用两个树上的位置。而交替只需要一个。任何一种设计方式都会有浪费CPU资源的时候,关键看你浪费得值不值,此处的耗费能否换来更大的收益才是衡量是否浪费的标准。和第二个问题综合来看,这里不算浪费

    (2) 为什么要可读以后设置可写,然后一直交替?
    答:服务器的基本工作无非数据的收发,epoll反应堆模型准从TCP模式,一问一答。服务器收到了数据,再给与回复,是目前绝大多数服务器的情况。
    (2-1) 服务器能收到数据并不是一定能写数据
    假设一 :服务器接收到客户端数据,刚好此时客户端的接收滑动窗口满,我们假设不进行可写事件设置,并且客户端是有意让自己的接收滑动窗口满的情况(黑客)。那么,当前服务器将随客户端的状态一直阻塞在可写事件,除非你自己在写数据时设置非阻塞+错误处理
    假设二 :客户端在发送完数据后突然由于异常原因停止,这将导致一个FIN发送至服务器,如果服务器不设置可写事件监听,那么在接收数据后写入数据会引发异常SIGPIPE,最终服务器进程终止。
    细节部分可以看看我的这篇关于SIGPIPE信号的博客
    https://blog.csdn.net/qq_36359022/article/details/78851178

    以上为网络高并发服务器epoll接口、epoll反应堆模型详解及代码实现,下面是源码
    链接:https://pan.baidu.com/s/1u99U2xEnbXE3sPQ6o2GcUg 密码:gc7k

    展开全文
  • 2 epoll反应堆模型

    2017-02-20 17:59:18
    int active_fds_cnt = epoll_wait(epfd, my_events, EPOLL_MAX_NUM, -1); int i = 0; for (i = 0; i ; i++) { // if fd == listen_fd if (my_events[i].data.fd == listen_fd) { //accept client_fd ...
    完整代码在我个人github上欢迎fork  https://github.com/hankai17/test/tree/master/linux_pro/day11
    
    #include <stdio.h>  
    #include <stdlib.h>  
    #include <ctype.h>  
    #include <string.h>  
      
    #include <unistd.h>  
    #include <sys/types.h>  
    #include <sys/socket.h>  
    #include <arpa/inet.h>  
      
    #include <sys/epoll.h>  
      
      
    #define SERVER_PORT         (7778)  
    #define EPOLL_MAX_NUM       (2048)  
    #define BUFFER_MAX_LEN      (4096)  
      
    char buffer[BUFFER_MAX_LEN];  
      
    void str_toupper(char *str)  
    {  
        int i;  
        for (i = 0; i < strlen(str); i ++) {  
            str[i] = toupper(str[i]);  
        }  
    }  
      
    int main(int argc, char **argv)  
    {  
        int listen_fd = 0;  
        int client_fd = 0;  
      
        struct sockaddr_in server_addr;  
        struct sockaddr_in client_addr;  
        socklen_t           client_len;  
      
        int epfd = 0;  
        struct epoll_event event, *my_events;  
      
        // socket  
        listen_fd = socket(AF_INET, SOCK_STREAM, 0);  
      
        // bind  
        server_addr.sin_family = AF_INET;  
        server_addr.sin_addr.s_addr = htonl(INADDR_ANY);  
        server_addr.sin_port = htons(SERVER_PORT);  
          
        bind(listen_fd, (struct sockaddr*)&server_addr, sizeof(server_addr));  
      
        // listen  
        listen(listen_fd, 10);  
      
        // epoll create  
        epfd = epoll_create(EPOLL_MAX_NUM);  
        if (epfd < 0) {  
            perror("epoll create");  
            goto END;  
        }  
      
        // listen_fd -> epoll  
        event.events = EPOLLIN;  
        event.data.fd = listen_fd;  
        if (epoll_ctl(epfd, EPOLL_CTL_ADD, listen_fd, &event) < 0) {  
            perror("epoll ctl add listen_fd ");  
            goto END;  
        }  
      
      
        my_events = malloc(sizeof(struct epoll_event) * EPOLL_MAX_NUM);  
          
      
        while (1) {  
            // epoll wait  
            int active_fds_cnt = epoll_wait(epfd, my_events, EPOLL_MAX_NUM, -1);  
              
            int i = 0;  
            for (i = 0; i < active_fds_cnt; i++) {  
                // if fd == listen_fd  
                if (my_events[i].data.fd == listen_fd) {  
                    //accept  
                    client_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &client_len);  
                    if (client_fd < 0) {  
                        perror("accept");  
                        continue;  
                    }  
      
                    char ip[20];  
                    printf("new connection[%s:%d]\n", inet_ntop(AF_INET, &client_addr.sin_addr, ip, sizeof(ip)), ntohs(client_addr.sin_port));  
      
                    event.events = EPOLLIN | EPOLLET;  
                    event.data.fd = client_fd;  
                    epoll_ctl(epfd, EPOLL_CTL_ADD, client_fd, &event);  
                }  
                else if (my_events[i].events & EPOLLIN) {  
                    printf("EPOLLIN\n");  
                    client_fd = my_events[i].data.fd;  
      
                    // do read  
      
                    buffer[0] = '\0';  
                    int n = read(client_fd, buffer, 5);  
                    if (n < 0) {  
                        perror("read");  
                        continue;  
                    }  
                    else if (n == 0) {  
                        epoll_ctl(epfd, EPOLL_CTL_DEL, client_fd, &event);  
                        close(client_fd);  
                    }  
                    else {  
                        printf("[read]: %s\n", buffer);  
                        buffer[n] = '\0';  
    #if 0  
                        str_toupper(buffer);  
                        write(client_fd, buffer, strlen(buffer));  
                        printf("[write]: %s\n", buffer);  
                        memset(buffer, 0, BUFFER_MAX_LEN);  
    #endif  
      
    #if 1  
                        event.events = EPOLLOUT;  
                        event.data.fd = client_fd;  
                        epoll_ctl(epfd, EPOLL_CTL_MOD, client_fd, &event);  
    #endif  
                    }  
                }  
                else if (my_events[i].events & EPOLLOUT) {  
                    printf("EPOLLOUT\n");  
    #if 1  
                    client_fd = my_events[i].data.fd;  
                    str_toupper(buffer);  
                    write(client_fd, buffer, strlen(buffer));  
                    printf("[write]: %s\n", buffer);  
                    memset(buffer, 0, BUFFER_MAX_LEN);  
      
                    event.events = EPOLLIN;  
                    event.data.fd = client_fd;  
                    epoll_ctl(epfd, EPOLL_CTL_MOD, client_fd, &event);  
    #endif  
                }  
            }  
        }  
          
      
      
    END:  
        close(epfd);  
        close(listen_fd);  
        return 0;  
    }  




    展开全文
  • 下面代码实现的思想:epoll反应堆模型:( libevent 网络编程开源库 核心思想) 1. 普通多路IO转接服务器: 红黑树 ―― 添加待监听的结点 ―― epoll_ctl ―― EPOLLIN ―― fd ―― 监听 ―― epoll_wait ―― ...

    =========================================================
    下面代码实现的思想:epoll反应堆模型:( libevent 网络编程开源库 核心思想)

    1.  普通多路IO转接服务器: 红黑树 ―― 添加待监听的结点 ―― epoll_ctl ―― EPOLLIN ―― fd ―― 监听 ―― epoll_wait ――

    返回满足监听事件的fd的总个数 ―― 传出参数 events数组 ―― 内部元素 ――满足对应监听事件的fd

    ―― 判断对应事件 ―― Accept、Read。――循环 epoll_wait 监听

    2.  epoll反应堆模型: 创建红黑树 ―― 添加监听结点 ―― epoll_ctl ―― EPOLLIN ―― fd ―― 监听 ―― epoll_wait ―― 将结点从树上摘下

    ――处理数据 ―― 修改fd的监听事件 ―― EPOLLOUT ―― 重新添加到红黑树 ―― 监听 ―― epoll_wait ―― 写数据到

    客户端 ―― 再将结点从树上摘下 ―― 修改监听时间 ―― EPOLLIN ―― 挂上红黑树监听。

    添加监听写事件的目的: “滑动窗口”已满,绕过写。epoll_wait满足后再进行写。

    #include <stdio.h>
    #include <time.h>
    #include <errno.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <sys/types.h>
    #include <sys/epoll.h>
    
    #define IP "127.0.0.1" 
    #define port 7095
    #define MAXEVENT 1024 
    
    struct myevent_s{
    
    	int fd; //所要监听的文件描述符;
    	int status; // 是否被挂载在监听红黑树上;
    	void *arg; // 指向自己的结构体指针;
    	int events; // 监听事件
    	void (*call_back)(int fd, int event, void *arg); // 回调函数
    	long last_active;// 连接时间
    	char buf[BUFSIZ];
    	int len;
    
    };
    
    int g_epfd;
    struct myevent_s g_events[BUFSIZ + 1];
    
    void recvdata(int fd, int events, void *arg);
    void sendData(int fd, int events, void *arg);
    void eventAdd(int epfd, int events, struct myevent_s *ev);
    
    void Fcntl(int fd){
    	int flag = fcntl(fd, F_GETFL);
    	flag |= O_NONBLOCK;
    	int ret = fcntl(fd, F_SETFL, flag);
    	if(ret < 0){
    		printf("%s: fcntl error: %s\n", __func__, strerror(errno));
    	}
    }
    
    void eventSet(struct myevent_s *ev, int fd, void (*call_back)(int fd, int events, void *arg), void *arg){
    	ev->fd = fd;
    	ev->events = 0;
    	ev->call_back = call_back;
    	ev->status = 0;
    	ev->arg = arg;
    	if(ev->len <= 0){
    		memset(ev->buf, 0, sizeof(ev->buf));
    		ev->len = 0;
    	}
    	ev->last_active = time(NULL);
    	return;
    }
    
    void eventAdd(int epfd, int events, struct myevent_s *ev){
    	struct epoll_event evs = {0, {0}};
    	int op = 0;
    
    	evs.data.ptr = ev;
    	evs.events = ev->events = events;
    
    	if(ev->status == 0){
    		op = EPOLL_CTL_ADD;
    		ev->status = 1;
    	}
    	else{
    		printf("%d is added\n", ev->fd);
    		//return;
    	}
    
    	char *str;
    	if(events == EPOLLIN){
    		str = "EPOLLIN";
    	}
    	else if(events == EPOLLOUT){
    		str = "EPOLLOUT";
    	}
    	else if(events == (EPOLLIN | EPOLLET)){
    		str = "EPOLLIN | EPOLLET";
    	}
    	int ret = epoll_ctl(g_epfd, op, ev->fd, &evs);
    	if(ret < 0){
    		printf("Events add failed fd[%d], events[%s]\n", ev->fd, str);
    	}
    	else{
    		printf("Events add OK fd[%d], events[%s]\n", ev->fd, str);
    	}
    	return;
    }
    
    void eventDel(int epfd, struct myevent_s *ev){
    	struct epoll_event evs = {0, {0}};
    	if(ev->status != 1){
    		return;
    	}
    	evs.data.ptr = NULL;
    	ev->status = 0;
    	epoll_ctl(g_epfd, EPOLL_CTL_DEL, ev->fd, &evs);
    	return;
    }
    
    void acceptance(int lfd, int events, void *arg){
    	struct sockaddr_in caddr;
    	socklen_t len = sizeof(caddr);
    	int cfd, i = 0;
    	if((cfd = accept(lfd, (struct sockaddr *)&caddr, &len)) == -1)
    	{
    		if(errno != EAGAIN && errno != EINTR)
    		{
    			sleep(1);
    		}
    		printf("%s:accept,%s\n",__func__, strerror(errno));
    		return;
    	}
    	do{
    		for(i = 0; i < BUFSIZ; i++){
    			if(g_events[i].status == 0){
    				break;
    			}
    		}
    
    		if(i == BUFSIZ){
    			printf("Error: too any people\n");
    			break;
    		}
    
    		//Fcntl(cfd);
    		int flag = 0;
    		if((flag = fcntl(cfd, F_SETFL, O_NONBLOCK)) < 0) //将cfd也设置为非阻塞
    		{
    			printf("%s: fcntl nonblocking failed, %s\n", __func__, strerror(errno));
    			break;
    		}
    
    		eventSet(&g_events[i], cfd, recvdata, &g_events[i]);
    		eventAdd(g_epfd, EPOLLIN, &g_events[i]);
    	}while(0);
    	printf("%s is connected\n", inet_ntoa(caddr.sin_addr));
    	return;
    }
    
    void recvdata(int fd, int events, void *arg){
    	struct myevent_s *ev = (struct myevent_s *)arg;
    	int len;
    
    	len = recv(fd, ev->buf, sizeof(ev->buf), 0);
    
    	eventDel(g_epfd, ev);
    
    	if(len > 0){
    		ev->buf[len] = '\0';
    		ev->len = len;
    
    		printf("C[%d]: %s \n", fd, ev->buf);
    
    		eventSet(ev, fd, sendData, ev);
    		eventAdd(g_epfd, EPOLLOUT, ev);
    		return;
    	}
    	else if(len == 0){
    		close(ev->fd);
    		printf("[fd=%d] pos[%ld], closed\n", fd, ev-g_events);
    		return;
    	}
    	else{
    		close(ev->fd);
    		printf("%s Error: %s \n", __func__, strerror(errno));
    	}
    	return ;
    }
    void sendData(int fd, int events, void *arg){
    	struct myevent_s *ev = (struct myevent_s *)arg;
    	int len;
    
    	len = write(ev->fd, ev->buf, ev->len);
    
    	eventDel(g_epfd, ev);
    
    	if(len > 0){
    		printf("send[fd = %d] [%d] %s\n", ev->fd, len, ev->buf);
    		eventSet(ev, fd, recvdata, ev);
    		eventAdd(g_epfd, EPOLLIN, ev);
    	}
    	else{
    		close(ev->fd);
    		printf("%s ERROR: %s", __func__, strerror(errno));
    	}
    	return ;
    }
    
    void initSocket(int efd){
    	struct sockaddr_in saddr;
    	int lfd, ret;
    	socklen_t len;
    
    	lfd = socket(AF_INET, SOCK_STREAM, 0);
    	if(lfd < 0){
    		printf("%s create socket error:%s\n", __func__, strerror(errno));
    		exit(1);
    	}
    
    	Fcntl(lfd);
    
    	saddr.sin_addr.s_addr = inet_addr(IP);
    	saddr.sin_family = AF_INET;
    	saddr.sin_port = htons(port);
    
    	len = sizeof(saddr);
    
    	ret = bind(lfd, (struct sockaddr*)&saddr, len);
    	if(ret < 0){
    		printf("%s bind error: %s", __func__, strerror(errno));
    		exit(1);
    	}
    
    	listen(lfd, 128);
    
    	eventSet(&g_events[BUFSIZ], lfd, acceptance, &g_events[BUFSIZ]);
    	eventAdd(g_epfd, EPOLLIN, &g_events[BUFSIZ]);
    }
    
    int main()
    {
    	g_epfd = epoll_create(BUFSIZ + 1);
    	if(g_epfd < 0){
    		printf("%s epoll cteate error: %s\n", __func__, strerror(errno));
    		exit(1);
    	}
    	initSocket(g_epfd);
    
    	struct epoll_event evts[BUFSIZ + 1];
    	printf("%s:%d is running\n", IP, port);
    
    	while(1){
    		int ready = epoll_wait(g_epfd, evts, BUFSIZ + 1, 1000);
    		if(ready < 0){
    			perror("epoll wait error:");
    			exit(1);
    		}
    
    		for(int i = 0; i < ready; ++i){
    
    			struct myevent_s *ev = (struct myevent_s *)evts[i].data.ptr;
    
    			if((evts[i].events & EPOLLIN) && (ev->events & EPOLLIN)){
    				ev->call_back(ev->fd, evts[i].events, ev->arg);
    			}
    			if((evts[i].events & EPOLLOUT) && (ev->events & EPOLLOUT)){
    				ev->call_back(ev->fd, evts[i].events, ev->arg);
    			}
    		}
    	}
    	return 0;
    }
    
    
    
    展开全文
  • libevent库epoll反应堆模型源码分析 /* *epoll基于非阻塞I/O事件驱动 */ #include <stdio.h> #include <sys/socket.h> #include <sys/epoll.h> #include <arpa/inet.h> #include <...
  • epoll 反应堆模型epoll ET模式 + 非阻塞、轮询 + void *ptr。 原来: socket、bind、listen -- epoll_create 创建监听 红黑树 -- 返回 epfd -- epoll_ctl() 向树上添加一个监听fd -- while(1)-- -- epoll...
  • libevent核心思想:epoll反应堆模型

    千次阅读 2018-10-30 19:59:43
    一.Linux下的I/O复用与epoll详解 与select/poll不同的是,epoll采用回调函数机制,epoll只关心“活跃”的连接,无需遍历全部的文件描述符 一.为什么引出epoll? 1.select的缺点 1.select所用到的FD_SET是有限...
  • epoll反应堆模型代码

    2019-10-05 09:53:16
    libevent函数库核心思想 ...epoll_loop.c ***/ #include<stdio.h> #include<sys/epoll.h> #include<sys/socket.h> #include<arpa/inet.h> #include<fcntl.h> #include<...
  • epoll反应堆模型实现

    2019-03-19 13:44:50
    <sys/epoll.h> # include # include # include using namespace std ; using std :: cin ; using std :: cout ; using std :: endl ; using std :: string ; # define SERV_...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 2,570
精华内容 1,028
关键字:

epoll反应堆模型