IO多路复用
# socket场景
对于客户端和服务器在网络中的通信,必须使用Socket编程,从而实现跨主机间通信。
为了服务更多用户,服务器可以为每个请求分配一个进程或者线程来实现处理请求。但是这两种方式都不适合。因为高并发情况下,将有成千上万的TCP连接到来,每个连接要维护一个进程或者线程,操作系统肯定是扛不住的。
为了解决这个问题,需要使用到I/O多路复用技术。
一个进程任一时刻只能处理一个请求,但是处理每个请求的事件时,耗时控制在1毫秒内,这样1秒就可以处理上千个请求。这看起来,相当于多个请求复用了一个进程,这就是多路复用。这种思想有点像一个CPU并发多个进程,所以也叫时分多路复用。
select/poll/epoll是内核提供给用户态的多路复用系统的系统调用,进程可以通过一个系统调用函数从内核获取多个事件。
在获取事件时,先把所有连接(在操作系统中体现为一个文件操作符)传给内核,再由内核返回产生了事件的连接,然后在用户态处理这些连接的请求。
# select
select实现多路复用的方式是,用户态下将已经连接的Socket都放在一个文件描述集合中,然后调用select函数将文件描述符集合拷贝到内核中,让内核来检查是否有网络事件产生。
操作系统内核检查的方式很粗暴,它通过遍历文件操作符集合的方式,当检查到有事件后,将此Socket标记为可读或可写,接着再将整个文件描述符集合拷贝回用户态。
用户态再通过遍历的方式找到可读或可写的Socket,然后再对其进行处理。
select的方式,需要两次遍历文件操作符集合,一次是在内核里,一次是在用户态;而且,还会发生两次拷贝文件描述符集合,先从用户态传入内核空间,由内核空间修改后,再传回用户空间。
更严重的是,select使用固定长度的bitmap来表示文件描述符集合,它所支持的文件描述符个数是有限制的。
# poll
poll不再使用BitsMap来存储所关注的文件描述符,取而代之的是动态数组,以链表来组织。这种方式突破了select的文件描述符个数限制。
但是,poll和select并没有太大的本质区别,两者都是使用线性结构来存储进程关注的Socket集合,因此都需要遍历文件描述符集合来找到或者修改可读可写的Socket,而且也需要在用户态与内核态之间拷贝文件描述符集合。
# epoll
epoll在内核里使用红黑树来跟踪进程所有待检测的文件描述字,把需要监控的socket加入内核中的红黑树里。通过红黑树的操作,不要像select和poll一样,每次操作都传入整个socket集合,只需要传入一个待检测的socket。
epoll使用事件驱动机制,内核里维护了一个链表来记录就绪事件,当某个socket有事件发生时,通过回调函数内核会将其加入到这个就绪事件列表中。当用户调用epoll_wait()函数时,只会返回有事件发生的文件描述符个数,不需要像select/poll一样轮询整个socket集合,大大提高了检测的效率。