上面的例子创建了两个具体事件处理器。第一个具体事件处理器
My_Accept_Handler用于接受和建立从客户到来的连接。另一个事件处理器是My_Input_Handler,它用于在连接建立后对连接进行处理。因而,My_Accept_Handler接受连接,并将实际的处理委托给My_Input_Handler。

图
6-3 反应堆的事件处理
在上面的例子中,我们首先创建了一个
ACE_INET_Addr地址对象,将我们希望在其上接受连接的端口作为参数传给它。其次,实例化一个类型为My_Accept_Handler的对象。随后地址对象通过My_Accept_Handler的构造器传递给它。My_Accept_Handler有一个用于连接建立的底层“具体接受器”(在讲述“IPC”的一章中有与具体接受器相关的内容)。My_Accept_Handler的构造器将对新连接的“侦听”委托给该具体接受器的open()方法。在处理器开始侦听连接后,它在反应堆上登记,通知说在接收到新连接请求时,它需要被回调。为完成此操作,我们采用ACE_Event_Handler::ACCEPT_MASK掩码调用register_handler()。当反应堆被告知要登记处理器时,它执行“双重分派”来确定事件处理器的底层句柄。为完成此操作,它调用
get_handler()方法。因为反应堆使用get_handle()方法来确定底层流的句柄,在My_Accept_Handler中必须实现get_handle()方法。在此例中,我们简单地调用具体接受器的get_handle(),它会将适当的句柄返回给反应堆。一旦在该句柄上接收到新的连接请求,反应堆会自动地回调
My_Accept_Handler的handle_input()方法。随后Accept Handler(接受处理器)实例化一个新的Input Handler(输入处理器),并调用具体接受器的accept()方法来实际地建立连接。注意Input Handler底层的流是作为accept()调用的第一个参数传入的。这使得新实例化的Input Handler中的流被设置为在连接建立(由accept()完成)后立即创建的新流。随后Accept Handler将Input Handler登记到反应堆,通知它如果有任何可读的输入就进行回调(使用ACE_Event_Handler::READ_MASK)。随后接受处理器返回-1,使自己从反应堆的内部事件分派表中被拆除。现在,如果有任何输入从客户到达,反应堆将自动回调
My_Input_Handler::handle_input()。注意在My_Input_Handler的handle_input()方法中,返回给反应堆是0。这指示我们希望保持它的登记;反之在My_Accept_Handler中我们在它的handle_input()中返回-1,以确保它被注销。除了在上面的例子中使用的
READ_MASK和ACCEPT_MASK而外,还有若干其他的掩码,可在登记或是拆除处理器时使用。这些掩码如表6-2所示,它们可与register_handler()和remove_handler()方法一起使用。每个掩码保证反应堆回调事件处理器时的不同行为方式,通常这意味着不同的“handle”方法会被回调。
|
掩码 |
回调方法 |
何时 |
和……一起使用 |
|
ACE_Event_Handler::READ_MASK |
handle_input() |
在句柄上有数据可读时。 |
register_handler() |
|
ACE_Event_Handler::WRITE_MASK |
handle_output() |
在 I/O设备输出缓冲区上有可用空间、并且新数据可以发送给它时。 |
register_handler() |
|
ACE_Event_Handler::TIMER_MASK |
handle_close() |
传给 handle_close()以指示调用它的原因是超时。 |
接受器和连接器的 handle_timeout方法。反应堆不使用此掩码。 |
|
ACE_Event_Handler::ACCEPT_MASK |
handle_input() |
在 OS内部的侦听队列上收到了客户的新连接请求时。 |
register_handler() |
|
ACE_Event_Handler::CONNECT_MASK |
handle_input() |
在连接已经建立时。 |
register_handler() |
|
ACE_Event_Handler::DONT_CALL |
None. |
在反应堆的 remove_handler()被调用时保证事件处理器的handle_close()方法不被调用。 |
remove_handler() |
表
6-2 反应堆中的掩码 定时器(Timer)反应堆还包括了调度定时器的方法,它们在超时的时候回调适当的事件处理器的
handle_timeout()方法。为调度这样的定时器,反应堆拥有一个schedule_timer()方法。该方法接收事件处理器(该事件处理器的handle_timeout()方法将会被回调)、以及以ACE_Time_value对象形式出现的延迟作为参数。此外,还可以指定时间间隔,使定时器在它超时后自动被复位。反应堆在内部维护
ACE_Timer_Queue,它以定时器要被调度的顺序对它们进行维护。实际使用的用于保存定时器的数据结构可以通过反应堆的set_timer_queue()方法进行改变。反应堆有若干不同的定时器结构可用,包括定时器轮(timer wheel)、定时器堆(timer heap)和哈希式定时器轮(hashed timer wheel)。这些内容将在后面的部分详细讨论。6.4.1 ACE_Time_Value
ACE_Time_Value
是封装底层OS平台的日期和时间结构的包装类。它基于在大多数UNIX操作系统上都可用的timeval结构;该结构存储以秒和微秒计算的绝对时间。其他的
OS平台,比如POSIX和Win32,使用略有不同的表示方法。该类封装这些不同,并提供了可移植的C++接口。ACE_Time_Value
类使用运算符重载,提供简单的算术加、减和比较。该类中的方法会对时间量进行“规范化”(normalize)。所谓规范化,是将timeval结构中的两个域调整为规范化的编码方式;这种编码方式可以确保精确的比较(更多内容参见附录和参考文献指南)。6.4.2 设置和拆除定时器
下面的例子演示怎样与反应堆一起使用定时器。
例
6-3#include ”test_config.h”
#include ”ace/Timer_Queue.h”
#include ”ace/Reactor.h”
#define NUMBER_TIMERS 10
static int done = 0;
static int count = 0;
class Time_Handler : public ACE_Event_Handler
{
public:
//Method which is called back by the Reactor when timeout occurs.
virtual int handle_timeout (const ACE_Time_Value &tv,
const void *arg)
{
long current_count = long (arg);
ACE_ASSERT (current_count == count);
ACE_DEBUG ((LM_DEBUG, ”%d: Timer #%d timed out at %d!\n”,
count, current_count, tv.sec()));
//Increment count
count ++;
//Make sure assertion doesn’t fail for missing 5th timer.
if (count ==5)
count++;
//If all timers done then set done flag
if (current_count == NUMBER_TIMERS - 1)
done = 1;
//Keep yourself registered with the Reactor.
return 0;
}
};
int main (int, char *[])
{
ACE_Reactor reactor;
Time_Handler *th=new Time_Handler;
int timer_id[NUMBER_TIMERS];
int i;
for (i = 0; i < NUMBER_TIMERS; i++)
timer_id[i] = reactor.schedule_timer (th,
(const void *) i, // argument sent to handle_timeout()
ACE_Time_Value (2 * i + 1)); //set timer to go off with delay
//Cancel the fifth timer before it goes off
reactor.cancel_timer(timer_id[5]);//Timer ID of timer to be removed
while (!done)
reactor.handle_events ();
return 0;
}
在上面的例子中,首先通过实现事件处理器
Time_Handler的handle_timeout()方法,将其设置用以处理超时。主函数实例化Time_Handler类型的对象,并使用反应堆的schedule_timer()方法调度多个定时器(10个)。handle_timeout方法需要以下参数:指向将被回调的处理器的指针、定时器超时时间,以及一个将在handle_timeout()方法被回调时发送给它的参数。每次调用schedule_timer(),它都返回一个唯一的定时器标识符,并随即存储在timer_id[]数组里。这个标识符可用于在任何时候取消该定时器。在上面的例子中也演示了定时器的取消:在所有定时器被初始调度后,程序通过调用反应堆的cancel_timer()方法(使用相应的timer_id作为参数)取消了第五个定时器。
