目录
什么是I/O多路复用?
使用I/O多路复用的原因和目的是什么?
select,poll,epoll底层操作原理
select:
poll:
与select的不同点:
与select的相同点:
epoll:
select/poll/epoll对比:
select,poll,epoll各自的使用场景
“复用”在电子及通信工程领域很常见,含义是“为了提高物理设备的效率,用最少的物理要素传递最多数据时使用的技术。”
那放到Web服务器上如何理解呢?即单个线程通过记录跟踪每一个Sock(I/O流)的状态来同时管理多个I/O流。
I/O多路复用简单来理解就是通过一种机制可以使得监视多个文件描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。
I/O多路复用可以使一个进程向多个客户端提供服务。
目的:是提高服务器的吞吐能力。
原因:与多进程和多线程技术相比,I/O多路复用技术的最大优势是系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程,从而大大减小了系统的开销。
select API:
#include
#include
int select(int maxfdp1,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval *timeout)
返回值:就绪描述符的数目,超时返回0,出错返回-1
select的操作步骤:
1)先将要检测的文件描述符集合fd_set从用户态拷贝到内核态
2)内核在内核空间对fd_set遍历,检查是否有就绪的文件描述符。
3)如果没有一个 socket 有事件发生,那么 select 的线程就需要让出 cpu 阻塞等待,这个等待可以是不设置超时时间的死等,也可以是设置 timeout 的有超时时间的等待。如果有事件发生,则将检测好后的fd_set从内核态拷贝到用户态,并返回就绪的文件描述符数量。
之后程序可以遍历文件描述符的集合,找到就绪的文件描述符进行相应处理。
select缺点:
1)内存拷贝开销大。每次调用select都需要将文件描述符的集合从用户态拷贝到内核态,检测好后,再从内核态拷贝到用户态,这个开销在fd很多的时候会很大。
2)内核轮询复杂度为O(n)。同时每次调用select都需要在内核遍历传进来的所有fd,这个开销在fd很多的时候也会很大。
3)要监测的文件描述符个数有最大限制。select支持的文件描述符个数太少了,默认是1024
4)fd集合不能复用,每次都需要重置。
使用结构体pollfd来表示要检测的文件描述符及其要检测的事件,事件发生的信息,所以pollfd可以复用。
poll是基于链表存储的,所以文件描述符的数量没有限制。
pollfd的结构是:
struct pollfd {
int fd; /* file descriptor */
short events; /* events to look for */
short revents; /* events returned */
};
poll函数及其头文件说明:
#include
int poll(struct pollfd* fds,nfds_t nfds,int timeout);
1.需要将文件描述符的数据结构从用户空间拷贝到内核空间。
2.内核线程需要遍历传入进来的文件描述符,并且当内核返回就绪的文件描述符数量后,还需要遍历一次找出就绪的文件描述符。
3.性能开销会随文件描述符的数量而线性增大
高效的三个关键因素:
1.红黑树(事件注册或删除时,速度很快,O(logN),且每个fd在整个过程中只会拷贝一次)
2.就绪链表(epoll_ctl将事件添加到红黑树中,并注册相应的回调函数。当就绪事件发生时,会调用回调函数,将文件描述符,发生的事件等信息加入到就绪链表中)
3.回调函数(通知机制,而不是select和poll的轮询)
//epoll操作过程需要三个接口,分别如下:
#include
/*
创建一个新的epoll实例,即在内核中创建了一个结构体,这个结构体有有两个比较重要的数据。
一个是需要检测的文件描述符的信息(红黑树),还有一个是就绪链表,存放检测到事件发生的文件描述符信息。
*/
// 另外注意:epoll_create中的size参数自从linux2.6.8以后就没有意义了,只要比0大就可以。
int epoll_create(int size);
// 向epoll实例中添加/删除/更改要监听的文件描述符及其事件
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
// 判断就绪链表是否为空
// 若为空,则按timeout参数阻塞等待。
// 若不为空,将发生事件的相关信息拷贝到events里,并返回就绪文件描述符的数量。
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
select | poll | epoll | |
---|---|---|---|
性能 | 随着连接数的增加,性能急剧下降,处理成千上万的并发连接数时,性能很差 | 随着连接数的增加,性能急剧下降,处理成千上万的并发连接数时,性能很差 | 随着连接数的增加,性能基本没有变化 |
连接数 | 一般1024 | 无限制 | 无限制 |
内存拷贝 | 每次调用select拷贝 | 每次调用poll拷贝 | fd首次调用epoll_ctl拷贝,每次调用epoll_wait不拷贝 |
数据结构 | bitmap | 链表 | 红黑树 |
内在处理机制 | 线性轮询 | 线性轮询 | FD挂在红黑树,通过事件回调callback |
时间复杂度 | O(n) | O(n) | O(1) |
当fd数量较少且fd IO 都非常繁忙的情况 select 在性能上比较占优势。
当fd数量较大,epoll在性能上占优势。