目录linux
epoll的行为与poll(2)类似,监视多个有IO事件的文件描述符。epoll除了提供select/poll那种IO事件的水平触发(Level Triggered)外,还提供了边缘触发(Edge Triggered),这就使得用户空间程序有可能缓存IO状态,减小epoll_wait/epoll_pwait的调用,提升应用程序效率。数组
epoll_create(2)
建立一个新的epoll实例,并返回一个引用该实例的文件描述符
epoll_ctl(2)
建立epoll实例后,注册对感兴趣的文件描述符。当前注册在epoll实例上的文件描述符集被称为epoll集合。
epoll_wait(2)
等待I/O事件,若是当前没有事件可用,则阻塞调用线程。缓存
水平触发
和边沿触发
epoll事件分布接口既能够表现为边缘触发(ET),也能够表现为水平触发(LT)。这两种机制的区别
能够这样描述。假设有这种状况发生:数据结构
若是使用边缘触发标志将rfd文件描述符注册到epoll接口,那么第五步的epoll_wait(2)的调用可能会挂起,尽管文件输入缓冲区仍然有1kb数据可读;同时,远程对等端可能正在指望基于它已发送的数据的应答。这样作的缘由是,只有在被监视文件描述符上发生更改时,边缘触发模式才交付事件。所以,在步骤5中,调用者可能会以等待那些仍在输入缓冲区中的数据的状态下结束。
在上面的例子中,将生成rfd上的一个事件,由于在2中完成了写入,而在3中使用了该事件。因为在4中完成的读操做不会消耗整个缓冲区数据,因此在步骤5中完成的对epoll_wait(2)的调用可能会无限期阻塞。并发
使用EPOLLET标志的应用程序应该使用非阻塞文件描述符,以免在处理多个文件描述符时出现有阻塞的读写饥饿任务。建议使用epoll做为边沿触发(EPOLLET)接口的方式以下:
i、 具备非阻塞文件描述符
ii、只有在read(2)或write(2)返回EAGAIN后才等待事件。
相反,当EPOLLET做为水平触发接口使用时(默认状况下,没有指定EPOLLET), epoll只是一个更快的poll(2),而且能够在使用后者的任何地方使用,由于它具备相同的语义。socket
select能打开的文件描述符有必定的限制,FD_SETSIZE设置,默认值是2048,有两种解决方法,一、修改它的值,而后从新编译内核。二、使用多进程加入要并发20w个客户,那么就要开100进程;epoll则没有这个限制,它所支持的FD上限是最大能够打开文件的数目,这个数字通常远大于2048,举个例子,在1GB内存的机器上大约是2万左右,具体数目能够cat /proc/sys/fs/file-max察看,通常来讲这个数目和系统内存关系很大。函数
select/poll采用轮询的方式扫描文件描述符,文件描述符数量越多,性能越差;内核 / 用户空间内存拷贝问题,select/poll须要复制大量的句柄数据结构,产生巨大的开销;select/poll返回的是含有整个句柄的数组,应用程序须要遍历整个数组才能发现哪些句柄发生了事件,致使效率呈现线性降低。可是epoll不存在这个问题,它只会对"活跃"的socket进行操做---这是由于在内核实现中epoll是根据每一个fd上面的callback函数实现的。性能
select/poll的触发方式是水平触发,应用程序若是没有完成对一个已经就绪的文件描述符进行IO操做,那么以后每次select/poll调用仍是会将这些文件描述符通知进程。测试
select/poll和epoll都须要内核把FD消息通知给用户空间,如何避免没必要要的内存拷贝很重要,在这点上,select/poll须要复制整个FD数组,产生巨大的开销;而epoll是经过内核于用户空间mmap同一块内存实现的。ui
int epoll_create(int size);
int epoll_create1(int flags);
建立一个epoll的句柄。自从linux2.6.8以后,size参数是被忽略的,更推荐使用epoll_crete1(0)来替代,flags能够设置EPOLL_CLOEXEC标志
#include <sys/epoll.h> int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
该系统调用对文件描述符epfd引用的epoll(7)实例执行控制操做。它请求对目标文件描述符fd执行操做op。
epfd
: epoll_create建立的文件描述符.
op
:参数的有效参数为:
EPOLL_CTL_ADD
在文件描述符epfd引用的epoll实例上注册目标文件描述符fd。
EPOLL_CTL_MOD
修改已注册描述符fd关联的事件。
EPOLL_CTL_DEL
从epfd引用的epoll实例中删除(取消注册)目标文件描述符fd。该事件将被忽略,而且能够是NULL
fd
:待监听的fd
epoll_event
: 描述连接到文件描述符fd的对象,它的定义以下
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 :关联的文件描述符能够读(包括对端SOCKET正常关闭);
EPOLLOUT:关联的文件描述符能够写;
EPOLLPRI:关联的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:关联的文件描述符发生错误;
EPOLLHUP:关联的文件描述符被挂断;
EPOLLRDHUP:流套接字对等关闭链接,或半关闭写。(当使用边缘触发监视时,此标记对于编写简单代码检测对等端是否关闭特别有用。2.6.17引入)
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来讲的。
EPOLLONESHOT:只监听一次事件,当监听完此次事件以后,若是还须要继续监听这个fd的话,须要再次把这个fd加入到EPOLL队列里
它们在内核头文件里的定义以下:
33 34 enum EPOLL_EVENTS 35 { 36 EPOLLIN = 0x001, 37 #define EPOLLIN EPOLLIN 38 EPOLLPRI = 0x002, 39 #define EPOLLPRI EPOLLPRI 40 EPOLLOUT = 0x004, 41 #define EPOLLOUT EPOLLOUT 42 EPOLLRDNORM = 0x040, 43 #define EPOLLRDNORM EPOLLRDNORM 44 EPOLLRDBAND = 0x080, 45 #define EPOLLRDBAND EPOLLRDBAND 46 EPOLLWRNORM = 0x100, 47 #define EPOLLWRNORM EPOLLWRNORM 48 EPOLLWRBAND = 0x200, 49 #define EPOLLWRBAND EPOLLWRBAND 50 EPOLLMSG = 0x400, 51 #define EPOLLMSG EPOLLMSG 52 EPOLLERR = 0x008, 53 #define EPOLLERR EPOLLERR 54 EPOLLHUP = 0x010, 55 #define EPOLLHUP EPOLLHUP 56 EPOLLRDHUP = 0x2000, 57 #define EPOLLRDHUP EPOLLRDHUP 58 EPOLLEXCLUSIVE = 1u << 28, 59 #define EPOLLEXCLUSIVE EPOLLEXCLUSIVE 60 EPOLLWAKEUP = 1u << 29, 61 #define EPOLLWAKEUP EPOLLWAKEUP 62 EPOLLONESHOT = 1u << 30, 63 #define EPOLLONESHOT EPOLLONESHOT 64 EPOLLET = 1u << 31 65 #define EPOLLET EPOLLET 66 }; 67 68 69 /* Valid opcodes ( "op" parameter ) to issue to epoll_ctl(). */ 70 #define EPOLL_CTL_ADD 1 /* Add a file descriptor to the interface. */ 71 #define EPOLL_CTL_DEL 2 /* Remove a file descriptor from the interface. */ 72 #define EPOLL_CTL_MOD 3 /* Change file descriptor epoll_event structure. */
#include <sys/epoll.h> int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); int epoll_pwait(int epfd, struct epoll_event *events, int maxevents, int timeout, const sigset_t *sigmask);
等待在epoll监控的事件中已经发生的事件。
epfd
: epoll_create() 的返回值.
events
: 分配好的epoll_event结构体数组,epoll将会把发生的事件赋值到events数组中(events不能够是空指针,内核只负责把数据复制到这个events数组中,不会去帮助咱们在用户态中分配内存)
maxevents
: maxevents告知内核这个events有多大,这个 maxevents的值大于0(不然Error :Invalid argument)
timeout
: 超时时间(毫秒,0会当即返回,-1将不肯定,也有说法说是永久阻塞)。若是函数调用成功,返回对应I/O上已准备好的文件描述符数目,如返回0表示已超时,它会阻塞直到
此程序简单测试一下三个API,注册标准输出的描述符到epoll,监视标准输出的读事件,触发后回显一遍,quit退出程序.
#include <fcntl.h> #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <time.h> #include <errno.h> #include <sys/epoll.h> #include <vector> typedef std::vector<struct epoll_event> PollFdList; int main(int argc ,char **argv) { int fd; char buf[1024]; int i,res,real_read, maxfd; if((fd=open("/dev/stdin",O_RDONLY|O_NONBLOCK)) < 0) { fprintf(stderr,"open data1 error:%s",strerror(errno)); return 1; } PollFdList m_pollfds; int epfd = epoll_create1(EPOLL_CLOEXEC); struct epoll_event ev; ev.events = EPOLLIN | EPOLLPRI; ev.data.fd = fd; epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev); m_pollfds.resize(1024); while(1) { int ret = epoll_wait(epfd, m_pollfds.data(), m_pollfds.size(), 5000); if (ret < 0) { printf("ePoll error : %s\n",strerror(errno)); return 1; } if(ret == 0){ printf("ePoll timeout\n"); continue; } for (i = 0; i< 1; i++) { if (m_pollfds[i].events & EPOLLIN) { memset(buf, 0, 1024); real_read = read(m_pollfds[i].data.fd, buf, 1024); if (real_read < 0) { if (errno != EAGAIN) { printf("read eror : %s\n",strerror(errno)); continue; } } else if (!real_read) { close(m_pollfds[i].data.fd); m_pollfds[i].events = 0; } else { if (i == 0) { buf[real_read] = '\0'; printf("%s", buf); if ((buf[0] == 'q') || (buf[0] == 'Q')) { printf("quit\n"); return 1; } } else { buf[real_read] = '\0'; printf("%s", buf); } } } } } exit(0); }
./test hello hello hello epoll hello epoll ePoll timeout quit quit quit