epoll的使用

关于epoll

    上一篇博文提到过select的使用方法。今天说一下epoll的使用方法。epoll相对于select而言有如下几个方面的优点:

  • 对于要监听的fd的数量没有限制

  • 内核会保存需要监听的fd,无需每次都进行初始化

  • 只返回产生事件(可读取、可写等)的fd,无需遍历监听的所有fd

     使用epoll主要用进行一步阻塞的调用。其基本的步骤为

  • 首先通过epoll_create调用创建一个epoll文件描述符(epfd)

  • 通过epoll_ctl对这个描述符进行设置,具体包括:添加需要监听的文件描述符、socket描述符等;设置相关监听的类型(例如监听是否可读:EPOLLIN,)等

  • 通过epoll_wait进行事件的监听

       看一段代码示例:

       #include <sys/epoll.h>

#include <unistd.h>

#include <stdlib.h>

#include <sys/types.h>

 

int main(int argc, char* argv[])

{

  //epoll_event用于epoll_ctl中与关联的fd进行设置,events用于epoll_wait返回可以处理的fd的信息

   struct epoll_event epollEvent, events[10];

   //监听时间设置为可读时触发

   epollEvent.events = EPOLLIN ;

   epollEvent.data.fd = 0;

   char buf[100];

  //创建epfd

   int epollFd = epoll_create(10);

   const char* errStr = "epoll_Create failed\n";

  //如果创建失败,则返回

   if(epollFd < 0)

   {

       

       write(2, errStr, 20);

       exit(-1);

   }

   errStr = "epoll_ctl failed\n";

 //监听标准输入, 将stdin添加至监听fd集合中

   if(-1 == epoll_ctl(epollFd, EPOLL_CTL_ADD, 0, &epollEvent))

   {

       write(2, errStr, 17);

       exit(-1);

   }

   int nfds;

   const char* splitLine = "----------\n";

   while(1)

   {

     //当stdin可读时,epoll_wait返回,超时时间设置为一直阻塞,知道有可用的fd时返回

       nfds = epoll_wait(epollFd, events, 10, -1);

       int i;

      //对可用的fd进行处理

       for(i = 0; i < nfds; ++i)

       {

           int readCount = read(events[i].data.fd, buf, 5);

           if(0 == readCount)

           {

               close(epollFd);

               exit(0);

           }

           write(1, buf, readCount);

           write(1, splitLine, 11);

       }

       //epoll_ctl(epollFd, EPOLL_CTL_MOD, 0, &epollEvent);

   }

   close(epollFd);

   return 0;

}

辅助的python脚本

#!/usr/bin/env python

#coding:utf-8


import time


output_str = "hello world"


for i in range(0, 5):

   print output_str

   time.sleep(2)

gcc -o test_epoll test_epoll.c -g -Wall

python cat_py.py | ./test_epoll

下面具体讲解一下每个函数的用法,详情请参考linux的官方文档(http://man7.org/linux/man-pages/man7/epoll.7.html),

     头文件#include <sys/epoll.h>

     函数原型:int epoll_create(int size);

size为需要监听的文件描述的数量,内核用该值去分分配相应的空间,但是自从内核2.6.8之后,使用动态的空间分配方式,size的取值已经被忽略,但是必须设置为大于0的数值。该函数的返回值为epoll的文件描述符,该描述符将用于后续的epoll_ctl和epoll_wait等的操作。

函数原型: int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

其中epfd为epoll_create的返回值,就是epoll的文件描述符,所有需要监听的fd都需要与epfd进行关联。

op为EPOLL_CTL_ADD、EPOLL_CTL_MOD和EPOLL_CTL_DEL三者之一EPOLL_CTL_ADD用于添加需要监听的文件描述符,即参数中的fd,将fd添加到需要监听的文件描述符集合中。EPOLL_CTL_MOD用于改变与fd进行关联的event。EPOLL_CTL_DEL用于将fd从监听的集合中删除。

对于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 */

};

此处主要设置epoll_event中的events,events的取值主要由EPOLLIN、EPOLLOUT、EPOLLRDHUP和EPOLLET等具体参考http://man7.org/linux/man-pages/man2/epoll_ctl.2.html  

EPOLLIN监听fd是否可读,EPOLLOUT监听fd是否可写。需要着重指出的是:epoll默认的事件触发机制是水平触发,因此如果使用EPOLLET则设置为边缘触发,对于边缘触发,在一次触发以后(epoll_wait返回处理后)要需要再次调用epoll_ctl使用EPOLL_CTL_MOD进行事件关联。否则如果本次处理不完全,例如有2k的字节可读,而只读取了1k,如果不再次设置,则再次调用epoll_wait后不会返回,因而可能会造成数据的丢失。epoll_data与fd想关联,保存着fd相关的信息。

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

epfd为epoll的fd,events为返回的对应fd的event的数组,maxevents为一次可以处理的fd的最大数量,通常设置为需要监听的fd的数量,timeout为超时返回的秒数。如果为0,则立即返回,如果为-1则一直阻塞,直到有需要处理的fd则返回。epoll_wait的返回值为目前可用的fd的数量。另外,如果发生错误则返回-1并设置errno。









posted @ 2013-10-06 13:44  冷暖自知01  阅读(382)  评论(0编辑  收藏  举报