I/O多路复用就是使用一个线程去维护多个连接,实现同时管理多个Socket。它的实现的话,有三种方式,有select、poll和epoll, select检查的方式很粗暴,就是通过遍历文件描述符集合的方式,当检查到有事件产生后,将此Socket标记为可读或可写,接着再把整个文件描述符集合拷贝回用户态里,然后用户态还需要再通过遍历的方法找到可读或可写的Socket,然后再对其处理。 poll和select的区别是,select是使用了固定长度的BitsMap来表示文件描述符集合,所支持文件描述符的个数是有限制的。而poll的话是用了链表来表示。但是本质上来说并没有太大的区别。都是采用线性结构存储Socket集合,因此都需要遍历文件描述符来找到可读或可写的Socket,时间复杂度为O(n),而且也需要在用户态和内核态之间拷贝文件描述符集合。
epoll在内核里是使用红黑树来跟踪进程所有待检测的文件描述符,增删查一般时间复杂度是O(logn),然后就是epoll是使用事件驱动的机制,内核里维护了一个链表来记录就绪事件,就是当某个Socket有事件发生时,通过回调函数会被加入到这个就绪事件列表中,当用户调用epoll_wait()函数时,只会返回有事件发生的文件描述符,不需要像select/poll那样拷贝并轮询整个Socket集合,这样大大提高了检测的效率。

epoll支持两种事件触发模式,分别是边缘触发(ET)和水平触发(LT),使用边缘触发时,当被监控的Socket描述符上有可读事件发生时,服务器端只会从epoll_wait中苏醒一次,因此我们程序要保证一次性将内核缓冲区的数据读取完。 使用水平触发模式时,当被监控的Socket上有可读事件发生时,服务端不断地从epoll_wait中苏醒,直到内核缓冲区数据被read函数读完才结束,目的是告诉我们有数据需要读取。 select和poll只有水平触发,epoll默认是水平触发。
