I/O 多路复用:select/poll/epoll
概述
I/O多路复用是一种使得程序能够同时监听多个文件描述符的技术,从而提高程序的性能。select/poll/epoll 是Linux下实现I/O复用的系统调用。
文件描述符(file descriptor)
是内核为了高效管理已被打开的文件所创建的索引,用于指向被打开的文件,所有的I/O操作的系统调用都会通过文件描述符。
在Linux操作系统中,每一个进程都对应着一个文件描述符表,对应一个数组指针,默认前三位分别为标准的输入流(键盘),标准的输出流(显示器),标准的错误流(显示器)。如果随后进程创建了一个Socket,那么这个文件描述符表的低四位就是指向这个socket的指针。
在Linux操作系统中,一切皆文件。
Socket连接
双方进行网络通信之前,各自都会创建一个Socket。Socket 实际上是一个文件,也就会对应一个文件描述符。在 Linux 下,单个进程打开的文件描述符数是有限制的,没有经过修改的值一般都是 1024,不过我们可以通过 ulimit 增大文件描述符的数目;
Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口(API)。我们所说的TCP/IP协议栈是在操作系统内核实现的,而Socket就是操作系统内核提供给应用层的一系列接口,Socket封装了TCP/IP,要使用TCP/IP来发送数据,就调用Socket的OutputStream,要使用TCP/IP接收数据,就调用Socket的InputStream。
IO复用
IO复用的历史和多进程一样长,Linux很早就提供了select
系统调用,可以在一个进程内维护1024个连接,后来加入poll
系统调用,poll
做了一系列改进后解决了1024个连接的限制问题,可以维持任意数量的连接。
一个进程任意时刻只能处理一个请求,但是如果耗时很短,这样短时间内就可以处理大量的请求,和并发类似,多个请求复用一个进程,这就是多路复用。
select
select将已经连接的Socket都放在一个文件描述符集合中,然后调用select函数将其拷贝到内核中,内核再检验是否有网络IO事件的发生,当检查到有事件发生后,将Socket标记为可读或可写,再把文件描述符集合拷贝回用户态,用户态再通过遍历的方法找到可读或可写的Socket。
select需要进行两次遍历和拷贝,一个进程最多维护1024个文件描述符。
poll
poll和select并没有本质上的区别,区别在于poll不再使用BitMap来存储文件描述符,转而使用动态数组,以链表的形式来存储文件描述符,突破了select1024的数量限制。
epoll
1.epoll在内核中使用了红黑树的数据结构来跟踪所有待检测的文件描述符,时间复杂度降为o(logn)。每次只需要传入一个待检测的socket,该socket会与相应的设备建立回调关系,相应的事件发生后,就会调用这个函数。
2.epoll使用事件驱动的机制,内核中维护了一个链表来记录就绪事件,当socket有事件发生时,通过回调函数内核将其加入就绪事件列表中,系统调用epoll_wait()函数时,会返回事件发生的文件描述符,不会再像poll和select一样遍历扫描。当数十万并发连接存在时,可能每一毫秒只有几十个活跃的连接,这时select的效率就会显著低于epoll。
边沿触发和水平触发
边沿触发:当被监控的socket描述符上有可读事件发生时,服务器只会从epoll_wait中苏醒一次。
水平触发:服务器会不断地从epoll_wait中苏醒,直到内核缓冲区的数据被read函数读完才结束