神刀安全网

epoll入门实例

epoll是目前进行服务器端编程的普遍选择,好处很多,这里不再赘述,本文主要描述如何在c语言中使用epoll的完整样例程序。首先介绍用到的数据结构和三个api说明,然后通过编写一个打印所有输入到socket的字符输出到终端的服务器端的程序来完成整个例子。

epoll_event是用来对要监控的socket描述, 它包括epoll_data_t和要监控的事件类型的(一个__uint32_t类型的events)。epoll_data_t里的fd是用来存储要监控的文件描述符。

events 结构体中第一个参数支持的事件类型

– EPOLLIN,读事件

– EPOLLOUT,写事件

– EPOLLPRI,带外数据,与select的异常事件集合对应

– EPOLLRDHUP,TCP连接对端至少写写半关闭

– EPOLLERR,错误事件

– EPOLLET,设置事件为边沿触发

– EPOLLONESHOT,只触发一次,事件自动被删除

epoll在一个文件描述符上只能有一个事件,在一个描述符上添加多个事件,会产生EEXIST的错误。同样,删除epoll的事件,只需描述符就够了

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 */ };

使用epoll的三个api

头文件 /usr/include/sys/epoll.h

1. 生成一个epoll专用的文件描述符

如果调用成功返回0,不成功返回-1

int epoll_create(int size) 

epoll_create返回的是一个文件描述符,也就是说epoll是以特殊文件的方式体现给用户__size提示操作系统,用户可能要使用多少个文件描述符,该参数已经废弃,填写一个大于0的正整数

2.用于控制某个文件描述符上的事件,可以注册事件,修改事件,删除事件。如果调用成功返回0,不成功返回-1

