目录
一、epoll函数及相关系统调用
博主之前的博文中详细介绍了poll和select的功能及使用方法,而本文所介绍的epoll根据Linux中man手册的说法是为处理大批量句柄而作了改进的 poll。
1.1epoll的相关系统调用
epoll_create:
int epoll_create(int size);
创建一个 epoll 的句柄。
linux2.6.8 之后,size 参数是被忽略的。
用完之后, 必须调用 close()关闭。
epoll_ctl:
注册epoll要监视的事件
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
不同于select()是在监听事件时直接告诉内核要监听什么类型的事件, 而是在这里先注册要监听的事件类型。
第一个参数是epoll_create()的返回值(epoll的句柄)。
第二个参数表示动作,也就是拿着fd对epfd做修改,用三个宏来表示。
第三个参数是需要监听的 fd 对象。
第四个参数是告诉系统内核需要具体监听什么事。
参数二的取值:
EPOLL_CTL_ADD:注册新的 fd 到 epfd 中;
EPOLL_CTL_MOD:修改已经注册的 fd 的监听事件;
EPOLL_CTL_DEL:从 epfd 中删除一个 fd;
参数四struct 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_data_t data;
}__EPOLL_PACKED;
events可以作为以下宏的集合:
EPOLLIN : 表示对应的文件描述符可读 (包括对端 SOCKET 正常关闭的情况);
EPOLLOUT : 表示对应的文件描述符可写;
EPOLLPRI : 表示对应的文件描述符有紧急的数据可读 (这里应该表示有带外数据的到来);
EPOLLERR : 表示对应的文件描述符发生了错误;
EPOLLHUP : 表示对应的文件描述符被挂断;
EPOLLET : 将 EPOLL 设为边缘触发模式, 这是相对于水平触发来说的。
EPOLLONESHOT:只监听一次事件, 当监听完这次事件之后, 如果还需要继续监听这个 socket 的话, 需要再次把这个 socket 加入到 EPOLL 队列里。
epoll_wait
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
收集在 epoll 监控的事件中已经发送的事件。
1、参数 events 是已经分配好的 epoll_event 结构体数组。
2、epoll 将会把发生的事件赋值到 events 数组当中(events 不可以是nullptr空指针,内核只负责把数据拷贝到 events 数组中,不会帮助我们在用户态中分配开辟内存)
3、maxevents 告诉内核这个 events 数组有多大,maxevents 的值不能大于创epoll_create()时的 size。
4、参数 timeout 是超时时间 (毫秒,0 会立即返回,-1则是 永久阻塞)
函数调用成功时,返回I/O上已经准备就绪的文件描述符的数目,返回0表示已经超时没有就绪,返回小于0表示函数失败了。
二、epoll的优点
1、接口使用更加方便: 虽然将整个epoll拆分成了三个函数, 但是反而使用起来更加高效. 不需要每次循环的去设置关注的文件描述符, 也做到了输入输出参数可以分离开
2、数据拷贝轻量: 只在合适的时候调用 EPOLL_CTL_ADD 将文件描述符结构拷贝到内核中, 这个操作并不频繁(而 select/poll 都是每次循环都要进行拷贝)
3、事件回调机制: 避免使用遍历的方法, 而是使用回调函数的方式, 将已经就绪的文件描述符 结构添加到就绪队列当中, epoll_wait 返回直接访问就绪队列就可以知道哪些文件描述符已经就绪。这个操作时间复杂度 O(1)。即使文件描述符数目很多, 效率也不会受到影响
4、没有数量限制: 文件描述符数目无上限