提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
一、IO多路复用是什么?
一句话解释:单线程或单进程同时监测若干个文件描述符是否可以执行IO操作的能力。
IO多路复用解决了什么问题?
应用程序通常需要处理来自多条事件流中的事件,比如我现在用的电脑,往往不是在一时间内只处理一个事件,需要同时处理键盘鼠标的输入、网络的连接等等...
而CPU单核在同一时间只能处理一件事情,这怎么办呢,一种解决办法是对CPU进行时分复用----多个事件流将CPU切割成多个时间片,不同事件流的时间片交替进行。其实在用户层看到的好像很多事件在并行执行一样,事实上并不是,只是时间轮询的比较快而已。
同样, 对于进程/线程,我们也需要同一时间进行多事件处理,那么怎么办呢,聪明的你肯定不知道(毕竟知道了你也就不用过来查了)-------------------IO多路复用。
实现原理:用户将想要监视的文件描述符(File Descriptor)添加到select/poll/epoll函数中,由内核监视,函数阻塞。一旦有文件描述符就绪(读就绪或写就绪),或者超时(设置timeout),函数就会返回,然后该进程可以进行相应的读/写操作。
提示:直奔主题,先把select poll epoll的区别拿出来,然后一一分析!!!!
二、select、poll、epoll的区别
select poll epoll的区别 | |||
select | poll | epoll | |
操作方式 | 遍历 | 遍历 | 回调 |
底层实现 | 数组 | 链表 | 哈希 |
io效率 | 每次回调都进行线性遍历,时间复杂度 为O(n) | 每次回调都进行线性遍历,时间复杂度 为O(n) | 事件通知方式,事件复杂度为O(1) |
最大连接数 | 1024 | 1024 | 无限制 |
fd拷贝 | 每次调用select都需要把fd集合从用户态拷贝到内核态 | 每次调用epoll都需要把fd集合从用户态拷贝到内核态 | 调用epoll_ctl时拷贝进内核并保存,之后每次epoll_wait不返回 |
二、select
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
select的调用会阻塞到有文件描述符可以进行IO操作或被信号打断或者超时才会返回。select将监听的文件描述符分为三组,每一组监听不同的需要进行的IO操作。readfds是需要进行读操作的文件描述符,writefds是需要进行写操作的文件描述符,exceptfds是需要进行异常事件处理的文件描述符。这三个参数可以用NULL来表示对应的事件不需要监听。
当select返回时,每组文件描述符会被select过滤,只留下可以进行对应IO操作的文件描述符。
select可同时监听的文件描述符数量是通过FS_SETSIZE来限制的,在Linux系统中,该值为1024。
三、poll
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
poll函数说明 功能: 多路IO复用,委托内核同时帮我们监控多个文件描述符
头文件: #include
参数: fds:要监视的文件描述符结构体数组的首地址
nfds: 数组中的有效元素个数,注意不是数组总长度
timeout: 阻塞的超时时间 单位 ms (5s ---> 5000 0 --->非阻塞 负数 永久阻塞)
返回值: 0 超时 -1 出错 >0 就绪的文件描述符的个数
和select用三组文件描述符不同的是,poll只有一个pollfd数组,数组中的每个元素都表示一个需要监听IO操作事件的文件描述符。events参数是我们需要关心的事件,revents是所有内核监测到的事件。
四、epoll
int epoll_create(int size);
int epoll_create1(int flags);
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);
int epoll_pwait(int epfd, struct epoll_event *events,
int maxevents, int timeout,
const sigset_t *sigmask);
epoll_create&epoll_create1用于创建一个epoll实例。
epoll_ctl用于往epoll实例中增删改要监测的文件描述符。
epoll_wait则用于阻塞的等待可以执行IO操作的文件描述符直到超时。
在需要同时监听的文件描述符数量增加时,select和poll是O(N)的复杂度,epoll是O(1),在N很小的情况下,差距不会特别大,但如果N很大的前提下,一次O(N)的循环可要比O(1)慢很多,所以高性能的网络服务器都会选择epoll进行IO多路复用。
epoll内部用一个文件描述符挂载需要监听的文件描述符,这个epoll的文件描述符可以在多个线程/进程共享,所以epoll的使用场景要比select&poll要多。