1、概念
reactor设计模式,是一种基于事件驱动的设计模式。Reactor框架是ACE各个框架中最基础的一个框架,其他框架都或多或少地用到了Reactor框架。
在事件驱动的应用中,将一个或多个客户的服务请求分离(demultiplex)和调度(dispatch)给应用程序。在事件驱动的应用中,同步地、有序地处理同时接收的多个服务请求。
reactor模式与外观模式有点像。不过,观察者模式与单个事件源关联,而反应器模式则与多个事件源关联 。当一个主体发生改变时,所有依属体都得到通知。
现在让我们用较专门的语言再来阐述Reactor模式。首先联想一下普通函数调用的机制:程序调用某函数,函数执行,程序等待,函数将结果和控制权返回给程序,程序继续处理[1]。这是一种顺序的处理方式,但Reactor并不会主动调用函数,而是首先将相应的处理函数注册到Reactor上,当我们感兴趣的事件发生时再通知Reactor调用之前注册的函数完成处理,这些函数就是所谓的“回调函数”。事件的种类可以多种多样,包括信号、I/O读写、定时、GUI消息等。Reactor模式有许多大名鼎鼎的实现,比如C中的libevent,Java的Netty以及现在很火的Node.js语言等等。
回到网络编程,我们处理的大部分问题其实就是各种各样的事件。服务器接收到客户端的连接、读数据、写数据、接收到错误,这些都是事件。一般的处理是一种主动调用函数的方式,比如最简单的TCP服务器模型:
socket(...); bind(...); listen(...); for ( ; ; ) { accept(...); if ((childpid = fork()) == 0) { .... } }
服务器端在完成基本的套接字建立、绑定、监听后,在for循环中主动的调用accept等待客户端连接到来,如果没有新连接,会阻塞在accept上,如果有新连接,则fork出一个子进程用以完成连接的处理。我们知道,随着并发连接的增多,我们需要fork出很多的子进程来处理。(当然也有其他的客户/服务器编程范式[2],比如一个客户一个线程)。但这样的方式会带来各种问题[3],比如效率、编程复杂度、移植性等,具体可参考[5]。而Reactor模式在高并发的场景下就有了优势。Reactor模式一般与非阻塞I/O结合,在处理一个事件时不用等待事件的完成,而是转而去处理其他的任务,当这个事件处理完毕后再去通知Reactor,从而用单个线程可以完成多线程才能完成的任务,提高了系统的吞吐量。[4]中用一个餐馆的例子解释了Reactor模式在高并发下的优势,很有意思
2、优点
1)响应快,不必为单个同步时间所阻塞,虽然Reactor本身依然是同步的;
2)编程相对简单,可以最大程度的避免复杂的多线程及同步问题,并且避免了多线程/进程的切换开销;
3)可扩展性,可以方便的通过增加Reactor实例个数来充分利用CPU资源;
4)可复用性,reactor框架本身与具体事件处理逻辑无关,具有很高的复用性;3、缺点
1)相比传统的简单模型,Reactor增加了一定的复杂性,因而有一定的门槛,并且不易于调试。
2)Reactor模式需要底层的Synchronous Event Demultiplexer支持,比如Java中的Selector支持,操作系统的select系统调用支持,如果要自己实现Synchronous Event Demultiplexer可能不会有那么高效。
3) Reactor模式在IO读写数据时还是在同一个线程中实现的,即使使用多个Reactor机制的情况下,那些共享一个Reactor的Channel如果出现一个长时间的数据读写,会影响这个Reactor中其他Channel的相应时间,比如在大文件传输时,IO操作就会影响其他Client的相应时间,因而对这种操作,使用传统的Thread-Per-Connection或许是一个更好的选择,或则此时使用Proactor模式。Reactor模式包括四个部分的组件[1]:
Initialization Dispatcher
Synchronous Event Demultiplexer
Handles
Event Handler
其中的Handles用于标识通过操作系统管理的各种资源,包括套接字、打开文件、锁、定时器等,在Unix系统中是文件描述符,在Windows系统中是句柄。Initialization Dispatcher实现了事件的注册、删除、分发的接口,是用于驱动的主模块。Synchronous Event Demultiplexer是它的一个组件,用于等待新事件的发生,其实这就是解复用的系统,包括我们熟知的select、poll、epoll等。当有事件在Handles上发生时,我们可以通过Synchronous Event Demultiplexer得知。此时Synchronous Event Demultiplexer通知Initialization Dispatcher,Dispatcher再根据事件的类型使用相应的Event Handler完成事件的处理。所以Event Handler是最终用于实际处理事件的组件,一般来说会根据事件类型的不同实现各种类型的钩子函数。下图就是Reactor模式的结构:
![]()
具体的处理过程如下:一个应用将某个Event Handler注册到Initialization Dispatcher上,并且指定其感兴趣的事件,希望当有感兴趣的事件在关联的handle上发生时去通知这个Event Handler。
当Event Handler注册完毕后,Initialization Dispatcher在一个事件循环中通过Synchronous Event Demultiplexer等待事件的发生。比如一个监听套接字等待accept一个新的客户端连接。
当与Handle关联的资源就绪时(比如当一个TCP套接字可读),Synchronous Event Demultiplexer会通知Initialization Dispatcher有事件发生。
此时Initialization Dispatcher找到和这个Handle关联的Event Handler,根据事件的类型调用Event Handler相应的钩子函数完成事件处理![]()
代码实现参考:https://github.com/miaobeihai/Reactor