目录
1. epoll的接口使用
1.1 创建
//创建
int epoll_create(int size);
//size无实际意义,默认填大于0的数
//返回非负文件描述符
1.2 监听队列
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
//epfd: epoll_create的返回值
//op: EPOLL_CTL_ADD | EPOLL_CTL_MOD | EPOLL_CTL_DEL
//event: 结构体
struct epoll_event {
uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
1.3 就绪队列
//等待
int epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout);
2. epoll的底层实现
① 调用epoll_create
用户空间返回一个epfd,内核此时创建一个event结构体,其中有两个重要成员:红黑树和双向链表。红黑树保存需要监听的文件描述符;双向链表保存就绪的文件描述符。
② 调用epoll_ctl
操作的是红黑树
③ 调用epoll_wait
操作的是双向链表
3. epoll VS select
3.1 select的缺点
① select监控的文件描述符有限,最多1024个;
② select需要将监听的文件描述符加入到监听队列,将数据从用户态拷贝到内核态,浪费时间;
③ select监控就绪的文件描述符时需要轮询位图,浪费时间;
④ select需要将就绪的文件描述符从内核态拷贝到用户态,浪费时间。
3.2 epoll的优点
① 监视的文件描述符数量只与内存大小有关,epoll对其不做限制;
② epoll将文件描述符一次性加入到红黑树中,永久有效,即“一次注册,永久生效”。
将文件描述符加入到红黑树的同时,内核会给每一个文件描述符注册一个回调函数,当文件描述符就绪时,会主动调用回调函数将自己添加到双向链表。
NOTICE:当监控的文件描述符数量很少时,select比epoll的平均响应时间更低。
4. epoll对文件描述符操作的两种模式
4.1 概念
LT (Level_trigger): 水平触发 当被监控的文件描述符上有可读写事件发生时,epoll_wait会通知处理程序去读写,如果这次没有把数据一次性全部读写完,那下次调用epoll_wait时,它还会在上次没读写完的文件描述符上继续读写。
ET (Edge_trigger): 边缘触发 当被监控的文件描述符上有可读写事件发生时,epoll_wait会通知处理程序去读写,如果这次没有把数据一次性全部读写完,那下次调用epoll_wait时,它不会通知你,直到文件描述符上出现第二次读写事件才会通知。
4.2 为什么ET模式又称为高效模式?
ET模式很大程度上减少了epoll事件被重复触发的次数(ET模式下执行一次epoll_wait系统调用,LT模式下需要多次执行)
4.3 ET模式下需要注意什么?
ET模式下必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作导致处理多个文件描述符的任务饥饿。
4.4 两种实现方式
① recv设置为非阻塞
ret = recv(newFd, buf, sizeof(buf), MSG_DONTWAIT);
② 将文件描述符设为非阻塞
void setNonBlock(int fd){
int status = 0;
//获取fd的状态
status = fcntl(fd, F_GETFL);
//设为为非阻塞态
status |= O_NONBLOCK;
fcntl(fd, F_SETFL, status);
}