申明:本文借助翻译软件翻译了Douglas C. Schmidt的《Reactor An Object Behavioral Pattern for Demultiplexing and Dispatching Handles for Synchronous Events》一文中的部分内容,可能跟原文存在出入,请谨慎学习!
--------------------------------------------------------------------------------------------------------------------------
Reactor(用于同步事件的多路分解和调度处理的对象行为模式)
1、目的
Reactor设计模式处理的服务请求由一个或多个同时提交给应用程序客户端。 应用程序中的每个服务都可以包含几个方法,由一个单独的事件处理程序表示负责调度特定于服务的请求。事件处理程序的调度由启动调度程序执行,该调度程序管理已注册的事件处理程序。服务请求的解复用由异步事件解复用器执行。
2、别名
调度程序,通知程序
3、例子
为了说明Reactor模式,请考虑事件驱动用于分布式日志记录服务的服务器,如图1所示。客户端应用程序使用日志记录服务在分布式环境中记录有关其状态的信息。 此状态信息通常包括错误通知,调试跟踪和性能报告。 记录记录被发送到中央日志服务器,可以写入记录到各种输出设备,如控制台,打印机,文件或网络管理数据库。
图1:分布式日志服务
图1中显示的日志服务器处理日志记录客户端发送的记录和连接请求。日志记录和连接请求可以同时到达多个句柄。 句柄标识OS内管理的网络通信资源。
日志服务器使用面向连接的协议与客户端通信,如TCP。 客户想要获取日志数据必须先向服务器发送连接请求。 服务器使用一个句柄工厂等待这些连接请求,监听客户端已知的地址。当连接请求到达时,句柄工厂在客户端和服务器之间建立连接创建表示连接端点的新句柄。 此句柄返回到服务器,然后服务器等待客户端服务请求到达句柄。 一旦客户端已连接,它们可以同时向服务器发送日志记录。 服务器通过连接的Socket句柄接收这些记录。
如图2所示,也许使用多个线程同时处理多个客户端是开发一个并发的日志服务器最直观的方法。这种方法同步接受网络连接并用“一个连接对一个线程”的方式来处理客户端日志记录。
图2:多线程日志服务
然而,使用多线程来实现服务器中日志记录的处理无法解决以下问题:
效率:产生的线程太多可能导致性能不佳,上下文切换,同步和数据移动等问题;
编程简单性:线程可能需要复杂的并发控制方案;
可移植性:不是所有操作系统都能提供线程;
由于这些缺点,可以看出多线程方式不是开发并发日志服务器最有效的解决方案。
4、背景
一个在分布式系统中同时从一个或多个客户端接收事件的服务应用程序。
5、问题
分布式系统中的服务器应用程序必须处理发送服务请求的多个客户端。 但是,在调用特定服务之前,服务器应用程序必须解复用(demultiplexing)和分派(dispatching)每个传入请求给相应的服务提供者。 开发一个有效的解复用和调度客户端请求机制的服务器需要解决以下问题:
可用性:服务器必须可用于处理传入请求,即使它正在等待其他请求到达。 特别是,服务器不得无限制地阻塞单个事件源的处理请求而排除其他事件源的请求,因为这可能会显著地延迟对其他客户端的响应;
效率:服务器必须做到最小化延迟,最大化吞吐量,并避免不必要地使用CPU;
编程简单:服务器的设计应该简化适当的并发策略的使用;
适应性:整合一个新的或改进服务,例如更改消息格式或添加服务器端缓存,需要对现有代码产生最小的修改和维护成本。 例如,实施新的应用程序服务不应该要求修改通用事件多路分解和调度机制;
可移植性:将服务器移植到新的OS平台不需要花费很多精力。
6、解决方案
集成事件的同步解复用和分派处理事件的相应事件处理程序。此外,将特定于应用程序的服务调度和实现与通用事件解复用和调度机制分离。
对于应用程序提供的每个服务,引入一个单独的事件处理程序(Event Handler)来处理特定类型的事件。所有事件处理程序(Event Handlers)都实现同一接口。事件处理程序(Event Handlers)注册初始化调度器(Initiation Dispatcher),它使用同步事件解复用器(Synchronous Event Demultiplexer)等待事件发生。当事件发生时,同步事件解复用器(Synchronous Event Demultiplexer)通知启动调度程序(Initiation Dispatcher),它将同步调用关联的事件处理程序(Event Handler)与事件有关。然后,事件处理程序(Event Handler)将实现所请求服务的方法的事件。
7、结构
Reactor模式的主要参与者包括:
Handles(句柄):
标识由操作系统管理的资源。这些资源通常包括网络连接、打开的文件、计时器、同步对象等。日志服务器中使用句柄来标识套接字终结点,以便同步事件解复用器可以等待事件发生。日志服务器感兴趣的两种类型的事件是连接事件和读取事件,它们表示传入的客户端连接和日志数据。日志服务器给每个客户端维护一个单独的连接。服务器中的每个连接都由一个套接字句柄表示。
Synchronous Event Demultiplexer(同步的事件解复用器):
等待事件在一组Handles上发生的块。当可以在Handle上启动操作而不阻塞时返回。 用于I / O事件的多路复用器是select,这是UNIX和Win32 OS平台提供的多路复用系统调用。 select调用指示哪些Handles可以同步调用它们,而不会阻止应用程序进程。
Initiation Dispatcher(启动调度器):
定义用于注册,删除和调度事件处理器的接口。 最终,同步事件多路分解器负责等待新事件发生。 当它检测到新事件时,去通知启动调度程序(Initiation Dispatcher)回调特定于应用程序的事件处理器。 常见事件包括连接接受事件,数据输入和输出事件以及超时事件。
Event Handler(事件处理器):
指定一个或多个由钩子方法组成的接口。抽象地表示调度操作特定于服务的事件。 此方法必须由特定于应用程序的服务实现。
Concrete Event Handler(具体事件处理器):
实现hook方法,以及在特定于应用程序中处理这些事件的方法。应用程序注册Concrete Event Handler和Initiation Dispatcher处理某些类型的事件。当这些事件到达时,Initiation Dispatcher回调Concrete Event Handler的hook方法。
这里的日志服务中有两个Concrete Event Handler:Logging Handler 和Logging Acceptor,Logging Handler负责接收和处理日志记录,Logging Acceptor创建并连接Logging Handler,它会处理后续来自客户端的日志记录。
Reactor模式参与者的结构如下OMT类图所示:
8、动态
8.1、General Collaborations(一般合作)
Reactor模式中发生以下的协作:
1)、当应用程序使用Initiation Dispatcher注册Concrete Event Handler时,应用程序会指示此事件处理程序希望Initiation Dispatcher通知它关于何时在关联的Handle上发生事件。
2)、Initiation Dispatcher请求每个Event Handler传回其内部的Handle。此Handle标识操作系统的Event Handler。
3)、当所有Event Handlers注册后,应用程序调用句柄事件(Handle-events)以启动Initiation Dispatcher的事件循环。 此时,Initiation Dispatcher结合了每个已注册的Event Handler的Handle,并使用同步事件多路分解器(Synchronous Event Demultiplexer)等待这些句柄上发生的事件。 例如,TCP协议层使用select同步事件多路分解操作来等待客户端记录事件到达连接的套接字句柄。
4)、当对应于事件源的句柄变为“准备好”时,同步事件多路分解器(Synchronous Event Demultiplexer)通知启动调度器(Initiation Dispatcher),例如,TCP套接字“准备好读操作”。
5)、Initiation Dispatcher触发Event Handler钩子方法以响应就绪句柄上的事件。 当事件发生时,Initiation Dispatcher使用由事件源激活的句柄作为“keys”来定位和分派适当的事件处理程序(Event Handler)的钩子方法。
6)、Initiation Dispatcher调用Event Handler的句柄事件(Handle-events)钩子方法来执行特定于应用程序的功能以响应事件。 发生的事件类型可以作为参数传递给方法,并由此方法在内部使用,以执行其他特定于服务的解复用和分派。 第9.4节描述了另一种调度方法。
以下交互图说明了Reactor模式中应用程序代码与参与者之间的协作:
8.2、Collaboration Scenarios(协作方案)
可以用两种方案说明日志服务器的Reactor模式内的协作。 这些方案显示了使用响应事件分派设计的日志记录服务器如何处理连接请求和从多个客户端记录数据。
8.2.1、Client Connects to a Reactive Logging Server(客户端连接到反应式日志记录服务器)
第一个方案显示了客户端连接到日志服务器时所采取的步骤。
这一系列步骤可概括如下:
1)、日志记录服务器使用Initiation Dispatcher注册Logging Acceptor来处理连接请求;
2)、日志记录服务器调用Initiation Dispatcher的handle events方法;
3)、Initiation Dispatcher调用同步事件多路分解的select操作以等待连接请求或记录数据到达;
4)、一个客户端连接到日志服务器;
5)、Initiation Dispatcher通知Logging Acceptor有新连接请求;
6)、Logging Acceptor接受新连接请求;
7)、Logging Acceptor创建Logging Handler为新客户端提供服务;
8)、Logging Handler使用Initiation Dispatcher注册其套接字句柄,并指示调度程序在套接字“准备好读取”时通知它。
8.2.2、Client Sends Logging Record to a Reactive Logging Server(客户端将日志记录发送到反应式日志服务器)
第二个方案显示了响应式日志记录服务器为日志记录服务所采取的步骤顺序。
这一系列步骤可概括如下:
1)、客户端发送日志记录;
2)、当客户端日志记录在OS的套接字句柄上排队时,Initiation Dispatcher通知关联的Logging Handler;
3)、以非阻塞方式接收记录(步骤2和3重复,直到完全接收记录记录);
4)、 Logging Handler处理日志记录并将其写入标准输出。
5)、 Logging Handler将控制返回到Initiation Dispatcher的事件循环。