epoll

来源:互联网 发布:简单游软件下载 编辑:程序博客网 时间:2024/05/17 04:28

 

epoll的工作原理是,你如果想进行IO操作时,先向epoll查询是否可读或可写,如果处于可读或可写状态后,epoll会通过epoll_wait函数通知你,此时你再进行进一步的recv或send操作。
epoll仅仅是一个异步事件的通知机制,其本身并不作任何的IO读写操作,它只负责告诉你是不是可以读或可以写了,而具体的读写操作,还要应用层自己来作。epoll仅提供这种机制也是非常好的,它保持了事件通知与IO操作之间彼此的独立性,使得epoll的使用更加灵活。

#include <stdio.h>
 #include <unistd.h>
 #include <errno.h>
 #include <time.h>
 #include <sys/time.h>
 #include <signal.h>
 #include <poll.h>
 #include <sys/types.h>
 #include <error.h>

 void call_poll(void)
 {
     struct pollfd fds;
     int32_t timeout_msecs = 5000;
     int err;

     fds.fd = 1;
     fds.events = POLLIN | POLLPRI ;

 // poll系统调用
//  poll()系统调用是System V的多元I/O解决方案。它解决了select()的几个不足,尽管select()仍然经常使用(多数还是出于习惯,或者打着可移植的名义):
 // 用户空间调用的poll函数定义如下:
// 和select()不一样,poll()没有使用低效的三个基于位的文件描述符set,而是采用了一个单独的结构体pollfd数组,由fds指针指向这个组。pollfd结构体定义如下:
     err = poll( &fds, 1, timeout_msecs );
     if ( err > 0 ) {
         printf("Data is available now.\n");
     }
     else if ( err == 0 ) {
         printf("No data within five seconds.\n");
     }
     else  {
         perror( "poll()" );
     }

 }
 #include <sys/epoll.h>
 //所用到的数据结构
 typedef union epoll_data {
 void *ptr;
 //*ptr: 通过指针ptr携带应用层数据, 当事件的通知到来时,
 //它不仅告诉你发生了什么样的事件,
 //还同时告诉这次事件所操作的数据是哪些
 int fd;
 __uint32_t u32;
 __uint64_t u64;
 } epoll_data_t;
 
 struct epoll_event { //epoll_event 被用于注册所感兴趣的事件和回传所发生待处理的事件
 __uint32_t events; /* Epoll events */
 epoll_data_t data; /* User data variable */
// epoll_data 联合体用来保存触发事件的某个文件描述符相关的数据,
//例如一个client连接到服务器,服务器通过调用accept函数可以得
//到与这个client对应的socket文件描述符,可以把这文件描述符赋给
//epoll_data的fd字段以便后面的读写操作在这个文件描述符上进行
 };
 epoll_event 结构体的events字段是表示感兴趣的事件和被触发的事件可能的取值为:
 EPOLLIN :表示对应的文件描述符可以读;
 EPOLLOUT:表示对应的文件描述符可以写;
 EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
 EPOLLERR:表示对应的文件描述符发生错误;
 EPOLLHUP:表示对应的文件描述符被挂断;
 EPOLLET:表示对应的文件描述符有事件发生;

 void call_epoll(void)
 {
     int epfd;
     struct epoll_event ev_stdin;
     int err;
 // 函数声明:int epoll_create(int size)
 // 该函数生成一个epoll专用的文件描述符,
 //其中的参数是指定生成描述符的最大范围

     epfd = epoll_create(1);
     if ( epfd < 0 ) {
         perror( "epoll_create()" );
         return ;
     }

     bzero( &ev_stdin, sizeof( struct epoll_event) );
     ev_stdin.events =
         //  available for read operations
         EPOLLIN | EPOLLPRI
         // available for write operations
         // | EPOLLOUT
         // Error condition && Hang up happened
         | EPOLLERR | EPOLLHUP
         // Sets the Edge Triggered behaviour
         | EPOLLET
         // Sets the one-shot behaviour.
         // must call epoll_ctl with EPOLL_CTL_MOD to re-enable
         | EPOLLONESHOT
         ;
//  epoll_ctl函数
//  函数声明:int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
//  该函数用于控制某个文件描述符上的事件,
//可以注册事件,修改事件,删除事件。

     err = epoll_ctl( epfd, EPOLL_CTL_ADD, 1, &ev_stdin );
     if ( err < 0 ) {
         perror( "epoll_ctl()" );
         goto _out;
     }
 
//  epoll_wait函数
//  函数声明:int epoll_wait(int epfd,struct epoll_event * events,int maxevents,int timeout)
//  该函数用于轮询I/O事件的发生;
  参数:
  epfd:由epoll_create 生成的epoll专用的文件描述符;
  epoll_event:用于回传代处理事件的数组;
  maxevents:每次能处理的事件数;
  timeout:等待I/O事件发生的超时值;
  返回发生事件数。

     err = epoll_wait( epfd, &ev_stdin, 1, 5000 );
     if ( err < 0 ) {
         perror( "epoll_wait()" );
     }
     else if ( err == 0 ) {
         printf("No data within five seconds.\n");
     }
     else {
          printf("Data is available now.\n");
     }
     //err = epoll_ctl( epfd, EPOLL_CTL_DEL, 1, &ev_stdin );
     //
     err = epoll_ctl( epfd, EPOLL_CTL_DEL, 1, &ev_stdin );
     if ( err < 0 ) {
         perror( "epoll_ctl()" );
     }

  for(i=0; i
  {
  if(epoll_event.data.fd == sock)// the listener port hava data
   {}
  }
 _out:
     close( epfd );
 }

epoll_wait运行的原理是 
等侍注册在epfd上的socket fd的事件的发生,如果发生则将发生的sokct fd和事件类型放入到events数组中。 
并 且将注册在epfd上的socket fd的事件类型给清空,所以如果下一个循环你还要关注这个socket fd的话,则需要用epoll_ctl(epfd,EPOLL_CTL_MOD,listenfd,&ev)来重新设置socket fd的事件类型。这时不用EPOLL_CTL_ADD,因为socket fd并未清空,只是事件类型清空。这一步非常重要。 

 int main ()
 {
     call_epoll();
     return 0;
 }
单个epoll并不能解决所有问题,特别是你的每个操作都比较费时的时候,因为epoll是串行处理的。 
所以你还是有必要建立线程池来发挥更大的效能。

////////////////////////////////////////////////////////////////////////////// 
man中给出了epoll的用法,example程序如下: 
       for(;;) { 
           nfds = epoll_wait(kdpfd, events, maxevents, -1); 

           for(n = 0; n < nfds; ++n) { 
               if(events[n].data.fd == listener) { 
                   client = accept(listener, (struct sockaddr *) &local, 
                                   &addrlen); 
                   if(client < 0){ 
                       perror("accept"); 
                       continue; 
                   } 
                   setnonblocking(client); 
                   ev.events = EPOLLIN | EPOLLET; 
                   ev.data.fd = client; 
                   if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, client, &ev) < 0) { 
                       fprintf(stderr, "epoll set insertion error: fd=%d\n", 
                               client); 
                       return -1; 
                   } 
               } 
               else 
                   do_use_fd(events[n].data.fd); 
           } 
       } 
