兼容性
select() 和 poll() 相对于 信号驱动和epoll() 在不同os之间的可移植性更高, 但是当fd过多的时候, 效率也远低于后两者.
触发方式
- poll 和 select 只支持 水平触发
- 信号驱动只支持边缘触发
- epoll支持水平触发和边缘触发
select
- select返回的是含有整个句柄的数组,select 返回后, 程序并不知道是哪些 fd 准备就绪, 而只知道一共有多少个就绪了, 需要进程自己对传递过去的集合进行遍历和判断
- select的触发方式是水平触发,应用程序如果没有完成对一个已经就绪的文件描述符进行IO操作,那么之后每次select调用还是会将这些文件描述符通知进程
- 内核 / 用户空间内存拷贝问题,select每次都会改变内核中的句柄数据结构集,因而每次select调用时都需要从用户空间向内核空间复制所有的句柄数据结构,产生巨大的开销
- 单个进程能够监视的文件描述符的数量存在最大限制,通常是1024,当然可以更改数量
poll
- 每个fd都有属于自身的 pollfd 结构, 它将 感兴趣事件和触发的事件分成了 events 和 revents. events 的值告诉内核我们关心的是描述符的哪些事件 ; 当某个 fd 有事件触发了之后, 就由内核修改 revents 的数据, 互不干扰, 所以不必像 select 那样, 每次调用都必须重置 fd 集合.
- 数组大小没有限制
- 跟 select() 一样, poll返回后, 程序并不知道是哪些 fd 准备就绪, 而只知道一共有多少个就绪了, 需要进程自己对传递过去的集合进行遍历和判断
信号驱动
- 不需要由用户进程复制fd数组到内核
- 信号处理不好可能会导致进程出问题.
epoll
- epoll 支持水平触发和边缘触发. epoll 的机制类似于信号驱动, 都是进程告诉内核对哪些 fd 感兴趣, 然后对应的fd上有事件的时候, 由内核主动通知进程, 进程再进行相应的处理.
- 可以避免复杂的信号处理流程(比如信号队列溢出时的处理)
- 灵活性高, 可以指定我们希望检查的事件类型(例如检查 socket 的读就绪事件、写就绪事件、或者两者都检查)
总结
select适合少量活跃连接,一般几千。兼容平台多。
epoll适合大量不太活跃的连接。