I/O多路复用就是通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知应用程序进行相应的读写操作。
1.select
基本原理:select 函数监视的文件描述符分3类,分别是writefds、readfds、和exceptfds。调用后select函数会阻塞,直到有描述符就绪(有数据 可读、可写、或者有except),或者超时(timeout指定等待时间,如果立即返回设为null即可),函数返回。当select函数返回后,可以通过遍历fdset,来找到就绪的描述符。
被监控的fd在select()后会发生改变,所以在下一次进入select()之前要重新初始化fd。
优点:良好的跨平台特性。
缺点:单个进程在Linux下默认最大连接数是1024(可以改);对socket扫描时采用轮询,效率低;需要维护fd_set(long型数组),这样会使得用户空间和内核空间在传递该结构时复制开销大,内核遍历开销也大。
2.poll
本质上和select没有什么区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态,如果设备就绪则在设备等待队列中加入一项并继续遍历,如果遍历完所有fd后没有发现就绪设备,则挂起当前进程,直到设备就绪或者主动超时,被唤醒后它又要再次遍历fd。这个过程经历了多次无谓的遍历。不同之处在于,它基于链表实现,没有最大连接数限制,但仍受限于硬限制,相对于epoll是 “水平触发”, 只需要初始化一次,关心的事件和发生的事件进行了分离。
3.epoll:双向链表+红黑树
epoll支持水平触发和边缘触发,最大的特点在于边缘触发,它只告诉进程哪些fd刚刚变为就绪态,并且只会通知一次。还有一个特点是,epoll使用“事件”的就绪通知方式,通过epoll_ctl注册fd,一旦该fd就绪,内核就会采用类似callback的回调机制来激活该fd,epoll_wait便可以收到通知,内核空间和用户空间共享一块内存实现。
优点:
(1)没有最大并发连接的限制,能打开的fd的上限远大于1024(1G的内存上能监听约10万个端口)。
(2)效率提升,不是轮询的方式,不会随着fd数目的增加效率下降。只有活跃可用的fd才会调用callback函数;即epoll最大的优点就在于它只管你“活跃”的连接,而跟连接总数无关,因此在实际的网络环境中,epoll的效率就会远远高于select和poll。
(3)内存拷贝,利用mmap()文件映射内存加速与内核空间的消息传递;即epoll使用mmap减少复制开销。
作者:王策