反应堆模式一直在发展之中,以为高效的事件多路分离和分派提供可扩展的面向对象构架。目前用于事件多路分离的
OS抽象既复杂又难以使用,因而也容易出错。反应堆本质上提供一组更高级的编程抽象,简化了事件驱动的分布式应用的设计和实现。除此而外,反应堆还将若干不同种类的事件的多路分离集成到易于使用的API中。特别地,反应堆对基于定时器的事件、信号事件、基于I/O端口监控的事件和用户定义的通知进行统一地处理。在本章里,我们描述怎样将反应堆用于对所有这些不同的事件类型进行多路分离。
反应堆组件

图
6-1反应堆中的内部组件和外部组件的协作
如图
6-1所示,ACE中的反应堆与若干内部和外部组件协同工作。其基本概念是反应堆构架检测事件的发生(通过在OS事件多路分离接口上进行侦听),并发出对预登记事件处理器(event handler)对象中的方法的“回调”(callback)。该方法由应用开发者实现,其中含有应用处理此事件的特定代码。于是用户(也就是,应用开发者)必须:
随后反应堆构架将自动地:
反应堆模式在
ACE中被实现为ACE_Reactor类,它提供反应堆构架的功能接口。如上面所提到的,反应堆将事件处理器对象作为服务提供者使用。一旦反应堆成功地多路分离和分派了某事件,事件处理器对象就对它进行处理。因此,反应堆会在内部记住当特定类型的事件发生时,应该回调哪一个事件处理器对象。当应用在反应堆上登记它的处理器对象,以处理特定类型的事件时,反应堆会创建这种事件和相应的事件处理器的关联。
因为反应堆需要记录哪一个事件处理器将被回调,它需要知道所有事件处理器对象的类型。这是通过替换模式(
Substitution Pattern)的帮助来实现的(或者换句话说,通过“是……类型”(is a type of)变种继承)。该构架提供名为ACE_Event_Handler的抽象接口类,所有应用特有的事件处理器都必须由此派生(这使得应用特有的处理器都具有相同的类型,即ACE_Event_Handler,所以它们可以相互替换)。要了解此概念的更多细节,请阅读替换模式的参考资料[V]。如果你留意上面的组件图,其中的事件处理器的椭圆形包括灰色的
Event_Handler部分,对应于ACE_Event_Handler;以及白色的部分,它对应于应用特有的部分。图
6-2对其进行说明:

