poll和select的改进版,在一个程序需要处理数百个文件描述符时很有用
2.6内核引入epoll机制,解决了poll和select的性能问题,并加入了一些新特性
poll和select每次调用都需要所有被监听的文件描述符,内核需要遍历所有的文件描述符,当数量变大时,性能消耗巨大
epoll将监听注册从实际监听中分离,从而解决了该问题
一个系统调用初始化一个epoll上下文,另一个从上下文中加入和删除文件描述符,第三个执行真正的事件等待
创建一个epoll实例
#include<sys/epoll.h>
int epoll_create(int size);
创建一个epoll实例,返回与该实例相关的文件描述符
size指定需要监听的文件描述符数量,传递一个近似值会带来性能提升
出错时返回-1
,并设置errno
EINVAL size不是正数
ENFILE 系统达到打开文件数上限
ENOMEN 没有足够内存完成操作
int epfd= epoll_create(100);
if(epfd<0){perror("epoll create err");
}
文件描述符需要调用close
关闭
控制epoll
#include<sys/epoll.h>
int epoll_ctl(int epfd, in op, int fd, struct epoll_event* event);
向指定的epoll上下文加入或删除文件描述符
struct epoll_event{__u32 events;union{void* ptr;int fd;__u32 u32;__u64 u64;}data;
};
参数op
指定要对fd进行的操作
参数event
指定更具体的行为
op
取值如下
op字段 | 说明 |
---|---|
EPOLL_CTL_ADD | 添加fd到epfd指定的epoll实例中 |
EPOLL_CTL_MOD | 使用event修改在已有fd上的监听行为 |
EPOLL_CTL_DEL | 从epfd指定的epoll实例中删除fd |
epoll_event
结构体指定了在文件描述符上的监听事件
epoll_event.events
字段取值如下,可用位或运算符同时设置
events字段 | 说明 |
---|---|
EPOLLERR | 文件出错,即使没有设置,该事件也是会被监听 |
EPOLLET | 在监听文件上开启边沿触发 |
EPOLLHUP | 文件被挂起,即使没有设置,该事件也是会被监听 |
EPOLLIN | 文件未阻塞,可读 |
EPOLLONESHOT | 在一次事件产生并被处理后,文件不再被监听 |
EPOLLOUT | 文件未阻塞,可写 |
EPOLLPRI | 高优先级的带外数据可读 |
epoll_event.data
字段由用户使用,确认事件后会返回给用户
通常将epoll_event.data.fd
设置为指定的fd
,从而可以知道触发事件的文件描述符
返回值,成功返回0
,失败返回-1
,并设置errno
为下列值
errno值 | 说明 |
---|---|
EBADF | epfd不是有效的epoll实例,或fd不是有效的文件描述符 |
EEXIST | op为EPOLL_CTL_ADD,但fd已与epfd关联 |
EINVAL | epfd不是一个epoll实例,epfd与fd相同,或op无效 |
ENOENT | op为EPOLL_CTL_MOD和EPOLL_CTL_DEL,但fd没有与epfd关联 |
ENOMEN | 没有足够内存完成进程请求 |
EPERM | fd不支持epoll |
在epfd实例中加入一个fd指定的监听文件
struct epoll_event evt;
evt.data.fd= fd;
evt.events= EPOLLIN | EPOLLOUT;
int ret= epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &evt);
if(ret){perror("epoll ctl");
}
修改epfd实例中的fd上的一个监听事件
struct epoll_event evt;
evt.data.fd= fd;
evt.events= EPOLLIN;
int ret= epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &evt);
if(ret){perror("epoll ctl");
}
当op
为EPOLL_CTL_DEL时,因为没有设置事件,event
参数可以为NULL
等待epoll事件
#include<sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event* event, int maxevents, int timeout);
等待epfd实例中的fd上的事件,超时timeout毫秒
成功返回,events指向包含epoll_event结构体的内存,最多可以有maxevents个事件
返回值为事件数,出错返回-1
,并设置errno
为如下值
errno值 | 说明 |
---|---|
EBADF | epfd为无效文件描述符 |
EFAULT | 进程对events指向的内存无写权限 |
EINTR | 系统调用在完成前被信号中断 |
EINVAL | epfd不是有效的epoll实例,和maxevents小于0 |
若timeout为0,即使没有事件发生,也会立即返回,此时调用返回0
若timeout为-1,将一直等待到有事件发生
一个完整的epoll_wait示例
#define MAX_EVENTS 64
int epfd;struct epoll_event* events;
events= malloc(sizeof(struct epoll_event)* MAX_EVENTS);
if(!events){perror("malloc")return 0;
}int nr_events= epoll_wait(epfd, events, MAX_EVENTS, -1);
if(nr_events<0){perror("epoll_wait");free(events);return 0;
}for(int i=0; i<nr_events; i++){printf("event=%ld on fd=%d\n", events[i].events, events[i].data.fd);
}
free(events);
边沿触发事件和水平触发事件
若epoll_ctl
参数event
中的events
设置为EPOLLET
,fd上的监听为边沿触发,相反则为水平触发
如下生产者和消费者在通过unix管道通信的情况
1.生产者向管道写入1kb数据
2.消费者在管道上调用epoll_wait
,等待管道出现数据,从而可读
对于水平触发的监听,步骤2对epoll_wait
的调用会立即返回
对于边沿触发的监听,调用会等到步骤1发生后才会返回,即使管道已有数据可读,调用也会等到有数据写入才会返回
水平触发是默认行为,是大多数开发者期望的,也是poll和select的行为
边沿触发需要一个不同的方式来写程序,通常利用非阻塞IO,并需要仔细检查EAGAIN