Epoll的用法
select与epoll
在旧的Linux中,通常都在使用select来做事件触发,而在新的Linux内核中,开始使用epoll来代替。相比于select,epoll最大的好处就是不会随着监听fd数目的增长而降低效率。
当使用select时,它是采用轮询来处理的,轮询的fd数目越多,耗时越长,而且select同时监听的fd数目有限,通常为1024个,当然,这可以通过修改相关头文件然后对内核进行重新编译来解决。
epoll接口
1. 创建epoll句柄
int epfd = epoll_create(int size);
创建一个epoll句柄,size用来告诉内核这个监听的数目一共多大。需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程ID/fd是能够看到这个fd的,所以在使用完epoll后,必须调用close()函数进行关闭,否则可能导致fd被耗尽。
该函数生成一个epoll专用的文件描述符,它其实是在内核申请一空间,用来存放你想关注的socket fd上是否发生以及发生了什么事件,size就是你在这个epoll fd上能关注的最大socket fd数目。
2. 将被监听的描述符添加到epoll句柄或从epoll句柄中删除 或者对监听事件进行修改
int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event);
该函数用来控制某个epoll文件描述符上的事件,可以注册事件、修改事件和删除事件。其中,epfd为由epoll_create生成的epoll专用的文件描述符;op为要进行的操作注册事件,可能的取值有EPOLL_CTL_ADD注册、EPOLL_CTL_MOD修改、EPOLL_CTL_DEL删除;fd为关联的文件描述符;event为指向epoll_event的指针。
epoll和select的一个不同点就是select()是在监听事件时告诉内核要监听什么类型的事件,而epoll是先注册要监听的事件类型。
这里epoll_ctl的第四个参数为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 触发该事件,表示对应的文件描述符上有可读数据
EPOLLOUT 触发该事件,表示对应的文件描述符上可以写数据
EPOLLPRI 表示对应的文件描述符有紧急的数据可读
EPOLLERR 表示对应的文件描述符发生错误
EPOLLHUP 表示对应的文件描述符被挂断
EPOLLET 将EPOLL设置为边缘触发模式(Edge Triggered)
EPOLLONESHOT 只监听一次事件,当监听完这次事件之后,如果还需要监听这个socket的话,需要再把这个 socket加入到EPOLL队列里
举个例子:
struct epoll_event ev;
ev.data.fd = listenfd; // 设置与要处理的事件相关的文件描述符
ev.events = EPOLLIN | EPOLLET; // 设置要处理的事件类型
epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev); // 注册epoll事件
3. 等待事件触发,当超过timeout还没有事件触发时,就超时
int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout);
等待事件的产生,类似于select()调用,参数events用来从内核得到事件的集合,maxevents告诉内核这个events有多大(数组成员的个数),这个maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定)。
该函数返回需要处理的事件数目,如返回0表示已超时;返回的事件集合在events数组中,数组中实际存放的成员个数是函数的返回值。
epoll_wait运行的原理是:等待注册在epfd上的socket fd事件的发生,如果发生则将发生的socket fd和事件类型放入到events数组中,并且将注册在epfd上的 socket fd的事件类型清空,所以如果下一个循环你还要关注这个socket fd的话,则需要使用epoll_ctl(epfd, EPOLL_CTL_MOD, listenfd, &ev)来重新设置socket fd的事件类型,这时不用EPOLL_CTL_ADD,因为socket fd并未清空,只是事件类型清空。