事件模型一般有select、poll、 eventport 、dev/poll、epoll、kqueue这几种。以下对这几种事件模型逐个作原理讲解。
1.select模型
select通过select系统调用监视多个文件描述符集合,select调用返回后,集合中的文件描述符会被内核进行标志位的修改,进程可以获得这些文件描述符从而进行后续的读写操作。select几乎在所有的操作系统平台上都支持。select模型的缺陷在于单个进程能够监视的文件描述符的数量存在限制。
select发生作用的原理是通过轮询监视的文件描述符集合,来获取发生”变化”的文件描述符,从而来进行读写事件的处理,select模型受制于文件描述符的数量。从监听的事件数量的角度来看,当监听的数量增多时,由于使用的是轮询的方式,因此在性能方面是不小的损耗。而这些损耗多数情况下是不必要的,因为对于监听的socket套接字或者文件异步IO,文件描述符通常是小部分会发生变化。不论网络的处理(网络存在延时同时瞬时活动的套接字的数量是比较少的)。
这里引用网上的select处理流程图:
2.poll模型
poll模型的处理机制和select类似,区别在于poll模型和select模型对于文件描述符集合的使用结构不同;poll使用的是pollfd结构,而select使用的是fd_set结构。其他两者的处理流程是类似的。
3.epoll模型
epoll几乎是高性能服务器的必备事件模型,包含网络事件及异步IO。epoll也是在linux平台上特有的事件模型。相比select轮询的方式处理事件,epoll只会处理发生了”改变”的文件描述符,这样能很好地避免不必要的事件处理,提升了处理的性能及效率。epoll在kernel中的实现是用红黑树及链表(就绪队列)来表达处理的,添加一个事件,会在事件的结构中添加回调函数;这样在事件被内核检测到触发时,这个事件会被添加到链表中,当是用epoll_wait调用时,传递到用户态的就是已经需要被处理的事件了。
同时,与select相比,epoll支持的文件描述符的数量是可以打开的文件数目,而不是select的进程可打开的文件描述符的数量,可打开的文件数量远远大于一个进程可以打开的文件描述符的数量(虽然进程可打开的文件描述符数量可以修改)。
3./dev/poll模型
支持Solaris 7 11/99+、HP/UX 11.22+、IRIX 6.5.15+以及Tru64 UNIX 5.1A+。/dev/poll也是针对select/poll进行了改进,描述文件描述符集合的结构是pollfd,/dev/poll能在调用之间维持状态。由此,可以预先设置待查询的描述符列表,等待事件完整之后,无需像select/poll那样还要再设置文件描述符。
4.eventport模型
eventport仅仅支持solaris 10及以上版本。
5.kqueue模型
支持 FreeBSD 4.1+、OpenBSD 2.9+、 NetBSD 2.0、以及macOS这几种平台。
kqueue的处理原理与epoll是比较类似的,但是在实现上存在差别。kqueue的实现依赖于名为knote的结构,knote包含两个功能:1.包含一个有事件发生需要通知用户态的knotes队列(与epoll中的就绪队列类似);2.保存需要监听的事件及文件描述符。kqueue对于事件及文件描述符的保存不是通过红黑树实现的,而是使用三个结构共同作用来进行描述。三个结构分别为:
1.队列,用于保存处于”活跃“状态的knotes节点
2.hash表,用于查找没有对应文件描述符的knotes节点
3.数组,和进程打开的文件描述符表是一致的