相关接口:
int epoll_create(int size); // 创建epoll句柄
int epoll_ctl(int epfd, int op, int fd, struct epoll_event event); // 事件注册函数
int epoll_wait(int epfd, struct epoll_event events, int maxevents, int timeout);
结构体定义:
struct epoll_event{
__uint32_t events;
epoll_data_t data;
};
typedef union epoll_data{
void *ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
}epoll_data_t;
参数:
size:用来告诉内核要监听的数目。
epfd:epoll函数的返回值。
op:表示动作(EPOLL_CTL_ADD/EPOLL_CTL_FD/EPOLL_CTL_DEL)。
fd:需要监听的fd。
events:指向epoll_event的指针,该结构记录监听的事件。
maxevents:告诉内核events的大小。
timeout:超时时间(ms为单位,0表示立即返回,-1将不确定)。
select、poll和epoll区别
操作方式及效率:
select是遍历,需要遍历fd_set每一个比特位(= MAX_CONN),O(n);poll是遍历,但只遍历到pollfd数组当前已使用的最大下标(≠ MAX_CONN),O(n);epoll是回调,O(1)。
最大连接数:
select为1024/2048(一个进程打开的文件数是有限制的);poll无上限;epoll无上限。
fd拷贝:
select每次都需要把fd集合从用户态拷贝到内核态;poll每次都需要把fd集合从用户态拷贝到内核态;epoll调用epoll_ctl时拷贝进内核并放到事件表中,但用户进程和内核通过mmap映射共享同一块存储,避免了fd从内核赋值到用户空间。
其他:
select每次内核仅仅是通知有消息到了需要处理,具体是哪一个需要遍历所有的描述符才能找到。epoll不仅通知有I/O到来还可通过callback函数具体定位到活跃的socket,实现伪AIO。
异步I/O模型
上面三种I/O方式均属于同步I/O。
从阻塞式I/O到非阻塞I/O,我们已经做到了调用I/O请求后立即返回,但不停轮询的操作效率又很低,如果能够既像非阻塞I/O能够立即返回又能不一直轮询的话会更符合我们的预期。
之所以用户进程会不停轮询就是因为在数据准备完毕后内核不会回调用户进程,只能通过用户进程一次又一次轮询来查询I/O结果。如果内核能够在完成I/O后通过消息告知用户进程来处理已经得到的数据自然是最好的,异步I/O就是这么回事。
异步I/O就是当用户进程发起I/O请求后立即返回,直到内核发送一个信号,告知进程I/O已完成,在整个过程中,都没有进程被阻塞。看上去异步I/O和非阻塞I/O的区别在于:判断数据是否准备完毕的任务从用户进程本身被委托给内核来完成。这里所谓的异步只是操作系统提供的一直机制罢了。