图
6-2 ACE_Event_Handler类图
ACE_Event_Handler
类拥有若干不同的“handle”(处理)方法,每个处理方法被用于处理不同种类的事件。当应用程序员对特定事件感兴趣时,他就对ACE_Event_Handler类进行子类化,并实现他感兴趣的处理方法。如上面所提到的,随后他就在反应堆上为特定事件“登记”他的事件处理器类。于是反应堆就会保证在此事件发生时,自动回调在适当的事件处理器对象中的适当的”handle”方法。使用
ACE_Reactor基本上有三个步骤:
下面的简单例子可以帮助我们更好地理解这些步骤:
例
6-1#include
#include ”ace/Reactor.h”
#include ”ace/Event_Handler.h”
//Create our subclass to handle the signal events
//that we wish to handle. Since we know that this particular
//event handler is going to be using signals we only overload the
//handle_signal method.
class MyEventHandler: public ACE_Event_Handler
{
int handle_signal(int signum, siginfo_t*,ucontext_t*)
{
switch(signum)
{
case SIGWINCH:
ACE_DEBUG((LM_DEBUG, ”You pressed SIGWINCH \n”));
break;
case SIGINT:
ACE_DEBUG((LM_DEBUG, ”You pressed SIGINT \n”));
break;
}
return 0;
}
};
int main(int argc, char *argv[])
{
//instantiate the handler
MyEventHandler *eh =new MyEventHandler;
//Register the handler asking to call back when either SIGWINCH
//or SIGINT signals occur. Note that in both the cases we asked the
//Reactor to callback the same Event_Handler i.e., MyEventHandler.
//This is the reason why we had to write a switch statement in the
//handle_signal() method above. Also note that the ACE_Reactor is
//being used as a Singleton object (Singleton pattern)
ACE_Reactor::instance()->register_handler(SIGWINCH,eh);
ACE_Reactor::instance()->register_handler(SIGINT,eh);
while(1)
//Start the reactors event loop
ACE_Reactor::instance()->handle_events();
}
在上面的例子中,我们首先创建了一个
ACE_Event_Handler的子类,在其中我们重载了handle_signal()方法,因为我们想要使用此处理器来处理多种类型的信号。在主函数中,我们对我们的处理器进行实例化,随后调用ACE_Reactor单体(Singleton)的register_handler,指明我们希望在SIGWINCH(终端窗口改变信号)或SIGINT(中断信号,通常是^C)发生时,事件处理器“eh”会被回调。然后,我们通过调用在无限循环中调用handle_events()来启动反应堆的事件循环。无论是发生哪一个事件,反应堆都将自动回调eh->handle_signal()方法,将引发回调的信号号码、以及siginfo_t结构(有关siginfo_t的更多信息,参见siginfo.h)传给它。注意程序是怎样使用单体模式(
Singleton Pattern)来获取全局反应堆对象的引用的。大多数应用都只需要一个反应堆,因而ACE_Reactor::instance()会确保无论何时此方法被调用,都会返回同一个ACE_Reactor实例(要阅读更多有关单体模式的信息,请见“设计模式”参考文献[VI])。表
6-1显示在ACE_Event_Handler的子类中必须重载哪些方法,以处理不同的事件类型。
ACE_Event_Handler 中的处理方法 | 在子类中重载,所处理事件的类型 :
|
handle_signal() | 信号。当任何在反应堆上登记的信号发生时,反应堆自动回调该方法。 |
handle_input() | 来自 I/O设备的输入。当I/O句柄(比如UNIX中的文件描述符)上的输入可用时,反应堆自动回调该方法。 |
handle_exception() | 异常事件。当已在反应堆上登记的异常事件发生时(例如,如果收到 SIGURG(紧急信号)),反应堆自动回调该方法。 |
handle_timeout() | 定时器。当任何已登记的定时器超时的时候,反应堆自动回调该方法。 |
handle_output() | I/O 设备输出。当I/O设备的输出队列有可用空间时,反应堆自动回调该方法。 |
表
6-1 ACE_Event_Handler中的处理方法及其对应事件6.2.1事件处理器登记如我们在上面的例子中所看到的,登记事件处理器、以处理特定事件,是在反应堆上调用
register_handler()方法来完成的。register_handler()方法是重载方法,就是说,实际上有若干方法可用于登记不同的事件类型,每个方法都叫做register_handler()。但是它们有着不同的特征:它们的参数各不相同。基本上,register_handler()方法采用handle/event_handle元组或signal/event_handler元组作为参数,并将它们加入反应堆的内部分派表。当有事件在handle上发生时,反应堆在它的内部分派表中查找相应的event_handler,并自动在它找到的event_handler上回调适当的方法。有关登记处理器的专用调用的更多细节将在后面的部分进行阐释。6.2.2事件处理器的拆除和生存期管理
一旦所需的事件被处理后,可能就无需再让事件处理器登记在反应堆上。因而,反应堆提供了从它的内部分派表中拆除事件处理器的技术。一旦事件处理器被拆除,它就不再会被反应堆回调。
为多个客户服务的服务器是这种情况的一个例子。客户连接到服务器,让它完成一些工作,然后从服务器断开。当有新的客户连接到服务器时,一个事件服务器对象被实例化,并登记到服务器的反应堆上,以处理所有与此客户有关的
I/O。当客户断开时,服务器必须将事件处理器从反应堆的分派队列中拆除,因为它将不再进行任何与此客户有关的I/O。在此例中,客户/服务器连接可能会被关闭,使得I/O句柄(UNIX中的文件描述符)变得无效。把这样的死掉的句柄从反应堆里拆除是很重要的,因为,如果不这样做,反应堆将会把此句柄标记为“读就绪”,并会持续不断地回调此事件处理器的handle_input()方法。
