EPOLL纸上谈兵

来源:互联网 发布:如何看windows版本 编辑:程序博客网 时间:2024/05/14 09:37

之所以是纸上谈兵,是因为在工作中写的服务器架构中,还是select/poll,没有尝试过epoll,下一次尝试一下。


比较好的几个讨论:

论epoll的使用:http://www.cppblog.com/peakflys/archive/2012/08/26/188344.html

浅谈服务器单I/O线程+工作者线程池模型架构及实现要点: http://www.cnblogs.com/ccdev/p/3542669.html

select、poll、epoll之间的区别总结: http://www.cnblogs.com/Anker/p/3265058.html

三个函数:


1. int epoll_create(int size);
创建一个epoll的句柄,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 {               void        *ptr;               int          fd;               uint32_t     u32;               uint64_t     u64;           } epoll_data_t;           struct epoll_event {               uint32_t     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队列里

3. int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
等待事件的产生,类似于select()调用。参数events用来从内核得到事件的集合,maxevents最多监听事件的数量,maxevents的值不能大于创建epoll_create(int size)时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。


两种模型:


Edge Triggered (ET) 
    只有在监视的文件句柄上发生了某个事件的时候 ET 工作模式才会汇报事件,则只是汇报一次。必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。
    i    基于非阻塞文件句柄
    ii   只有当read(2)或者write(2)返回EAGAIN时才需要挂起,等待。
    但这并不是说每次read()时都需要循环读,直到读到产生一个EAGAIN才认为此次事件处理完成,当read()返回的读到的数据长度小于请求的数据长度时,就可以确定此时缓冲中已没有数据了,也就可以认为此事读事件已处理完成。

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

    在许多测试中我们会看到如果没有大量的idle -connection或者dead-connection,epoll的效率并不会比select/poll高很多,但是当我们遇到大量的idle- connection(例如广域网环境中存在大量的慢速连接),就会发现epoll的效率大大高于select/poll。

使用总结

1.EPOLL 默认是水平触发模式LT, 如果要使用边缘触发模式ET,要将listen的socket事件加上EPOLLET

  ev.data.fd = listen_fd;
  ev.events = EPOLLIN  | EPOLLOUT | EPOLLHUP | EPOLLERR | EPOLLET;
  //add server socket fd in to event set
  epoll_ctl( epfd, EPOLL_CTL_ADD, listen_fd, &ev ); 

2.需要设置非阻塞

    int opts;    opts = fcntl( sock_fd, F_GETFL );    if( opts < 0 )    {        printf("err:fcntl F_GETFL");        exit( 1 );    }    opts = opts | O_NONBLOCK ;    if( fcntl( sock_fd, F_SETFL, opts ) < 0 )    {        printf("err:fcntl F_SETFL");        exit( 1 );    } 

3.如果是以LT方式处理,只要数据未处理完(send、recv内核缓冲有数据)就会不停的收到 EPOLLIN/EPOLLOU消息(已设置监听)。如果是以ET方式处理,事件只会通知一次,如数据到达可接收,需要使用while循环来接入数据。同样可发送也只会通常一次。send/recv都以收到EAGAIN为标识,如果还没处理完,才会现收到事件通知。

    注意EPOLLOUT事件,如果是LT模式,只要send缓冲区空闲就会一直有这个事件通知,程序需要判断是否有数据可发送。如果是ET模式,只有在accept一个socket时触发一次,除非发送,缓冲区满了,再变为空闲时,才会再通知一次

4 ET模式EPOLLOUT和EPOLLIN触发时刻
ET模式称为边缘触发模式,顾名思义,不到边缘情况,是死都不会触发的。
EPOLLOUT事件:EPOLLOUT事件只有在连接时触发一次,表示可写,其他时候想要触发,那你要先准备好下面条件:
1.某次write,写满了发送缓冲区,返回错误码为EAGAIN。
2.对端读取了一些数据,又重新可写了,此时会触发EPOLLOUT。
简单地说:EPOLLOUT事件只有在不可写到可写的转变时刻,才会触发一次,所以叫边缘触发,这叫法没错的!
其实,如果你真的想强制触发一次,也是有办法的,直接调用epoll_ctl重新设置一下event就可以了,event跟原来的设置一模一样都行(但必须包含EPOLLOUT),关键是重新设置,就会马上触发一次EPOLLOUT事件。
EPOLLIN事件:EPOLLIN事件则只有当对端有数据写入时才会触发,所以触发一次后需要不断读取所有数据直到读完EAGAIN为止。否则剩下的数据只有在下次对端有写入时才能一起取出来了。

其余要点:

1、如果fd被注册到两个epoll中时,如果fd有事件发生则两个epoll都会触发事件。
2、如果注册到epoll中的fd被关闭,则其会自动被清除出epoll监听列表。
3、如果多个事件同时触发epoll,则多个事件会被联合在一起返回。
4、epoll_wait会一直监听EPOLLHUP事件发生,所以其不需要添加到events中。
5、为了避免大数据量io时,ET模式下只处理一个fd,其他fd被饿死的情况发生。linux建议可以在fd联系到的结构中增加ready位,然后epoll_wait触发事件之后仅将其置位为ready模式,然后在循环检测所有事件后轮询ready fd列表。

示例:

Server

#include <sys/socket.h>  #include <sys/epoll.h>  #include <netinet/in.h>  #include <arpa/inet.h>  #include <fcntl.h>  #include <unistd.h>  #include <stdio.h>  #include <stdlib.h>  #include <memory.h>  #include <pthread.h>  #include <errno.h>  #define MAX_LINE_LEN 1024  #define OPEN_MAX 256  #define SVR_PORT 17143  struct STask  {    int fd;           struct STask * next;  };  struct SData  {    int fd;      int size;      char data[ MAX_LINE_LEN ];};  int epfd;struct epoll_event ev, events[20];  void setNonBlock( int sock_fd )  {    int opts;      opts = fcntl( sock_fd, F_GETFL );      if( opts < 0 )      {          printf("err:fcntl F_GETFL");          exit( 1 );      }      opts = opts | O_NONBLOCK ;      if( fcntl( sock_fd, F_SETFL, opts ) < 0 )    {          printf("err:fcntl F_SETFL");          exit( 1 );      }}bool setSockReuse( int sock_fd )  {      int flags = 1;      if(setsockopt( sock_fd , SOL_SOCKET, SO_REUSEADDR, &flags, sizeof(flags)) == -1)      {          return false;      }      printf("set sock reuse ok.\n");    return true;}  void OnAccept( int conn_fd, sockaddr_in * cln_addr )  {    setNonBlock( conn_fd );      char * str = inet_ntoa( cln_addr->sin_addr );      printf("connect_from:%s\n", str);      //add to epoll event for reading event      ev.data.fd = conn_fd;      ev.events = EPOLLIN | EPOLLOUT | EPOLLET;       epoll_ctl( epfd, EPOLL_CTL_ADD, conn_fd, &ev ); }  void OnRead( epoll_event * aEv )  {      int n;      struct SData * newData = (struct SData *)malloc( sizeof(struct SData ) );      newData->fd = aEv->data.fd;      int bRead = 1;      int recv_len = 0;      memset( newData->data, 0, MAX_LINE_LEN );      while( bRead )       {          n = read( aEv->data.fd, newData->data + recv_len, 5 );          printf("read return n:%d\n",n);          recv_len += n;          if( n < 0 )         {              if( errno == ECONNRESET )   //connection reset            {                  close( aEv->data.fd );                  aEv->data.fd = -1;                  break;              }              else if( errno == EAGAIN ) //read interupt(no data in buf)              {                  printf("EAGAIN\n");                  break;              }              else if( errno == EWOULDBLOCK )  //operation would block            {                  perror("recv not over...\n");              }              else              {                  printf("read err");                  break;              }          }        else if( n == 0 )          {              close( aEv->data.fd );              aEv->data.fd = -1;              break;        }    }      newData->data[ recv_len ] = '\n';      printf("read: %s", newData->data );      free( newData );        //trigger EPOLLOUT event    ev.data.fd = aEv->data.fd;       ev.events = EPOLLIN | EPOLLOUT | EPOLLET;      epoll_ctl(epfd, EPOLL_CTL_MOD, aEv->data.fd ,&ev);}  void OnWrite( epoll_event * aEv )  {      struct SData * newData = (struct SData *)malloc( sizeof(struct SData ) );      newData->fd = aEv->data.fd;    newData->size = snprintf( newData->data, MAX_LINE_LEN, "hello! fd=%d\n", aEv->data.fd );     if( 0 != newData->size )      {          printf("send back: %s\n", newData->data );          write( aEv->data.fd , newData->data, newData->size );    }      free( newData );}  int main(){      int i;      int listen_fd, conn_fd,sock_fd, nfds;    epfd = epoll_create( OPEN_MAX );        struct sockaddr_in svrAddr;      struct sockaddr_in clnAddr;      socklen_t clnLen;      listen_fd = socket( AF_INET, SOCK_STREAM, 0);      setNonBlock( listen_fd );      setSockReuse( listen_fd );          ev.data.fd = listen_fd;      ev.events = EPOLLIN | EPOLLET | EPOLLOUT | EPOLLHUP | EPOLLERR;       //add server socket fd in to event set      epoll_ctl( epfd, EPOLL_CTL_ADD, listen_fd, &ev );          memset( &svrAddr, 0 , sizeof( sockaddr_in ) );      inet_aton( "127.0.0.1", &svrAddr.sin_addr );      svrAddr.sin_family = AF_INET;      svrAddr.sin_port = htons( SVR_PORT );          int ret;      ret = bind( listen_fd, (sockaddr*)&svrAddr, sizeof( sockaddr_in ) );      if( ret != 0 )      {          perror( "bind fail" );          exit(-3);    }      listen( listen_fd, 20 );      for(;;)      {           nfds = epoll_wait( epfd, events, 20, 500 );          //maybe serval event arrive          for(i=0; i<nfds;++i)           {              if( events[i].data.fd == listen_fd )              {                  printf("event accept.\n");                  clnLen = sizeof( sockaddr_in );                  memset( &clnAddr, 0 , sizeof( sockaddr_in ) );                  conn_fd = accept( listen_fd, (sockaddr*)&clnAddr, &clnLen );                  if( conn_fd < 0 )                  {                      if( errno == EWOULDBLOCK )                          printf("EWOULDBLOCK\n");                      perror("conn_fd < 0");                      break;                  }                  OnAccept( conn_fd, &clnAddr );              }              else if( events[i].events & EPOLLIN )  //对应的文件描述符可以读(包括对端SOCKET正常关闭)            {                  printf("event EPOLLIN.\n");                  if( ( sock_fd = events[i].data.fd ) < 0 )                      continue;                  OnRead( &events[i] );              }              else if( events[i].events & EPOLLOUT )  //对应的文件描述符可以写            {                  printf("event EPOLLOUT.\n");                  if( ( sock_fd = events[i].data.fd ) < 0 )                      continue;                  OnWrite( &events[i] );              }              else if( events[i].events & EPOLLHUP )  //对应的文件描述符被挂断            {                  printf("event EPOLLHUP. fd=%d.\n", events[i].data.fd );              }              else if( events[i].events & EPOLLERR )  //对应的文件描述符发生错误            {                  printf("event EPOLLERR. fd=%d.\n", events[i].data.fd );              }         }      }      close(epfd);    return 0;  }   

Clinet

#include <sys/socket.h>  #include <netinet/in.h>  #include <arpa/inet.h>#include <fcntl.h>#include <unistd.h>#include <stdio.h>#include <stdlib.h>#include <errno.h>#include <string.h>  int main(int argc, char *argv[]){if (argc < 3){fprintf(stderr,"Please enter the server's hostname!\n");return -1;}int sockfd;if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){perror("socket bind");return -2;}printf("IP: %s, PORT: %d\n", argv[1], atoi(argv[2]));struct sockaddr_in serv_addr;serv_addr.sin_family = AF_INET;serv_addr.sin_addr.s_addr = inet_addr(argv[1]);serv_addr.sin_port = htons(atoi(argv[2]));if (connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(struct sockaddr)) == -1){perror("connect error");return -3;}const char* str="Hello World";if (send(sockfd, str, strlen(str)+1, 0) == -1){perror("send error");}char buf[1024];for(int i = 0; i != 2; i++){int rn = read(sockfd, buf, 1024);buf[rn] = '\0';printf("%d: %s\n", i, buf);send(sockfd, buf, strlen(buf), 0);sleep(1);}close(sockfd);return 0;}



原创粉丝点击