int epoll_ctl(     int epfd,                    //由 epoll_create 生成的epoll专用的文件描述符      int op,                      //要进行的操作例如注册事件,可能的取值EPOLL_CTL_ADD 注册、                                  //EPOLL_CTL_MOD 修改、EPOLL_CTL_DEL 删除      int fd,                      //关联的文件描述符      struct epoll_event *event    //指向epoll_event的指针      ) 

3.用于轮询I/O事件的发生,返回发生事件数

int epoll_wait(      int epfd,                   //由epoll_create 生成的epoll专用的文件描述符      struct epoll_event * events,//用于回传代处理事件的数组      int maxevents,              //每次能处理的事件数      int timeout                 //等待I/O事件发生的超时值                                  //为0的时候表示马上返回,为-1的时候表示一直等下去,直到有事件                                  //为任意正整数的时候表示等这么长的时间,如果一直没有事件                                  //一般如果网络主循环是单独的线程的话,可以用-1来等,这样可以保证一些效率                                  //如果是和主逻辑在同一个线程的话,则可以用0来保证主循环的效率。      ) 

epoll的api使用方式

1.epoll_create 生成的epoll专用的文件描述符

2.使用epoll_ctl注册事件,修改事件,删除事件对应的文件描述符到epollfd指定的epoll内核事件表中

3.使用epoll_wait阻塞等待注册的文件描述符上可读事件的发生

4.当有新客户端的连接或者客户端的数据写入,返回需要处理的事件数目

epoll的两种模式:

1. 水平触发(LT):使用此种模式,当数据可读的时候,epoll_wait()将会一直返回就绪事件。如果你没有处理完全部数据,并且再次在该epoll实例上调用epoll_wait()才监听描述符的时候,它将会再次返回就绪事件,因为有数据可读。ET只支持非阻塞socket。

2. 边缘触发(ET):使用此种模式,只能获取一次就绪通知,如果没有处理完全部数据,并且再次调用epoll_wait()的时候,它将会阻塞,因为就绪事件已经释放出来了。

ET的效能更高,但是对程序员的要求也更高。在ET模式下,我们必须一次干净而彻底地处理完所有事件。LT两种模式的socket都支持。

实例代码

1.创建并绑定服务器端socket

采用一种可移植的方式来生产socket,getaddrinfo返回对应的网卡信息,遍历对应的网络接口生成socket

成功返回socket文件描述符,失败返回-1

static int create_and_bind (char *port) {   struct addrinfo hints;   struct addrinfo *result, *rp;   int s, sfd;    memset (&hints, 0, sizeof (struct addrinfo));   hints.ai_family = AF_UNSPEC;     /* Return IPv4 and IPv6 choices */   hints.ai_socktype = SOCK_STREAM; /* We want a TCP socket */   hints.ai_flags = AI_PASSIVE;     /* All interfaces */    s = getaddrinfo (NULL, port, &hints, &result);   if (s != 0)     {       fprintf (stderr, "getaddrinfo: %s/n", gai_strerror (s));       return -1;     }    for (rp = result; rp != NULL; rp = rp->ai_next)     {       sfd = socket (rp->ai_family, rp->ai_socktype, rp->ai_protocol);       if (sfd == -1)         continue;        s = bind (sfd, rp->ai_addr, rp->ai_addrlen);       if (s == 0)         {           /* We managed to bind successfully! */           break;         }        close (sfd);     }    if (rp == NULL)     {       fprintf (stderr, "Could not bind/n");       return -1;     }    freeaddrinfo (result);    return sfd; }

2.设置socket为非阻塞模式通过在文件描述符上设置 O_NONBLOCK 表识来实现非阻塞socket

static int make_socket_non_blocking (int sfd) {   int flags, s;    flags = fcntl (sfd, F_GETFL, 0);   if (flags == -1)     {       perror ("fcntl");       return -1;     }    flags |= O_NONBLOCK;   s = fcntl (sfd, F_SETFL, flags);   if (s == -1)     {       perror ("fcntl");       return -1;     }    return 0; }

3.event 循环处理

#define MAXEVENTS 64  int main (int argc, char *argv[]) {   int sfd, s;   int efd;   struct epoll_event event;   struct epoll_event *events;    if (argc != 2)     {       fprintf (stderr, "Usage: %s [port]/n", argv[0]);       exit (EXIT_FAILURE);     }    sfd = create_and_bind (argv[1]);   if (sfd == -1)     abort ();    s = make_socket_non_blocking (sfd);   if (s == -1)     abort ();    s = listen (sfd, SOMAXCONN);   if (s == -1)     {       perror ("listen");       abort ();     }    efd = epoll_create1 (0);   if (efd == -1)     {       perror ("epoll_create");       abort ();     }    event.data.fd = sfd;   event.events = EPOLLIN | EPOLLET;   s = epoll_ctl (efd, EPOLL_CTL_ADD, sfd, &event);   if (s == -1)     {       perror ("epoll_ctl");       abort ();     }    /* Buffer where events are returned */   events = calloc (MAXEVENTS, sizeof event);    /* The event loop */   while (1)     {       int n, i;        n = epoll_wait (efd, events, MAXEVENTS, -1);       for (i = 0; i < n; i++)  {    if ((events[i].events & EPOLLERR) ||               (events[i].events & EPOLLHUP) ||               (!(events[i].events & EPOLLIN)))      {               /* An error has occured on this fd, or the socket is not                  ready for reading (why were we notified then?) */        fprintf (stderr, "epoll error/n");        close (events[i].data.fd);        continue;      }     else if (sfd == events[i].data.fd)      {               /* We have a notification on the listening socket, which                  means one or more incoming connections. */               while (1)                 {                   struct sockaddr in_addr;                   socklen_t in_len;                   int infd;                   char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];                    in_len = sizeof in_addr;                   infd = accept (sfd, ∈_addr, ∈_len);                   if (infd == -1)                     {                       if ((errno == EAGAIN) ||                           (errno == EWOULDBLOCK))                         {                           /* We have processed all incoming                              connections. */                           break;                         }                       else                         {                           perror ("accept");                           break;                         }                     }                    s = getnameinfo (∈_addr, in_len,                                    hbuf, sizeof hbuf,                                    sbuf, sizeof sbuf,                                    NI_NUMERICHOST | NI_NUMERICSERV);                   if (s == 0)                     {                       printf("Accepted connection on descriptor %d "                              "(host=%s, port=%s)/n", infd, hbuf, sbuf);                     }                    /* Make the incoming socket non-blocking and add it to the                      list of fds to monitor. */                   s = make_socket_non_blocking (infd);                   if (s == -1)                     abort ();                    event.data.fd = infd;                   event.events = EPOLLIN | EPOLLET;                   s = epoll_ctl (efd, EPOLL_CTL_ADD, infd, &event);                   if (s == -1)                     {                       perror ("epoll_ctl");                       abort ();                     }                 }               continue;             }           else             {               /* We have data on the fd waiting to be read. Read and                  display it. We must read whatever data is available                  completely, as we are running in edge-triggered mode                  and won't get a notification again for the same                  data. */               int done = 0;                while (1)                 {                   ssize_t count;                   char buf[512];                    count = read (events[i].data.fd, buf, sizeof buf);                   if (count == -1)                     {                       /* If errno == EAGAIN, that means we have read all                          data. So go back to the main loop. */                       if (errno != EAGAIN)                         {                           perror ("read");                           done = 1;                         }                       break;                     }                   else if (count == 0)                     {                       /* End of file. The remote has closed the                          connection. */                       done = 1;                       break;                     }                    /* Write the buffer to standard output */                   s = write (1, buf, count);                   if (s == -1)                     {                       perror ("write");                       abort ();                     }                 }                if (done)                 {                   printf ("Closed connection on descriptor %d/n",                           events[i].data.fd);                    /* Closing the descriptor will make epoll remove it                      from the set of descriptors which are monitored. */                   close (events[i].data.fd);                 }             }         }     }    free (events);    close (sfd);    return EXIT_SUCCESS; }

main函数的流程是

1. create_and_bind创建服务器端的socket描述符

2. 设置描述符为非阻塞

3. 监听描述符

4. 创建epoll文件描述符efd

5. 使用边缘触发的方式添加sfd输入监听事件

最外层的while循环时主事件循环(event loop)。调用epoll_wait阻塞等待事件发生,当事件到达epoll_wait返回事件在事件参数中,一批epoll_event结构体。epoll事件循环中epoll实例在建立新连接时候添加事件和当断开连接的时候删除事件。

当事件发生时,有如下几种方式

错误:当发生错误,或者不是可读事件通知时,简单关闭文件描述符,关闭文件描述符会自动从efd的监控集中删除。

新连接:当监听描述符可读时,表示有新的连接到达,accept()新连接,设置新连接描述符为非阻塞并添加到efd监控集中。

客户端数据:当任何一个客户端文件描述符可读,使用read()每次读区512字节循环读取。 因为我们需要读取当前所有可读区数据 ,当边缘触发的情况下不会再次通知可读。 读取到的数据调用write写到标准输出stdout (fd=1)。如果read(2)返回0,表示EOF并切可以关闭客户端连接。如果返回-1并且 errno设置为EAGAIN表示这个事件所有可读的数据读取完毕可以返回进入主事件循环。

就这样不断的循环,添加和删除文件描述符到efd的监控集中。https://banu.com/blog/2/how-to-use-epoll-a-complete-example-in-c/

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » epoll入门实例

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
分享按钮