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,关联的文件可写;
EPOLLRDHUP,流socket端关闭连接;
EPOLLPRI,紧急数据(urgent data)可读;
EPOLLERR,关联文件错误发生;
EPOLLHUP,关联文件结束;
EPOLLET,使用边缘触发;
EPOLLLT,使用水平触发;
epoll_ctl调用成功返回0,失败返回-1,并且设置errno。
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
当epfd epoll实例设置好后,调用epoll_wait函数进入监视阶段,events用来存放满足的时间。
边缘触发(Edge-Triggered,EPOLLET)和水平触发(Level-Triggered,EPOLLLT)是epoll支持的两种触发方式。假如epoll监听的文件描述符接收到2K的数据,epoll_wait会返回,并保存事件到events数组中,接着程序使用read系统调用从fd中读取1K数据,则在fd的缓存buffer中还剩下1K的数据未读,这个时候使用LT关联的文件会再次告诉epoll有数据可读,而使用ET方式关联的则不会再次通知。
在通常的使用中ET触发模式常常跟nonblocking文件描述符关联,建议使用ET模式的程序使用以下流程:
1、于非阻塞方式打开文件描述符;
2、调用read或者write函数知道它们返回EAGAIN错误。
Example:
#define MAX_EVENTS 10
struct epoll_event ev, events[MAX_EVENTS];
int listen_sock, conn_sock, nfds, epollfd;
/* Set up listening socket, 'listen_sock' (socket(),
bind(), listen()) */
epollfd = epoll_create(10);
if (epollfd == -1) {
perror("epoll_create");
exit(EXIT_FAILURE);
}
ev.events = EPOLLIN;
ev.data.fd = listen_sock;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listen_sock, &ev) == -1) {
perror("epoll_ctl: listen_sock");
exit(EXIT_FAILURE);
}
for (;;) {
nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);
if (nfds == -1) {
perror("epoll_pwait");
exit(EXIT_FAILURE);
}
for (n = 0; n < nfds; ++n) {
if (events[n].data.fd == listen_sock) {
conn_sock = accept(listen_sock,
(struct sockaddr *) &local, &addrlen);
if (conn_sock == -1) {
perror("accept");
exit(EXIT_FAILURE);
}
setnonblocking(conn_sock);
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = conn_sock;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, conn_sock,
&ev) == -1) {
perror("epoll_ctl: conn_sock");
exit(EXIT_FAILURE);
}
} else {
do_use_fd(events[n].data.fd);
}
}
do_use_fd()函数反复的read操作,直到返回EAGAIN后退出该函数。