epoll简介
epoll是Linux中的一种使用IO多路复用机制,即使用单个线程就可以监听多个文件描述符的读写事件,避免开启大量线程或者执行大量轮询的造成的不必要的性能开销。类似的机制还有select和poll,关于其区别参考。
头文件和API
#include <sys/epoll.h>
// 创建epoll,通常使用epoll_create即可,epoll_create1可以设置一些额外的配置
int epoll_create(int size);
int epoll_create1(int flags);
// 向epoll中添加、删除、修改fd
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
// 等待fd的事件
int epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout);
// epoll_pwait相比epoll_wait多了屏蔽指定信号的功能,即通过指定sigmask可以在
// epoll_pwait函数中修改信号屏蔽设置,epoll_pwait返回后恢复原始信号屏蔽设置
int epoll_pwait(int epfd, struct epoll_event *events,
int maxevents, int timeout,
const sigset_t *sigmask);
// epoll_pwait2和epoll_wait区别在于timeout可以指定的精度不同,epoll_pwait2
// 可以指定纳秒级别的精度(实际内核调度精度达不到),上边函数的timeout精度为毫秒
int epoll_pwait2(int epfd, struct epoll_event *events,
int maxevents, const struct timespec *timeout,
const sigset_t *sigmask);
#include <unistd.h>
// 关闭并释放epoll
int close(int fd);
从Linux2.6.8开始,
epoll_create
中的size实际上没有作用了
关键数据结构
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 */
};
该结构有两个用途,第一是用于在epoll_ctl
中设置需要监听的fd的事件、事件属性以及附带的额外数据,第二是在epoll_wait
中获取触发对应事件以及额外数据。
events
是需要监听的文件描述符事件以及事件属性,其为一个bitmask,可以是如下内容通过或运算(|
)得到的值(仅包含常用部分,全部内容见参考文献):
events | 说明 |
---|---|
EPOLLIN | 文件描述符中有数据可以读取(事件) |
EPOLLOUT | 文件描述符可以写入数据(事件) |
EPOLLRDHUP (since Linux 2.6.17) | 对端关闭连接(事件) |
EPOLLET | 使用边沿触发(即仅在事件发生(例如有数据可读)时epoll_wait 返回一次,即时数据没有读取完,第二次调用epoll_wait时仍然阻塞)。默认状态下epoll使用水平触发,即如果有文件描述符的数据没有读取完则每次调用epoll_wait均不会阻塞。 |
data
通常用于设置对应的文件描述符(fd)。在epoll_ctl
中设置的epoll_data
可以在epoll_wait
中获取到,因此通常将epoll_data
设置为文件描述符以便于在epoll_wait
返回后操作对应文件。
epoll_ctl中op的合法值
op | 描述 |
---|---|
EPOLL_CTL_ADD | 将一个fd加入到epoll中 |
EPOLL_CTL_MOD | 使用新的epoll_event来修改已经加入到epoll中的fd的设置 |
EPOLL_CTL_DEL | 从epoll中删除一个fd,events可以设置为NULL |
参考
基本使用
#define MAX_FD 3
#define LOG_ERROR(fmt, ...) printf("[ERROR]" fmt "\n", ##__VA_ARGS__)
void demo() {
int ret;
int in_fd;
int epfd = -1;
// 需要监听的socket
int in_socks[MAX_FD] = {0, 1, 2};
// 在添加socket到epoll时使用的的epoll_event结构
struct epoll_event set_event;
// epoll_event数组用于接收准备就绪的fd
struct epoll_event ep_events[MAX_FD];
// 创建epoll(从Linux2.6.8开始,epoll_create的参数已经没有作用,设置为任何大于等于0的数即可)
epfd = epoll_create(MAX_FD);
if (epfd < 0) {
LOG_ERROR("Fail to create epoll, %s", strerror(epfd));
goto out;
}
// 将需要监听的socket加入到epoll中
for (i=0; i<MAX_FD; i++) {
// 设置事件以及属性
set_event.events = EPOLLIN;
// 设置额外数据,通过额外数据可以直接是fd或者能够获取到fd的索引,这里直接设置为fd
set_event.data.fd = in_socks[i];
// 将socket加入对应的epoll
ret = epoll_ctl(epfd, EPOLL_CTL_ADD, in_socks[i], &set_event);
if (ret < 0) {
LOG_ERROR("Fail to add fd to epoll, %s", strerror(errno));
goto out;
}
}
while (1) {
// 等待fd中的数据就绪,timeout设置为-1表示一直阻塞等待
ready_fd_num = epoll_wait(epfd, ep_events, MAX_FD, -1);
if (ready_fd_num < 0) {
LOG_ERROR("Fail to wait for data, err: %s", strerror(errno));
goto out;
}
// 处理就绪的fd
for(int i=0; i<ready_fd_num; i++) {
in_fd = ep_events[i].data.fd;
// 处理数据
}
}
out:
if (epfd != -1) {
close(epfd);
}
return;
}
附录
下边是部分Linux中的API和其头文件
#include <errno.h>
// errno全局变量
#include <string.h>
char *strerror(int errnum);
#include <stdlib.h>
void exit(int status);