미래를 어느 정도 현실 속에 도입할 수 있는지를 정확히 아는 것이 현명한 정부의 비결. ―빅토르 위고
* 제목 : "The Reactor : An Architectural Pattern for Event De-multiplexing and Dispatching"
개요 #
The Reactor Pattern has been developed to provide an extensible OO framework for efficient event de-multiplexing and dispatching. Current OS abstractions that are used for event de-multiplexing are difficult and complicated to use, and are therefore errorprone.
The Reactor pattern essentially provides for a set of higher-level programming abstractions that simplify the design and implementation of event-driven distributed applications. Besides this, the Reactor integrates together the de-multiplexing of several different kinds of events to one easy-to-use API. In particular, the Reactor handles timerbased events, signal events, I/O-based port monitoring events and user-defined
notifications uniformly. In this chapter, we describe how the Reactor is used to de-multiplex all of these different event types.
Reactor Components #

As shown in the above figure, the Reactor in ACE works in conjunction with several components, both internal and external to itself. The basic idea is that the Reactor framework determines that an event has occurred (by listening on the OS Event Demultiplexing Interface) and issues a "callback" to a method in a pre-registered event handler object. This object is implemented by the application developer and contains application specific code to handle the event.
The user (i.e., the application developer) must thus :
- Create an Event Handler to handle an event he is interested in.
- Register with the Reactor, informing it that he is interested in handling an event and at this time also passing a pointer to the event handler he wishes to handle the event.
- The Reactor maintains tables internally, which associate different event types with event handler objects
- When an event occurs that the user had registered for, it issues a call back to the appropriate method in the handler.
Event Handlers #
The Reactor pattern is implemented in ACE as the ACE_Reactor class, which provides an interface to the reactor framework's functionality.
As was mentioned above, the reactor uses event handler objects as the service providers which handle an event once the reactor has successfully de-multiplexed and dispatched it.
The reactor therefore internally remembers which event-handler object is to be called back when a certain type of event occurs. This association between events and their event handlers is created when an application registers its handler object with the reactor to handle a certain type of event.
Since the reactor needs to record which Event Handler is to be called back, it needs to know the type of all Event Handler object. This is achieved with the help of the substitution pattern (or in other words through inheritance of the "is a type of" variety).
The framework provides an abstract interface class named ACE_Event_Handler from which all application specific event handlers MUST derive. (This causes each of the Application Specific Handlers to have the same type, namely ACE_Event_Handler, and thus they can be substituted for each other). For more detail on this concept, please see the reference on the Substitution Pattern [V].
If you notice the component diagram above, it shows that the event handler ovals consist of a blue Event_Handler portion, which corresponds to ACE_Event_Handler, and a white portion, which corresponds to the application-specific portion.
This is illustrated in the class diagram below:

