epoll理解及应用

epoll_event

epoll函数将通过如下结构体epoll_event,将发生变化的(发生事件的)文件描述符单独集中到一起。

struct epoll_event
{
    __uint32_t events;
    epoll_data_t data;
};

typedef union epoll_data
{
    void *ptr;
    int fd;
    __uint32_t u32;
    __uint64_t u64;
} epoll_data_t;

epoll_create

epoll_create: 创建保存epoll文件描述符的空间

#include <sys/epoll.h>

int epoll_create(int size);
/*
size:   `epoll`实例大小
*/

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

调用epoll_create函数创建的文件描述符保存空间称为“epoll例程”,但有些情况不同。

通过参数size传递的值决定epoll例程的大小,但该值只是向操作系统提的建议。

换言之,size并非用来决定epoll例程大小,而仅供操作系统参考。(Linux 2.6.8后内核会完全忽略size参数)

epoll_ctl

epoll_ctl: 向空间注册并注销文件描述符

#include <sys/epoll.h>

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
/*
epfd:   用于注册监视对象的`epoll`例程文件描述符。
op:     用于指定监视对象的增删改等操作
fd:     需要注册的监视对象文件描述符
event:  监视对象的事件
*/

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

参数op的可取值:

  1. EPOLL_CTL_ADD:将文件描述符注册到epoll例程。
  2. EPOLL_CTL_DEL:从epoll例程中删除文件描述符。如果fd取此值,则第四个参数event应为NULL
  3. EPOLL_CTL_MOD:更改注册的文件描述符的关注事件发生情况。

参数event的使用:这里用到的是epoll_event结构体的events成员,events成员用来指明关注的事件类型。 events可以指明的事件类型不止3种,它的可取值如下:

  • EPOLLIN: 表示对应的文件描述符可以读(包括对端SOCKET正常关闭)。
  • EPOLLOUT: 表示对应的文件描述符可以写。
  • EPOLLPRI: 表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来)。
  • EPOLLDHUP: 表示对应的文件描述符被挂断,在边缘触发模式下很有用。
  • EPOLLERR: 表示对应的文件描述符发生错误。
  • EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
  • EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里。发送一次事件后,相应的文件描述符不再收到事件通知。因此需要向epoll_ctl函数的第二个参数传递EPOLL_CTL_MOD,再次设置事件。

可以通过位运算同时传递多个上述参数。

epoll_wait

epoll_wait: 与select函数类似,等待文件描述符发生变化

#include <sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout);
/*
功能:监视发生事件的文件描述符

参数:
    `epfd`:        `epoll`例程的文件描述符;
    `events`:      保存发生事件的文件描述符集合的结构体地址;
    `maxevents`:   最多监听的事件数,必须大于 0;
    `timeout`:     超时时间,以`ms`为单位。如果`timeout`为 -1,则一直等待到事件发生。(注意类型为 int,和 select 函数中的不同)

返回值:成功时返回发生事件的文件描述符数量,失败时返回 -1。
*/

条件触发和边沿触发

epoll中有两种触发方式:条件触发LT(Level Trigger)和边缘触发ET(Edge Trigger)

  • 条件触发:只要输入缓冲中有数据就一直通知该事件。
  • 边缘触发:只有当输入缓冲收到数据时注册一次该事件,之后即使输入缓冲中还有数据也不会再注册。
  • epoll默认以条件触发方式工作。select函数也是以条件触发方式工作的。

条件触发

Example

该示例与echo_epollserv.c之间的差异如下:

  • 将调用read函数时使用的缓冲大小减为4 (第10行)
  • 插入验证epoll_wait函数调用次数的语句 (第56行)

边沿触发

Example

将文件(套接字)改为非阻塞模式

#include <fcntl.h>
int fcntl(int filedes, int cmd, ...);
        // 功能:更改或读取文件属性。
        // 参数:filedes:要更改属性的文件描述符;cmd:指明函数调用的目的;...:可变参数,根据 cmd 的不同值会有不同情况。

成功时返回cmd参数相关值,失败时返回-1

cmd的可取值(包括但不限于以下):

  1. F_GETFL:取此值时,fcntl函数用于获取文件描述符filedes的属性(会返回相应值)。
  2. F_SETFL:更改文件描述符属性。

将文件(套接字)改为非阻塞模式要使用如下的两条语句:

int flag = fcntl(fd, F_GETFL, 0);      // 获取之前的属性
fcntl(fd, F_SETFL, flag|O_NONBLOCK);   // 在此基础上添加非阻塞`O_NONBLOCK`标志。