此时使用的是ET模式,即,边沿触发,类似于电平触发,epoll中的边沿触发的意思是只对新到的数据进行通知,而内核缓冲区中如果是旧数据则不进行通知,所以在do_use_fd函数中应该使用如下循环,才能将内核缓冲区中的数据读完。 
        while (1) { 
           len = recv(*******); 
           if (len == -1) { 
             if(errno == EAGAIN) 
                break; 
             perror("recv"); 
             break; 
           } 
           do something with the recved data........ 
        } 

在上面例子中没有说明对于listen socket fd该如何处理,有的时候会使用两个线程,一个用来监听accept另一个用来监听epoll_wait,如果是这样使用的话,则listen socket fd使用默认的阻塞方式就行了,而如果epoll_wait和accept处于一个线程中,即,全部由epoll_wait进行监听,则,需将listen socket fd也设置成非阻塞的,这样,对accept也应该使用while包起来(类似于上面的recv),因为,epoll_wait返回时只是说有连接到来了,并没有说有几个连接,而且在ET模式下epoll_wait不会再因为上一次的连接还没读完而返回,这种情况确实存在,我因为这个问题而耗费了一天多的时间,这里需要说明的是,每调用一次accept将从内核中的已连接队列中的队头读取一个连接,因为在并发访问的环境下,有可能有多个连接“同时”到达,而epoll_wait只返回了一次。

唯一有点麻烦是epoll有2种工作方式:LT和ET。 

LT(level triggered)是缺省的工作方式,并且同时支持block和no-block socket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表. 

ET (edge-triggered)是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如,你在发送,接收或者接收请求,或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK 错误)。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once),不过在TCP协议中,ET模式的加速效用仍需要更多的benchmark确认。