The ACE_Event_Handler class has several different "handle" methods, each of which are used to handle different kinds of events. When an application programmer is interested in a certain event, he subclasses the ACE_Event_Handler class and implements the handle methods that he is interested in. As mentioned above, he then proceeds to "register" his event handler class for that particular event with the reactor. The reactor will then make sure that when the event occurs, the appropriate "handle" method in the appropriate event handler object is called back automatically.
Once again, there are basically three steps to using the ACE_Reactor:
- Create a subclass of ACE_Event_Handler and implement the correct "handle_" method in your subclass to handle the type of event you wish to service with this event handler. (See table below to determine which "handle_" method you need to implement. Note that you may use the same event handler object to handle multiple types of events, and thus may overload more then one of the "handle_" methods.)
- Register your Event handler with the reactor by calling register_handler() on the reactor object.
- As events occur, the reactor will automatically call back the correct "handle_" method of the event handler object that was previously registered with the Reactor to process that event.
Example 1 #
#include <signal.h>
#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 call back 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();
}
In the above example, we first create a sub-class of ACE_Event_Handler in which we overload the handle_signal() method, since we intend to use this handler to handle various types of signals. In the main routine, we instantiate our handler and then call register_handler on the ACE_Reactor Singleton, specifying that we wish the event handler 'eh' to be called back when either SIGWINCH (signal on terminal window change) or SIGINT (interrupt signal, usually ^C) occur. After this, we start the reactor's event loop by calling handle_events() in an infinite loop. Whenever either of the events happen the reactor will call back the eh->handle_signal() method automatically, passing it the signal number which caused the callback, and the siginfo_t structure (see siginfo.h for more on siginfo_t).
Notice the use of the Singleton pattern to obtain a reference to the global reactor object.
Most applications require a single reactor and thus ACE_Reactor comes complete with the instance() method which insures that whenever this method is called, the same ACE_Reactor instance is returned. (To read more about the Singleton Pattern please see the Design Patterns reference [ VI].)
The following table shows which methods must be overloaded in the subclass of ACE_Event_Handler to process different event types.
| Handle Methods in ACE_Event_Handler | Overloaded in subclass to be used to handle event of type: |
| handle_signal() | Signals. When any signal is registered with the reactor, it will call back this method automatically when the signal occurs. |
| handle_input() | Input from I/O device. When input is available on an I/O handle (such as a file descriptor in UNIX), this method will be called back by the reactor automatically. |
| handle_exception() | Exceptional Event. When an exceptional event occurs on an event that has been registered with the reactor (for example, if SIGURG (Urgent Signal) is received), then this method will be automatically called back. |
| handle_timeout() | Timer. When the time for any registered timer expires, this method will be called back automatically. |
| handle_output() | Output possible on I/O device. When the output queue of an I/O device has space available on it, this method will automatically be called back. |
Registration of Event Handlers #
As we saw in the example above, an event handler is registered to handle a certain event by calling the register_handler() method on the reactor. The register_handler() method is overloaded, i.e. there are actually several methods for registering different event types, each called register_handler(), but having a different signature, i.e. the methods differ in their arguments. The register_handler() methods basically take the handle/event_handler tuple or the signal/event_handler tuple as arguments and add it to the reactor's internal dispatch tables. When an event occurs on handle, it finds the corresponding event_handler in its internal dispatch table and automatically calls back the appropriate method on the event_handler it finds. More details of specific calls to register handlers will be illustrated in later sections.
Removal and lifetime management of Event Handlers #
Once the required event has been processed, it may not be necessary to keep the event handler registered with the reactor. The reactor thus offers techniques to remove an event handler from its internal dispatch tables. Once the event handler is removed, it will no longer be called back by the reactor.
An example of such a situation could be a server which serves multiple clients. The clients connect to the server, have it perform some work and then disconnect later. When a new client connects to the server, an event handler object is instantiated and registered in the server's reactor to handle all I/O from this client. When the client disconnects then the server must remove the event handler from the reactor's dispatch queue, since it no longer expects any further I/O from the client. In this example, the client/server connection may be closed down, which leaves the I/O handle (file descriptor in UNIX) invalid. It is important that such a defunct handle be removed from the Reactor, since, if this is not done, the Reactor will mark the handle as "ready for reading" and continually call back the handle_input() method of the event handler forever.
There are several techniques to remove an event handler from the reactor.
Implicit Removal of Event Handlers from the Reactors Internal dispatch tables #
The more common technique to remove a handler from the reactor is implicit removal.
Each of the "handle_" methods of the event handler returns an integer to the reactor. If this integer is 0, then the event handler remains registered with the reactor after the handle method is completed. However, if the "handle_" method returns <0, then the reactor will automatically call back the handle_close() method of the Event Handler and remove it from its internal dispatch tables. The handle_close() method is used to perform any handler specific cleanup that needs to be done before the event handler is removed, which may include things like deleting dynamic memory that had been allocated by the handler or closing log files.
In the example described above, it is necessary to actually remove the event handler from memory. Such removal can also occur in the handle_close() method of the concrete event handler class. Consider the following concrete event handler:
class MyEventHandler: public ACE_Event_Handler{
public:
MyEventHandler(){//construct internal data members}
virtual int handle_close(ACE_HANDLE handle, ACE_Reactor_Mask mask)
{
delete this; //commit suicide
}
~MyEventHandler(){//destroy internal data members}
private:
//internal data members
};
This class deletes itself when it is de-registers from the reactor and the handle_close() hook method is called. It is VERY important however that MyEventHandler is always allocated dynamically otherwise the global memory heap may be corrupted. One way to ensure that the class is always created dynamically is to move the destructor into the private section of the class. For example:
class MyEventHandler: public ACE_Event_Handler{
public:
MyEventHandler(){//construct internal data members}
virtual int handle_close(ACE_HANDLE handle, ACE_Reactor_Mask mask)
{
delete this; //commit suicide
}
private:
//Class must be allocated dynamically
~MyEventHandler(){//destroy internal data members}
};
Explicit removal of Event Handlers from the Reactors Internal Dispatch Tables #
Another way to remove an Event Handler from the reactor's internal tables is to explicitly call the remove_handler() set of methods of the reactor. This method is also overloaded, as is register_handler(). It takes the handle or the signal number whose handler is to be removed and removes it from the reactor's internal dispatch tables. When the remove_handler() is called, it also calls the handle_close() method of the Event Handler (which is being removed) automatically. This can be controlled by passing in the mask ACE_Event_Handler::DONT_CALL to the remove_handler() method, which causes the handle_close() method NOT to be called. More specific examples of the use of remove_handler() will be shown in the next few sections.
Event Handling with the Reactor #
In the next few sections, we will illustrate how the Reactor is used to handle various types of events.
I/O Event De-multiplexing #
The Reactor can be used to handle I/O device based input events by overloading the handle_input() method in the concrete event handler class. Such I/O may be on disk files, pipes, FIFOs or network sockets. For I/O device-based event handling, the Reactor internally uses the handle to the device which is obtained from the operating system. (The handle on UNIX-based system is the file descriptor returned by the OS when a file or socket is opened. In Windows the handle is a handle to the device returned by Windows.). One of the most useful applications of such de-multiplexing is obviously for network applications. The following example will help illustrate how the reactor may be used in conjunction with the concrete acceptor to build a server.
Example 2 #
#include "ace/Reactor.h"
#include "ace/SOCK_Acceptor.h"
#define PORT_NO 19998
typedef ACE_SOCK_Acceptor Acceptor;
//forward declaration
class My_Accept_Handler;
class My_Input_Handler: public ACE_Event_Handler
{
public:
//Constructor
My_Input_Handler() {
ACE_DEBUG((LM_DEBUG, "Constructor\n");
}
//Called back to handle any input received
int handle_input(ACE_HANDLE) {
//receive the data
peer().recv_n(data,12);
ACE_DEBUG((LM_DEBUG, "%s\n", data));
// do something with the input received.
// ...
//keep yourself registered with the reactor
return 0;
}
//Used by the reactor to determine the underlying handle
ACE_HANDLE get_handle() const {
return this->peer_i().get_handle();
}
//Returns a reference to the underlying stream.
ACE_SOCK_Stream &peer_i() {
return this->peer_;
}
private:
ACE_SOCK_Stream peer_;
char data [12];
};
class My_Accept_Handler: public ACE_Event_Handler
{
public:
//Constructor
My_Accept_Handler(ACE_Addr &addr){
this->open(addr);
}
//Open the peer_acceptor so it starts to "listen"
//for incoming clients.
int open(ACE_Addr &addr)
{
peer_acceptor.open(addr);
return 0;
}
// Overload the handle input method
int handle_input(ACE_HANDLE handle)
{
//Client has requested connection to server.
//Create a handler to handle the connection
My_Input_Handler *eh= new My_Input_Handler();
//Accept the connection "into" the Event Handler
if (this->peer_acceptor.accept (eh->peer(), // stream
0, // remote address
0, // timeout
1) ==-1) //restart if interrupted
ACE_DEBUG((LM_ERROR, "Error in connection\n"));
ACE_DEBUG((LM_DEBUG, "Connection established\n"));
//Register the input event handler for reading
ACE_Reactor::instance()->register_handler(eh,ACE_Event_Handler::READ_MASK);
//Unregister as the acceptor is not expecting new clients
return -1;
}
//Used by the reactor to determine the underlying handle
ACE_HANDLE get_handle(void) const {
return this->peer_acceptor.get_handle();
}
private:
Acceptor peer_acceptor;
};
int main(int argc, char * argv[])
{
//Create an address on which to receive connections
ACE_INET_Addr addr(PORT_NO);
//Create the Accept Handler which automatically begins to "listen"
//for client requests for connections
My_Accept_Handler *eh=new My_Accept_Handler(addr);
//Register the reactor to call back when incoming client connects
ACE_Reactor::instance()->register_handler(eh, ACE_Event_Handler::ACCEPT_MASK);
//Start the event loop
while(1)
ACE_Reactor::instance()->handle_events();
}
In the above example, two concrete event handlers are created. The first concrete event handler, My_Accept_Handler, is used to accept and establish incoming connections from clients. The other event handler is My_Input_Handler, which is used to handle the connection after it has been established. Thus My_Accept_Handler accepts the connection and delegates the actual handling off to My_Input_Handler.
In the above example, first we create an ACE_INET_Addr object passing it the port on which we wish to accept connections. Next, an object of type My_Accept_Handler is instantiated. The address object is then passed to My_Accept_Handler through its constructor. My_Accept_Handler has an underlying "concrete acceptor" (read more about concrete acceptors in the chapter on "IPC") which it uses to establish the connection. The constructor of My_Accept_Handler delegates the "listening" for new connections to the open() method of this concrete acceptor. After the handler starts listening for connections it is registered with the reactor informing it that it is to be called back when a new connection request is received. To do this, we call register_handler() with the mask "ACE_Event_Handler::ACCEPT_MASK".
When the reactor is told to register the handler, it performs a "double dispatch" to determine the underlying handle of the event handler. To do this, it calls the get_handle() method. Since the reactor uses the get_handle() method to determine the handle to the underlying stream, it is necessary that this method be implemented in My_Accept_Handler. In this example, we simply call get_handle() on the concrete acceptor, which returns the appropriate handle to the reactor.
Once a new connection request is received on this handle, the reactor will automatically call back the handle_input() method of My_Accept_Handler. The Accept Handler then instantiates a new Input Handler and calls accept() on the concrete acceptor to actually establish the connection. Notice that the underlying stream of the Input Handler is passed in as the first argument to the accept() call. This causes the stream in the newly instantiated input handler to be set to the new stream which has just been created after establishment of the connection (by the accept() call). The Accept Handler then registers the Input Handler with the reactor, informing it to call back if any input is available to read (using ACE_Event_Handler::READ_MASK). It then returns -1, which causes it to be removed from the reactor's internal event dispatch tables.
When any input now arrives from the client, My_Input_Handler::handle_input() will automatically be called back by the reactor. Note that in the handle_input() method of My_Input_Handler, 0 is returned to the reactor. This indicates that we wish to keep it registered, whereas in My_Accept_Handler we insured that is was de-registered by returning -1 in its handle_input() method.
Besides the READ_MASK and ACCEPT_MASK that are used in the example above,
there are several other masks that can be used when registering and removing handles
from the reactor. These masks are shown in the table below, and can be used in
conjunction with the register_handler() and remove_handler() methods. Each mask
insures different behavior of the reactor when it calls back an event handler, usually
meaning a different "handle" method is to be called.
| MASK | Calls back method | When | Used with |
| ACE_Event_Handler::READ_MASK | handle_input(). | When there is data available to read on the handle. | register_handler() |
| ACE_Event_Handler::WRITE_MASK | handle_output(). | When there is room available on the I/O devices output buffer and new data can be sent to it. | register_handler() |
| ACE_Event_Handler::TIMER_MASK | handle_close(). | Passed to handle_close() to indicate the reason for calling it was a time-out. | Acceptor and Connectors handle_timeout methods and NOT by the Reactor. |
| ACE_Event_Handler::ACCEPT_MASK | handle_input(). | When a client request for a new connection is heard on the OS's internal listen queue. | register_handler() |
| ACE_Event_Handler::CONNECT_MASK | handle_input(). | When the connection has been established. | register_handler() |
| ACE_Event_Handler::DONT_CALL | None. | Insures that the handle_close() method of the Event_Handler is NOT called when the reactor's remove_handler() method is called. | remove_handler() |
Timers #
The Reactor also includes methods to schedule timers, which on expiry call back the
handle_timeout() method of the appropriate event handler. To schedule such timers, the
reactor has a schedule_timer() method. This method is passed the event handler ,whose
handle_timeout() method is to be called back, and the delay in the form of an
ACE_Time_Value object. In addition, an interval may also be specified which causes the
timer to be reset automatically after it expires.
Internally, the Reactor maintains an ACE_Timer_Queue which maintains all of the timers
in the order in which they are to be scheduled. The actual data structure used to hold the
timers can be varied by using the set_timer_queue() method of the reactor. Several
different timer structures are available to use with the reactor, including timer wheels,
timer heaps and hashed timer wheels. These are discussed in a later section in detail.
ACE_Time_Value #
The ACE_Time_Value object is a wrapper class which encapsulates the data and time
structure of the underlying OS platform. It is based on the timeval structure available on
most UNIX operating systems, which stores absolute time in seconds and micro-seconds.
Other OS platforms, such as POSIX and Win32, use slightly different representations.
This class encapsulates these differences and provides a portable C++ interface.
The ACE_Time_Value class uses operator overloading, which provides for simple
arithmetic additions, subtractions and comparisons. Methods in this class are
implemented to "normalize" time quantities. Normalization adjusts the two fields in a
timeval struct to use a canonical encoding scheme that ensures accurate comparisons.
(For more see Appendix and Reference Guide).
Setting and Removing Timers #
The following example illustrates how timers can be used with the reactor.
Example 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;
}
In the above example, an event handler, Time_Handler is first set up to handle the timeouts
by implementing the handle_timeout() method. The main routine instantiates an
object of type Time_Handler and schedules multiple timers (10 timers) using the
schedule_timer() method of the reactor. This method takes, as arguments, a pointer to the
handler which will be called back, the time after which the timer will go off and an
argument that will be sent to the handle_timeout() method when it is called back. Each
time schedule_timer() is called, it returns a unique timer identifier which is stored in the
array timer_id[]. This identifier may be used to cancel that timer at any time. An example
of canceling a timer is also shown in the above example, where the fifth timer is canceled
by calling the reactor's cancel_timer() method after all the timers have been initially
scheduled. We cancel this timer by using its timer_id as an argument to the
cancel_timer() method of the reactor.
Using different Timer Queues #
Different environments may require different ways of scheduling and canceling timers.
The performance of algorithms to implement timers become an issue when any of the
following are true:
- Fine granularity timers are required.
- The number of outstanding timers at any one time can potentially be very large.
- The algorithm is implemented using hardware interrupts which are too expensive.
| Timer | Description of data structure | Performance |
| ACE_Timer_Heap | The timers are stored in a heap implementation of a priority queue. | Cost of schedule_timer()= O(lg n) Cost of cancel_timer()= O(lgn) Cost of finding current timer O(1) |
| ACE_Timer_List | The timers are stored in a doubly linked list .. insertions are..?? | Cost of schedule_timer()= O(n) Cost of cancel_timer()= O(1) Cost of finding current timer O(1) |
| ACE_Timer_Hash | This structure used in this case is a variation on the timer wheel algorithm. The performance is highly dependent on the hashing function used. | Cost of schedule_timer()= Worst = O(n) Best = O(1) Cost of cancel_timer()= O(1) Cost of finding current timer O(1) |
| ACE_Timer_Wheel | The timers are stored in an array of "pointers to arrays". With each array being pointed to is sorted. | Cost of schedule_timer()= Worst =O(n) Cost of cancel_timer()= O(1) Cost of finding current timer O(1) |
Handling Signals #
As we saw in example 1, the Reactor includes methods to allow the handling of signals.
The Event Handler which handles the signals should overload the handle_signal()
method, since this will be called back by the reactor when the signal occurs. To register
for a signal, we use one of the register_handler() methods, as was illustrated in example
1. When interest in a certain signal ends, the handler can be removed and restored to the
previously installed signal handler by calling remove_handler(). The Reactor internally
uses the sigaction() system call to set and reset signal handlers. Signal handling can also
be done without the reactor by using the ACE_Sig_Handlers class and its associated
methods.
One important difference in using the reactor for handling signals and using the
ACE_Sig_Handlers class is that the reactor-based mechanism only allows the application
to associate one event handler with each signal. The ACE_Sig_Handlers class however
allows multiple event handlers to be called back when a signal occurs.
Using Notifications #
The reactor not only issues call backs when system events occur, but can also call back handlers when user defined events occur. This is done through the reactor's
"Notification" interface, which consists of two methods, notify() and max_notify_iterations().
The reactor can be explicitly instructed to issue a callback on a certain event handler
object by using the notify() method. This is very useful when the reactor is used in
conjunction with message queues or with co-operating tasks. Good examples of this kind
of usage can be found when the ASX framework components are used with the reactor.
The max_notify_iterations() method informs the reactor to perform only the specified
number of "iterations" at a time. Here, "iterations" refers to the number of "notifications"
that can occur in a single handle_events() call. Thus if max_notify_iterations() is used to
set the max number of iterations to 20, and 25 notifications arrive simultaneously, then
the handle_events() method will only service 20 of the notifications at a time. The
remaining five notifications will be handled when handle_events() is called the next time
in the event loop.
An example will help illustrate these ideas further:
Example 4 #
#include "ace/Reactor.h"
#include "ace/Event_Handler.h"
#include "ace/Synch_T.h"
#include "ace/Thread_Manager.h"
#define WAIT_TIME 1
#define SLEEP_TIME 2
class My_Handler: public ACE_Event_Handler {
public:
//Start the event handling process.
My_Handler(){
ACE_DEBUG((LM_DEBUG,"Event Handler created\n"));
ACE_Reactor::instance()->max_notify_iterations(5);
return 0;
}
//Perform the notifications i.e., notify the reactor 10 times
void perform_notifications(){
for(int i=0;i<10;i++)
ACE_Reactor::instance()->
notify(this,ACE_Event_Handler::READ_MASK);
}
//The actual handler which in this case will handle the notifications
int handle_input(ACE_HANDLE){
ACE_DEBUG((LM_DEBUG,"Got notification # %d\n",no));
no++;
return 0;
}
private:
static int no;
};
//Static members
int My_Handler::no=1;
int main(int argc, char *argv[]){
ACE_DEBUG((LM_DEBUG,"Starting test \n"));
//Instantiating the handler
My_Handler handler;
//The done flag is set to not done yet.
int done=0;
while(1){
//After WAIT_TIME the handle_events will fall through if no events
//arrive.
ACE_Reactor::instance()->handle_events(ACE_Time_Value(WAIT_TIME));
if(!done){
handler.perform_notifications();
done=1;
}
sleep(SLEEP_TIME);
}
}
In the above example, a concrete handler is created as usual and the handle_input()
method is overload, as it would be if we were expecting our handler to handle input data
from an I/O device. The handler also contains an open() method, which performs
initialization for the handler, and a method which actually performs the notifications.
In the main() function, we first instantiate an instance of our concrete handler. The
constructor of the handler insures that the number of max_notify_iterations is set to 5 by
using the max_notify_iterations() method of the reactor. After this, the reactor’s event
handling loop is started.
One major difference in the event-handling loop to be noted here is that handle_events()
is passed an ACE_Time_Value. If no events occur within this time, then the
handle_events() method will fall through. After handle_events() falls through,
perform_notifications() is called, which uses the reactor’s notify() method to request it to
notify the handler that is passed in as an argument of the occurrence of an event. The
reactor will then proceed to use the mask that it is passed to perform an upcall on the
appropriate “handle” method of the handler. In this case, we use notify() to inform our
event handler of input by passing it the ACE_Event_Handler::READ_MASK. This causes
the reactor to call back the handle_input() method of the handler.
Since we have set the max_notify_iterations to 5 therefore only 5 of the notifications will
actually be issued by the reactor during one call to handle_events(). To make this clear,
we stop the reactive event loop for SLEEP_TIME before issuing the next call to
handle_events().
The above example is overly simplistic and very non-realistic, since the notifications
occur in the same thread as the reactor. A more realistic example would be of events
which occur in another thread and which then notify the reactor thread of these events.
The same example with a different thread to perform the notifications is shown below:
Example 5 #
#include "ace/Reactor.h"
#include "ace/Event_Handler.h"
#include "ace/Synch_T.h"
#include "ace/Thread_Manager.h"
class My_Handler: public ACE_Event_Handler{
public:
//Start the event handling process.
My_Handler(){
ACE_DEBUG((LM_DEBUG,”Got open\n”));
activate_threads();
ACE_Reactor::instance()->max_notify_iterations(5);
return 0;
}
//Spawn a separate thread so that it notifies the reactor
void activate_threads(){
ACE_Thread_Manager::instance()->spawn((ACE_THR_FUNC)svc_start,(void*)this);
}
//Notify the Reactor 10 times.
void svc(){
for(int i=0;i<10;i++)
ACE_Reactor::instance()->
notify(this, ACE_Event_Handler::READ_MASK);
}
//The actual handler which in this case will handle the notifications
int handle_input(ACE_HANDLE){
ACE_DEBUG((LM_DEBUG, ”Got notification # %d\n”, no));
no++;
return 0;
}
//The entry point for the new thread that is to be created.
static int svc_start(void* arg);
private:
static int no;
};
//Static members
int My_Handler::no=1;
int My_Handler::svc_start(void* arg){
My_Handler *eh= (My_Handler*)arg;
eh->svc();
return -1; //de-register from the reactor
}
int main(int argc, char *argv[]){
ACE_DEBUG((LM_DEBUG,”Starting test \n”));
My_Handler handler;
while(1){
ACE_Reactor::instance()->handle_events();
sleep(3);
}
}
This example is very similar to the previous example, except for a few additional
methods to spawn a thread and then activate it in the event handler. In particular, the
constructor of the concrete handler My_Handler calls the activate method. This method
uses the ACE_Thread_Manager::spawn() method to spawn a separate thread with its
entry point as svc_start().
The svc_start() method calls perform_notifications() and the notifications are sent to the
reactor, but this time they are sent from this new thread instead of from the same thread
that the reactor resides in. Note that the entry point of the thread, svc_start(),was defined
as a static method in the function, which then called the non-static svc() method. This is a
requirement when using thread libraries, i.e. the entry point of a thread be a static
function with file scope.









![[http]](/wiki/imgs/http.png)
