我们在IO模型和Java网络编程模型中,对IO有了一定的理解。这一篇,主要讲解基于事件驱动的两种是在原来基础上的扩展。在基于事件驱动的网络编程模型中,Reactor和Proactor模型是两种常用的IO设计模型。
我们知道BIO(阻塞IO)只有等待阻塞方法结束了,操作权才会交还给调用线程,在阻塞期间,调用者做不了任何操作,只能等待。在此期间调用者线程无法转向其他能做的事,对调用者来讲,这其实是一种浪费。在NIO(非阻塞IO)中,非阻塞IO会立刻将结果返回到调用者,调用者获取结果无需等待。获得的结果无非两种:数据准备好了,你继续表演吧;要么数据还没准备好,你要不再试试,或者你等会再试试。而对AIO(异步IO),调用方立刻获得返回,并且操作系统会使用另外的资源来达成此次的IO请求,并在操作系统完成数据准备后,通知调用者。
Reactor模式和Proactor模型
Reactor模型的中心思想采用的就是我们在IO模型和Java网络编程模型中所讲的多路复用IO。Reactor模型主要包含以下几个角色:
1)Handle,也叫句柄,有些地方也叫描述符。如网络Socket IO中称网络IO句柄或者Socket描述符,在文件读写中称文件IO句柄或者文件描述符等。
2)Synchronous Event Demultiplexer,也称同步事件多路分解器,用于阻塞等待发生在句柄集合上的一个或者多个事件,因为事件的到来是随机不可预测的,底层要实现事件的监听采取的是循环等待的策略,这种策略也叫事件循环。事件循环依赖系统调用,这里的系统调用,指的就是select/poll/epol等底层函数。这些底层函数一旦监听到句柄(或描述符)的读就绪或者写就绪,就会通知调用方进行读写操作。同步事件分离器依赖底层系统调用的实现。
3)Event Handler, 可称为事件处理器,不同的事件,可以有不同的处理器。通常IO框架库会将事件处理器定义成一个模板函数,不同的事件处理器可以有不同的特性,拥有自己业务相关的实现。
4)Reactor,也称为反应器,反应器主要功能包括以下:注册或者删除应用程序所关注的事件句柄,运行事件循环监控事件的到来,当事件到达时,反应器分离事件,并通知到对应的具体的事件处理器上,由具体的事件处理器处理器调用相关的函数来实现数据的读或者写,处理完数据后,交还系统的控制权。
Reactor模型图
整体的业务流可以概括为,首先调用者先向反应器注册,其对某种具体 的IO动作感兴趣,反应器会循环监控调用 者注册的事件是否已发生(如可读可写等),当事件发生后,事件分解器就被唤醒,会通知到事件对应的处理器来完成 事件的读写,可以看出,在具体事件到达时,处理程序不是调用的反应器,而是通过反应器分离的事件处理器来作数据的读或者写,这种方式又被称为“好莱坞法则”,类似于好莱坞大导演找演员的模型,你别来找我,等活来了我来找你吧。Reactor模型当有对应的IO完成时,回调对应的函数来处理,这种模型本质上还是一种同步的IO模型,其底层也并没有调用对应的异步IO的函数。
Reactor模型中的事件处理器是基于模板的模型来实现的,这意味着不同的事件处理器是分离的,业务间具体低侵入性。Reactor模型的底层系统调用也是一个模板模型,可以用select实现,亦或epoll,但是必须满足能监控活跃连接的功能。
Proactor与Reactor则不同,Reactor是基于同步IO模型实现的,而Proactor是基于异步IO。
Proactor中,调用者会调用异步操作处理器提供的异步函数,调用之后, 调用者线程和异步操作处理器会独立运行,这依赖于操作系统对异步IO的支持,实际的IO是由操作系统来完成 的。然后Proactor会进行事件循环,等待事件的到来 ,当事件分解器等待事件到来的同时,操作系统已经在同时将目标数据拷备到用户空间,当拷备完成 后,会通知到事件分解器,事情已经搞完了,事件分解器将完成事件转发到相应的事件处理者或者 回调函数,调用 者就可以利用这些数据处理自己的业务逻辑 了。
Reactor和Proactor都是对某个具体IO事件的告知,这也就我们所说的基于事件驱动,Reactor是告知的事件已准备好,而Proactor是事件已做好。他们的业务 逻辑也很类似,都是事件分解器来负责IO的监控,并回调对应的事件处理器,不同之处在于Reactor事件告知的是已准备好,还需要进一步的从内核 空间数据拷备到用户空间。而Proactor是数据已经准备好,调用 者可以直接在用户空间使用了。