epoll有两种触发模式,水平触发和边沿触发,但是高效率的epoll往往搭配边沿触发和非阻塞。为什么水平触发的效率不高呢?因为系统中一旦有大量不需要读的数据,剩余的数据都会使得epoll_wait函数都会返回,势必会影响效率。而在边沿触发模式下,缓冲区剩余未读尽的数据不会导致epoll_wait返回。

epoll反应堆的事件处理过程

  1、准备工作,socket()、bind()、listen();

  2、创建一颗监听红黑树,epoll_create();

  3、通过函数epoll_ctl()把socket()返回的文件描述符挂在监听树上;

  4、while(1);

  5、一旦有客户端请求连接epoll_wait()返回满足监听条件的数组;

  6、判断返回的是建立连接的请求还是通信数据请求;如果是建立连接请求调用accept(),返回用于通信的文件描述符,并将次文件描述符设置监听读事件挂在树上;如果是通信数据请求,read()读数据,接着处理数据,将文件描述符从树上取下,重新调用epoll_ctl()将文件描述符设置监听写事件,等待epoll_wait()返回,说明可写,将数据写给客户端,又将文件描述符从树上取下,设置监听的读事件,挂上树.......

代码分析

    /* 描述就绪文件描述符相关信息 */    
    struct myevent_s {  
        int fd;                                                 //要监听的文件描述符  
        int events;                                             //对应的监听事件  
        void *arg;                                              //泛型参数  
        void (*call_back)(int fd, int events, void *arg);       //回调函数  
        int status;                                             //监听状态,1表示在树上(监听), 0表示不在  
        char buf[BUFLEN];  
        int len;  
        long last_active;                                       //记录每次加入红黑树的时间  
    };  

设置listenfd的相关信息

// MAX_EVENTS=1024
eventset(&g_events[MAX_EVENTS], lfd, acceptconn, &g_events[MAX_EVENTS]);

      
void eventset(struct myevent_s *ev, int fd, void (*call_back)(int, int, void *), void *arg)  
    {  
        ev->fd = fd;  
        ev->call_back = call_back;  
        ev->events = 0;  
        ev->arg = arg;  
        ev->status = 0;  
        memset(ev->buf, 0, sizeof(ev->buf));  
        ev->len = 0;  
        ev->last_active = time(NULL);                         
      
        return;  
    }  

将listenfd挂在树上

//监听listenfd的读事件
eventadd(efd, EPOLLIN, &g_events[MAX_EVENTS]);  

void eventadd(int efd, int events, struct myevent_s *ev)  
{  
        struct epoll_event epv ;  
        int op;  
        epv.data.ptr = ev;  
        epv.events = ev->events = events;       //设为EPOLLIN 
      
        if (ev->status == 0) {                       //如果没有上树  
            op = EPOLL_CTL_ADD;                 //将其加入红黑树 , 并将status置1  
            ev->status = 1;  
        }  
}  

 

当有读事件满足,会调用acceptconn

 int nfd = epoll_wait(g_efd, events, MAX_EVENTS+1, 1000);  
 for (i = 0; i < nfd; i++)
{    
 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);  
}

//回调函数具体实现
void acceptconn(int lfd, int events, void *arg)  
{  
     struct sockaddr_in cin;  
     socklen_t len = sizeof(cin);  
     int cfd, i;  
     cfd = accept(lfd, (struct sockaddr *)&cin, &len)   
     for (i = 0; i < MAX_EVENTS; i++)                                //从全局数组g_events中找一个空闲元素  
                if (g_events[i].status == 0)                                
                         break;                                                  //跳出 for  
      
        if (i == MAX_EVENTS) {  
            //容错处理
          }  
      //设置为非阻塞
      int flg=fcntl(cfd,F_GETFL);
      flg|O_NONBLOCK;
      fcntl(cfd,F_SETFL,flg);
      
            /* 给cfd设置一个 myevent_s 结构体, 回调函数 设置为 recvdata */  
       eventset(&g_events[i], cfd, recvdata, &g_events[i]);     
       eventadd(g_efd, EPOLLIN, &g_events[i]);                         //将cfd添加到红黑树中,监听读事件  
      
}

当epoll_wait()返回,cfd的读事件满足,调用recvdata()函数

 int nfd = epoll_wait(g_efd, events, MAX_EVENTS+1, 1000);  
 for (i = 0; i < nfd; i++)
{    
 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);  
}


    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);            //读文件描述符, 数据存入myevent_s成员buf中  
      
        eventdel(g_efd, ev);        //将该节点从红黑树上摘除  
      
        if (len > 0) {  
      
            ev->len = len;  
            ev->buf[len] = '\0';                                //手动添加字符串结束标记  
      
            eventset(ev, fd, senddata, ev);                     //设置该 fd 对应的回调函数为 senddata  
            eventadd(g_efd, EPOLLOUT, ev);                      //将fd加入红黑树中,监听其写事件  
      
        } else if (len == 0) {  
            close(ev->fd);   
        } else {  //出错
            close(ev->fd);  
        }  
      
        return;  
    }  

当epoll_wait()返回,cfd的写事件满足,调用senddata()函数

 int nfd = epoll_wait(g_efd, events, MAX_EVENTS+1, 1000);  
 for (i = 0; i < nfd; i++)
{    
 struct myevent_s *ev = (struct myevent_s *)events[i].data.ptr;    
      
 if ((events[i].events & EPOLLOUT) && (ev->events & EPOLLOUT)) 
           //写就绪事件  
     ev->call_back(ev->fd, events[i].events, ev->arg);  
}

    void senddata(int fd, int events, void *arg)  
    {  
        struct myevent_s *ev = (struct myevent_s *)arg;  
        int len;  
      
        len = send(fd, “hello”, 6, 0);                    //直接将数据 回写给客户端。
      
        eventdel(g_efd, ev);                                //从红黑树中移除  
      
        if (len > 0) {  
      
          
            eventset(ev, fd, recvdata, ev);                     //将该fd的 回调函数改为 recvdata  
            eventadd(g_efd, EPOLLIN, ev);                       //从新添加到红黑树上, 设为监听读事件  
      
        } else {  
            close(ev->fd);                                      //关闭链接  
        }  
      
        return ;  
    }  

 如此反复