在linux 没有实现epoll事件驱动机制之前,我们一般选择用select或者poll等IO多路复用的方法来实现并发服务程序。自Linux 2.6内核正式引入epoll以来,epoll已经成为了目前实现高性能网络服务器的必备技术,在大数据、高并发、集群等一些名词唱得火热之年代,select和poll的用武之地越来越有限,风头已经被epoll占尽。
那究竟什么是epoll呢?epoll 是 Linux 内核的可扩展 I/O 事件通知机制,其最大的特点就是性能优异。
打个比方说说一下select,poll,epoll的区别:select就像一个兵长,每一个小兵路过他都要问一句“有没有事件发生啊?”,一个兵长的能力有限,他一次最多可以问1024个小兵;而poll这个兵长能力比select强一些,他不限制询问小兵的数量,也就是没有1024这个限制了。而epoll这个兵长就更厉害了更聪明了,他不仅不限制数量还不用一个一个亲自去问,他只需要坐在办公室喝茶了,因为他给每个小兵都写了一个名字在脸上,只要这个小兵来了,epoll兵长只要看一眼就知道发生什么事件了,然后再通知上级。
也就是说,epoll在获取事件的时候,无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。
水平触发:
是缺省的工作方式,什么叫缺省呢?就是当一个文件描述符准备就绪了,内核就会通知你可以对这个就绪的fd进行IO操作,如果你一直不操作内核就会一直提醒你直到你对这个fd进行处理。这种方式的好处就是可以保证出错的概率小一些,传统的select和poll都是采用的这种工作方式。
边沿触发:
是高速工作方式,在这种模式下,当描述符从未就绪变为就绪时,内核通 过epoll告诉你。然后内核默认你已经知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了
epoll的实现分为三步:
1.调用epoll_create()建立一个epoll对象(在epoll文件系统中为这个句柄对象分配资源)
2. 调用epoll_ctl()向epoll对象中添加连接的套接字
3. 调用epoll_wait()收集发生的事件的连接
1.创建epoll实例:int epoll_create();
#include <sys/epoll.h>
int epoll_create(int size);
2.修改epoll的兴趣列表:epoll_ctl()
#include <sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *ev);
第一个参数epfd:是epoll_create()的返回值;
第二个参数op:用来指定需要执行的操作,它可以是如下几种值:
第三个参数fd:指明了要修改兴趣列表中的哪一个文件描述符的设定。
第四个参数ev是指向结构体epoll_event的指针,结构体的定义如下:
typedef union epoll_data
{
void *ptr; /* Pointer to user-defind data */
int fd; /* File descriptor */
uint32_t u32; /* 32-bit integer */
uint64_t u64; /* 64-bit integer */
} epoll_data_t;
struct epoll_event
{
uint32_t events; /* epoll events(bit mask) */
epoll_data_t data; /* User data */
};
3.事件等待:epoll_wait()
#include <sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event *evlist, int maxevents, int timeout);
描述:系统调用epoll_wait()返回epoll实例中处于就绪态的文件描述符信息,单个epoll_wait()调用能够返回多个就绪态文件描述符的信息。调用成功后epoll_wait()返回数组evlist中的元素个数,如果在timeout超时间隔内没有任何文件描述符处于就绪态的话就返回0,出错时返回-1并在errno中设定错误码以表示错误原因。
第一个参数epfd是epoll_create()的返回值;
第二个参数evlist所指向的结构体数组中返回的是有关就绪态文件描述符的信息,数组evlist的空间由调用者负责申请;
第三个参数maxevents指定所evlist数组里包含的元素个数;
第四个参数timeout用来确定epoll_wait()的阻塞行为,有如下几种: