当前位置: 首页 > 后端技术 > Java

高级IO模型的kqueue和epoll

时间:2023-04-01 23:15:38 Java

介绍任何程序都离不开IO,有的是明显的IO,比如读写文件,有的是不明显的IO,比如网络数据传输。那么这些IO的模式有哪些呢?我们在使用中应该如何选择呢?高级IO模型kqueue和epoll是如何工作的?一起来看看吧。BlockIO和nonblockingIO我们先来了解一下IO模型中最简单的两种模型:blockingIO和nonblockingIO。比如我们有多个线程从一个Socket服务器读取数据,那么读取的过程其实可以分为两部分。第一部分是等待socket数据准备好,第二部分是读取相应的数据进行业务处理。.对于阻塞IO,它的工作流程是这样的:一个线程等待socket通道数据准备好。当数据准备好后,线程进行程序处理。其他线程等待第一个线程结束后,继续上面的过程。为什么叫阻塞IO?这是因为当一个线程在执行的时候,其他线程只能等待,也就是说IO是阻塞的。什么是非阻塞IO?还是上面的例子,如果它在非阻塞IO中的工作流程是这样的:一个线程尝试读取socket的数据。如果套接字中的数据没有准备好,立即返回。线程继续尝试从套接字读取数据。如果socket中的数据准备好了,那么这个线程就继续执行后续的程序处理步骤。为什么叫非阻塞IO呢?这是因为线程发现socket没有数据会立即返回。不会阻塞这个socket的IO操作。从上面的分析我们可以看出,非阻塞IO虽然不会阻塞Socket,但是不会释放Socket,因为它会一直轮询Socket。IO多路复用和selectIO多路复用的模型很多,select是最常见的一种。实时不管是netty还是JAVA的NIO,都是用select模型。选择模型如何工作?其实select模型有点类似于非阻塞IO,不同的是select模型中有一个单独的线程来检查socket中的数据是否就绪。如果发现数据准备好了,select可以选择通过之前注册的事件处理器通知特定的数据处理线程。这样做的好处是虽然select线程本身是阻塞的,但是其他用来实际处理数据的线程是非阻塞的。而一个select线程其实可以用来监听多个socket连接,从而提高IO的处理效率,所以select模型应用在很多场合。为了更详细的理解select的原理,我们先看一下unix下的select方法:intselect(intnfds,fd_set*readfds,fd_set*writefds,fd_set*errorfds,structtimeval*timeout);先解释一下这几个参数的含义,我们知道在Unix系统中,所有的对象都是文件,所以这里的fd代表的是文件描述符,也就是文件描述符。fds表示filedescriptorsets,也就是文件描述符的集合。nfds是一个整数值,代表文件描述符集合中的最大值+1。readfds是待检查文件读取的描述符集合。writefds是要检查的文件写入描述符集。errorfds是一组要检查的文件异常描述符。timeout为超时时间,表示等待选择完成的最大间隔时间。它的工作原理是轮询所有的文件描述符,然后找到那些需要监控的文件描述符。pollpoll和select类很相似,只是描述fd集合的方式不同。poll主要用于POSIX系统。在epoll实时中,select和poll虽然都是多路复用IO,但是都存在一些不足。而epoll和kqueue就是他们的优化。epoll是linux系统中的一个系统命令,可以看成是事件轮询。它首次在linux内核2.5.44版本中引入。主要用于监控多个文件描述符之间的IO是否就绪。对于传统的select和poll,因为需要不断遍历所有的文件描述符,所以每次select的执行效率都是O(n),但是对于epoll,这个时间可以增加到O(1)。这是因为epoll会在特定的监控事件发生时触发通知,所以不需要像select那样使用轮询,它的效率会更高。epoll使用红黑树(RB-tree)数据结构来跟踪当前正在监视的所有文件描述符。epoll有3个api函数:intepoll_create1(intflags);用于创建一个epoll对象并返回它的文件描述符。传入的标志可用于控制epoll的性能。intepoll_ctl(intepfd,intop,intfd,structepoll_event*event);这个方法是用来控制epoll的,可以用来监听具体的文件描述符和哪些事件。这里的op可以是ADD、MODIFY或DELETE。intepoll_wait(intepfd,structepoll_event*events,intmaxevents,inttimeout);epoll_wait用于监听使用epoll_ctl方法注册的事件。epoll提供了两种触发方式,即边沿触发和电平触发。如果注册到epoll的管道收到数据,调用epoll_wait会返回,说明有数据要读。但在级别触发模式下,只要管道的缓冲区包含要读取的数据,对epoll_wait的调用就会返回。但是在level-triggered模式下,epoll_wait只有在新数据写入管道后才会返回。和epoll一样,kqueuekqueue是用来替代select和poll的。不同的是kqueue用于FreeBSD、NetBSD、OpenBSD、DragonFlyBSD和macOS。kqueue不仅可以处理文件描述符事件,还可以用于其他各种通知,如文件修改监听、信号、异步I/O事件(AIO)、子进程状态变化监听、支持纳秒级的定时器resolution,另外,kqueue除了内核提供的事件外,还提供了一种使用用户自定义事件的方法。kqueue提供了两个API,第一个是构建kqueue:intkqueue(void);第二个是创建kevent:intkevent(intkq,conststructkevent*changelist,intnchanges,structkevent*eventlist,intnevents,conststructtimespec*timeout);kevent中第一个参数是要注册的kqueue,changelist是要监听的事件列表,nchanges是监听事件的长度,eventlist是kevent返回的事件列表,nevents是事件列表要返回的长度,最后一个参数是超时。另外,kqueue还有一个EV_SET宏用来初始化kevent结构:EV_SET(&kev,ident,filter,flags,fflags,data,udata);epoll和kqueue的优点epoll和kqueue之所以比select和poll更高级,是因为充分利用了操作系统的底层功能。对于操作系统来说,数据什么时候准备好是确定的。通过向操作系统注册相应的事件,可以避免select的轮询操作,提高运行效率。需要注意的是epoll和kqueue需要底层操作系统的支持,使用时一定要注意相应的nativelibraries支持。本文已收录于http://www.flydean.com/14-kqueue-epoll/最流行的解读,最深刻的干货,最简洁的教程,很多你不知道的小技巧等着你去探索!欢迎关注我的公众号:《程序那些事儿》,懂技术,更懂你!