3.3 The ACE_Event_Handler Class

Ru-Brd

Motivation

Networked applications are often based on a reactive model, in which they respond to various types of events, such as I/O activity, expired timers, or signals. Application-independent mechanisms that detect events and dispatch them to event-handling code should be reused across applications, while application-defined code that responds to the events should reside in the event handlers. To reduce coupling and increase reuse, a framework separates the reuseable mechanisms and provides the means to incorporate application-defined event handlers. This separation of concerns is the basis of the ACE Reactor framework's inversion of control. Its event detection and dispatching mechanisms control execution flow and invoke event-handling callback hook methods when there's application processing to perform.

Sidebar 8: The ACE_Get_Opt Class

ACE_Get_Opt is an iterator for parsing options from command-line arguments. Options passed in an optstring are preceded by ' - ' for short options or ' -- ' for long options. ACE_Get_Opt can be used to parse argc / argv arguments, such as those passed as a program's main() command line or to an init() hook method. This class provides the following capabilities:

  • A thin C++ wrapper facade for the standard POSIX getopt() function. Unlike getopt() , however, each instance of ACE_Get_Opt maintains its own state, so it can be used reentrantly. ACE_Get_Opt is also easier to use than getopt() since the optstring and argc / argv arguments are only passed once to its constructor, rather than to each iterator call.

  • It can be told to start processing the command line at an arbitrary point specified by the skip_args parameter, which allows it to skip the program name when parsing a command line passed to main() or continue processing where it left off at a later time.

  • It can regroup all the option arguments at the beginning of the command line, while maintaining their relative order, which simplifies option and nonoption argument processing. After all the options are scanned, it returns EOF and opt_ind() points to the first nonoption argument, so the program can continue processing the remaining arguments.

  • Multiple argument ordering modes: PERMUTE _ ARGS , REQUIRE _ ORDER , and RETURN _ IN _ ORDER .

  • A colon following a short option character in the optstring signifies that the option takes an argument. The argument is taken from the remaining characters of the current argv -element or the next argv -element as needed. If an argv -element of ' -- ' is encountered , this signifies the end of the option section and EOF is returned.

  • Short options that don't take arguments can be grouped together on the command line after the leading ' - ', but in that case, only the last short option in the group can take an argument. A '?' is returned if the short option is not recognized.

  • Long option formats are similar to the GNU getopt_long() function. Long options can be defined with corresponding short options. When ACE_Get_Opt finds a long option with a corresponding short option, it returns the short option making it much easier for the caller to handle it in a switch statement. Many examples in the book illustrate the use of both long and short options.

  • Since short options are defined as integers, long options that wouldn't normally have a meaningful short option equivalent can designate nonalphanumeric values for the corresponding short option. These nonalphanumeric cannot appear in the argument list or in the optstring parameter, but can be returned and processed efficiently in a switch statement.

Figure 3.3 The ACE_Event_Handler Class
  ACE_Event_Handler  - priority_ : int  - reactor_ : ACE_Reactor *  # ACE_Event_Handler (r : ACE_Reactor * = 0,                       prio : int = LO_PRIORITY)  +  ~ACE_Event_Handler ()  +  handle_input (h : ACE_HANDLE = ACE_INVALID_HANDLE) : int  +  handle_output (h : ACE_HANDLE = ACE_INVALID_HANDLE) : int  +  handle_exception (h : ACE_HANDLE = ACE_INVALID_HANDLE) : int  +  handle_timeout (now : ACE_Time_Value &, act : void * = 0) : int  +  handle_signal (signum : int, info : siginfo_t * = 0,   ctx : ucontext_t * = 0) : int  +  handle_close (h : ACE_HANDLE, mask : ACE_Reactor_Mask) : int  +  get_handle () : ACE_HANDLE  +  reactor () : ACE_Reactor *  +  reactor (r : ACE_Reactor *)  +  priority () : int  +  priority (prio : int)  

To maintain this separation of concerns, there must be a way to invoke callbacks. One way to implement callbacks is to define a separate function for each type of event. This approach can become unwieldy for application developers, however, since they must:

  1. Keep track of which functions correspond to which events

  2. Design a way to associate data with callback functions

  3. Use a procedural model of programming since there's no object interface involved

To resolve these problems and support an object-oriented callback model, the ACE Reactor framework defines the ACE_Event_Handler base class.

Class Capabilities

ACE_Event_Handler is the base class of all reactive event handlers in ACE. This class provides the following capabilities:

  • It defines hook methods for input events, output events, exception events, timer events, and signal events. [1]

    [1] On Windows, an ACE_Event_Handler can also handle synchronization events, such as transitioning from the nonsignaled to signaled state with Windows event objects, mutexes , or semaphores [SR00], which we discuss in Section 4.4.

  • Its hook methods allow applications to extend event handler subclasses in many ways without changing the framework.

  • Its use of object-oriented callbacks simplifies the association of data with hook methods that manipulate the data.

  • Its use of objects also automates the binding of an event source (or set of sources) with data the event source is associated with, such as a network session.

  • It centralizes how event handlers can be destroyed when they're not needed.

  • It holds a pointer to the ACE_Reactor that manages it, making it simple for an event handler to manage its event (de)registration correctly.

The interface for ACE_Event_Handler is shown in Figure 3.3 and its key methods are outlined in the following table:

Method

Description

ACE_Event_Handler()

Assigns the ACE_Reactor pointer that can be associated with an event handler.

ACE_Event_Handler()

Calls purge_pending_notifications() (page 77) to remove itself from the reactor's notification mechanism.

handle_input()

Hook method called when input events occur, for example, connection or data events.

handle_output()

Hook method called when output events are possible, for example, when flow control abates or a nonblocking connection completes.

handle_exception()

Hook method called when an exceptional event occurs, for example, the arrival of TCP urgent data.

handle_timeout()

Hook method called when a timer expires .

handle_signal()

Hook method called when signaled by the OS, either via POSIX signals or when a Windows synchronization object transitions to the signaled state.

handle_close()

Hook method that performs user -defined termination activities when one of the other handle_*() hook methods returns -1 or when ACE_Reactor::remove_handler() is called explicitly to unregister an event handler (page 73).

get_handle()

Returns the underlying I/O handle. This method can be left as a no-op if an event handler only handles time-driven events.

reactor()

Accessors to get/set the ACE_Reactor pointer that can be associated with an ACE_Event_Handler .

priority()

Accessors to get/set the priority of the event handler, as used by the ACE_Priority_Reactor (mentioned in Chapter 4).

Applications can inherit from ACE_Event_Handler to create event handlers that have the following properties:

  • They override one or more of the ACE_Event_Handler class's handle_*() hook methods to perform application-defined processing in response to the corresponding types of events.

  • They are registered or scheduled with an ACE_Reactor , which then dispatches hook methods on the handlers to process events that occur.

  • Since event handlers are objects, not functions, it's straightforward to associate data with a handler's hook methods to hold state across multiple reactor callbacks.

Below, we discuss three aspects of programming ACE_Event_Handler hook methods.

1. Types of events and event handler hook methods. When an application registers an event handler with a reactor, it must indicate what type(s) of event(s) the event handler should process. ACE designates these event types via the following enumerators defined in ACE_Event_Handler :

Event Type

Description

READ_MASK

Indicates input events, such as data on a socket or file handle. A reactor dispatches the handle_input() hook method to process input events.

WRITE_MASK

Indicates output events, such as when flow control abates. A reactor dispatches the handle_output() hook method to process output events.

EXCEPT_MASK

Indicates exceptional events, such as urgent data on a socket. A reactor dispatches the handle_exception() hook method to process exceptional events.

ACCEPT_MASK

Indicates passive-mode connection events. A reactor dispatches the handle_input() hook method to process connection events.

CONNECT_MASK

Indicates a nonblocking connection completion. A reactor dispatches the handle_output() hook method to process nonblocking connection completion events.

These values can be combined ("or'd" together) to efficiently designate a set of events. This set of events can populate the ACE_Reactor_Mask parameter that's passed to the ACE_Reactor::register_handler() methods (page 73).

Event handlers used for I/O events can provide a handle, such as a socket handle, via their get_handle() hook method. When an application registers an event handler with a reactor, the reactor calls back to the handler's get_handle() method to retrieve its handle. This handle is then included in the handle set a reactor uses to detect I/O events.

2. Event handler hook method return values. When registered events occur, the reactor dispatches the appropriate event handler's handle_*() hook methods to process them. Sidebar 9 describes some idioms to apply when implementing these hook methods. When a handle_*() method finishes its processing, it must return a value that's interpreted by the reactor as follows :

  • Return value 0 indicates that the reactor should continue to detect and dispatch the registered event for this event handler (and handle if it's an I/O event). This behavior is common for event handlers that process multiple instances of an event, for example, reading data from a socket as it becomes available.

    Sidebar 9: Idioms for Designing ACE Event Handlers

    The following are some idioms for designing event handlers for use with the ACE Reactor framework:

    • To prevent starvation , keep the execution time of an event handler's handle_*() hook methods short, ideally shorter than the average interval between event occurrences. If a hook method may run for a long time processing a request, consider queueing the request in an ACE_Message_Queue and processing it later. The Example portion of Section 6.3 illustrates this approach by combining the ACE Task framework with the ACE Reactor framework to implement a concurrent logging server based on the Half-Sync/Half-Async pattern.

    • Consolidate an event handler's cleanup activities in its handle_close() hook method, rather than dispersing them throughout its other methods. This idiom is particularly important when dealing with dynamically allocated event handlers that are deallocated via delete this , because it's easier to check whether there are potential problems with deleting nondynamically allocated memory.

    • Only call delete this in an event handler's handle_close() method, and only after the handler's final registered event has been removed from the reactor. This idiom avoids dangling pointers that can otherwise occur if an event handler that registered with a reactor for multiple events is deleted prematurely. Sidebar 10 (page 53) illustrates one way to keep track of this information.

  • Return value greater than 0 also indicates that the reactor should continue to detect and dispatch the registered event for this event handler. Additionally, if a value > 0 is returned after processing an I/O event, the reactor will dispatch this event handler on the handle again before the reactor blocks on its event demultiplexer . This feature enhances overall system fairness for cooperative I/O event handlers by allowing one event handler to perform a limited amount of computation, then relinquish control to allow other event handlers to be dispatched before it regains control again.

  • Return value -1 instructs the reactor to stop detecting the registered event for this event handler (and handle if it's an I/O event). Before the reactor removes this event handler/handle from its internal tables, it invokes the handler's handle_close() hook method, passing it the ACE_Reactor_Mask value of the event that's now unregistered. This event handler may remain registered for other events on the same, or a different, handle; it's the handler's responsibility to track which event(s) it's still registered for, as shown in Sidebar 10 (page 53).

3. Cleaning up an event handler. An event handler's handle_close() method is called when one of its other hook methods decides that cleanup is required. The handle_close() method can then perform user-defined shutdown activities, such as releasing memory allocated by the object, closing IPC objects or log files, etc. The ACE Reactor framework ignores the return value of the handle_close() method itself.

ACE_Reactor only calls handle_close() when a hook method returns a negative value, as described above, or when a handler is removed from the reactor explicitly (page 74). It will not call handle_close() automatically when an IPC mechanism reaches end-of-file or an I/O handle is closed by either the local application or a remote peer. Applications must therefore detect when an I/O handle has closed and take steps to ensure an ACE_Reactor calls handle_close() . For example, when a recv() or read() call returns 0, the event handler should return -1 from the handle_*() method or call the ACE_Reactor::remove_handler() method (page 74).

In addition to the event types shown in the table on page 50, a reactor can pass the following enumerators defined in ACE_Event_Handler to handle_close() :

Event Type

Description

TIMER_MASK

Indicates time-driven events and is passed by a reactor when a handle_timeout() hook method returns -1.

SIGNAL_MASK

Indicates signal-based events (or handle-based events on Windows) and is passed by a reactor when a handle_signal() hook method returns -1. ACE's signal-handling facilities are described in [HJS].

Example

We implement our logging server by inheriting from ACE_Event_Handler and driving its processing via the ACE_Reactor 's event loop. We handle two types of events:

  1. Data events, which indicate the arrival of log records from connected client logging daemons

  2. Accept events, which indicate the arrival of new connections from client logging daemons

We therefore define two types of event handlers in our logging server:

  1. Logging_Event_Handler ” This class processes log records received from a connected client logging daemon. It uses the ACE_SOCK_Stream class from Chapter 3 in C++NPv1 to read log records from a connection.

  2. Logging_Acceptor ” This class is a factory that allocates a Logging_Event_Handler dynamically and initializes it when a client logging daemon connects. It uses the ACE_SOCK_Acceptor class from Chapter 3 of C++NPv1 to initialize the ACE_SOCK_Stream contained in Logging_Event_Handler .

Both event handlers inherit from ACE_Event_Handler , which enables a reactor to dispatch their handle_input() methods. The relationship between ACE_Reactor , ACE_Event_Handler , Logging_Acceptor , and Logging_Event_Handler is shown in Figure 3.4.

Figure 3.4. ACE_Reactor -based Logging Server Classes

Sidebar 10: Tracking Dynamic Event Handler Event Registrations

Applications are responsible for determining when a dynamically allocated event handler can be deleted. For example, the following class shows an idiom in which an event handler keeps track of when all events it's registered for have been removed from its associated reactor.

 class My_Event_Handler : public ACE_Event_Handler {  private:    // Keep track of the events the handler's registered for.    ACE_Reactor_Mask mask_;  public:    // ... class methods shown below ... 

The class constructor initializes the mask_ data member for READ and WRITE events and then registers this object with its reactor parameter to handle both types of events, as follows:

 My_Event_Handler (ACE_Reactor *r): ACE_Event_Handler (r) {    ACE_SET_BITS (mask_,                  ACE_Event_Handler::READ_MASK                   ACE_Event_Handler::WRITE_MASK);    reactor ()->register_handler (this, mask_);  } 

The handle_input() and handle_output() methods must return -1 when they're finished processing READ and WRITE events, respectively. Each time a handle_*() method returns -1 the reactor dispatches the handle_close() hook method, passing it the ACE_Reactor_Mask value of the event that's being unregistered. This hook method clears the corresponding bit from mask_ , as follows:

 virtual int handle_close (ACE_HANDLE, ACE_Reactor_Mask mask) {    if (mask == ACE_Event_Handler::READ_MASK) {      ACE_CLR_BITS (mask_, ACE_Event_Handler::READ_MASK);      // Perform READ_MASK cleanup logic...    }    if (mask == ACE_Event_Handler::WRITE_MASK) {      ACE_CLR_BITS (mask_, ACE_Event_Handler::WRITE_MASK);      // Perform WRITE_MASK cleanup logic.    }    if (mask_ == 0) delete this;    return 0;  } 

Only when mask_ is zero does handle_close() call delete this .

We start by creating the Logging_Acceptor.h file that includes the necessary headers:

 #include "ace/Event_Handler.h"  #include "ace/INET_Addr.h"  #include "ace/Log_Record.h"  #include "ace/Reactor.h"  #include "ace/SOCK_Acceptor.h"  #include "ace/SOCK_Stream.h"  #include "Logging_Handler.h" 

All but one of these headers are defined in ACE. The exception is Logging_Handler.h , which contains the Logging_Handler class defined in Chapter 4 of C++NPv1.

The Logging_Acceptor class inherits from ACE_Event_Handler and defines a private instance of the ACE_SOCK_Acceptor factory, as shown below:

 class Logging_Acceptor : public ACE_Event_Handler {  private:    // Factory that connects <ACE_SOCK_Stream>s passively.    ACE_SOCK_Acceptor acceptor_;  protected:    virtual Logging_Acceptor () {} // No-op destructor. 

We declare the no-op destructor in the protected access control section to ensure dynamic allocation of Logging_Acceptor . Sidebar 11 explains why event handlers should generally be allocated dynamically.

Sidebar 11: Strategies for Managing Event Handler Memory

Event handlers should generally be allocated dynamically for the following reasons:

  • Simplify memory management. For example, deallocation can be localized in an event handler's handle_close() method, using the event handler event registration tracking idiom shown in Sidebar 10 (page 53).

  • Avoid "dangling handler" problems. For example, the lifecycle of an event handler instantiated on the stack or as a member of another class is controlled externally while its reactor registrations are controlled internally. If the handler is destroyed while it's still registered with a reactor, there will be unpredictable problems later if the reactor tries to dispatch the nonexistent handler.

  • Avoid portability problems. For example, dynamic allocation alleviates subtle problems stemming from the delayed event handler cleanup semantics of the ACE_WFMO_Reactor (page 107).

Certain types of applications, such as real-time systems, avoid or minimize the use of dynamic memory to improve their predictability. If you must allocate event handlers statically for such applications, here are some conventions to follow:

  1. Don't call delete this in handle_close() .

  2. Unregister all events from reactors in the class destructor, at the latest.

  3. Ensure that the lifetime of a registered event handler is longer than the reactor it's registered with if it can't be unregistered for some reason.

  4. Avoid the use of the ACE_WFMO_Reactor since it defers the removal of event handlers, thereby making it hard to enforce convention 3.

  5. If using ACE_WFMO_Reactor , pass the DONT_CALL flag to ACE_Reactor::remove_handler() and carefully manage shutdown activities without the benefit of the reactor's handle_close() callback.

We next show the interface and portions of the Logging_Acceptor method implementations . Although we don't show much error handling code in this example, a production implementation (such as the logging server in the ACE network service components library) should take appropriate corrective action if failures occur.

 public:    // Simple constructor.    Logging_Acceptor (ACE_Reactor *r = ACE_Reactor::instance ())      : ACE_Event_Handler (r) {}    // Initialization method.    virtual int open (const ACE_INET_Addr &local_addr);    // Called by a reactor when there's a new connection to accept.    virtual int handle_input (ACE_HANDLE = ACE_INVALID_HANDLE);    // Called when this object is destroyed, e.g., when it's    // removed from a reactor.    virtual int handle_close (ACE_HANDLE = ACE_INVALID_HANDLE,                              ACE_Reactor_Mask = 0);    // Return the passive-mode socket's I/O handle.    virtual ACE_HANDLE get_handle () const      { return acceptor_.get_handle (); }    // Returns a reference to the underlying <acceptor_>.    ACE_SOCK_Acceptor &acceptor () { return acceptor_; }  }; 

The Logging_Acceptor::open() method initializes a passive-mode acceptor socket to listen for connections at local_addr . The Logging_Acceptor then registers itself with the reactor to handle ACCEPT events.

 int Logging_Acceptor::open (const ACE_INET_Addr &local_addr) {    if (acceptor_.open (local_addr) == -1) return -1;    return reactor ()->register_handler             (this, ACE_Event_Handler::ACCEPT_MASK);  } 

Since the passive-mode socket in the ACE_SOCK_Acceptor becomes active when a new connection can be accepted, the reactor dispatches the Logging_Acceptor::handle_input() method automatically. We'll show this method's implementation on page 58 after first defining the following Logging_Event_Handler class:

 class Logging_Event_Handler : public ACE_Event_Handler  {  protected:    // File where log records are written.    ACE_FILE_IO log_file_;    // Connection to remote peer.    Logging_Handler logging_handler_; 

This class inherits from ACE_Event_Handler and adapts the Logging_Handler defined in Chapter 4 of C++NPv1 for use with the ACE Reactor framework. In addition to a Logging_Handler , each Logging_Event_Handler contains an ACE_FILE_IO object to keep a separate log file for each connected client.

The public methods in the Logging_Event_Handler class are shown below.

 public:    // Initialize the base class and logging handler.    Logging_Event_Handler (ACE_Reactor *reactor)      : ACE_Event_Handler (reactor),      logging_handler_ (log_file_) {}    virtual Logging_Event_Handler () {} // No-op destructor.    virtual int open (); // Activate the object.    // Called by a reactor when logging events arrive.    virtual int handle_input (ACE_HANDLE = ACE_INVALID_HANDLE);    // Called when this object is destroyed, e.g., when it's    // removed from a reactor.    virtual int handle_close (ACE_HANDLE = ACE_INVALID_HANDLE,                              ACE_Reactor_Mask = 0);    // Return socket handle of the contained <Logging_Handler>.    virtual ACE_HANDLE get_handle () const;    // Get a reference to the contained <ACE_SOCK_Stream>.    ACE_SOCK_Stream &peer () { return logging_handler_.peer (); }    // Return a reference to the <log_file_>.    ACE_FILE_IO &log_file () const { return log_file_; }  }; 

The Logging_Event_Handler::get_handle() method is defined as follows:

 ACE_HANDLE Logging_Event_Handler::get_handle (void) const {    Logging_Handler &h =        ACE_const_cast (Logging_Handler &, logging_handler_);    return h.peer ().get_handle ();  } 

Since get_handle() is a const method, we use the ACE_const_cast macro to call the nonconst Logging_Handler::peer() method. This is safe since we call the const get_handle() method using it. Sidebar 17 (page 176) of C++NPv1 explains the various macros ACE provides to support portable casting on all C++ compilers. You don't need to use these macros if your applications use only compilers that support the standard C++ cast operators.

Now that we've outlined Logging_Event_Handler , we'll implement Logging_Acceptor::handle_input() , which is dispatched by a reactor whenever a new connection can be accepted. This factory method creates, connects, and activates a Logging_Event_Handler , as shown below:

 1 int Logging_Acceptor::handle_input (ACE_HANDLE) {   2   Logging_Event_Handler *peer_handler = 0;   3   ACE_NEW_RETURN (peer_handler,   4                   Logging_Event_Handler (reactor ()), -1);   5   if (acceptor_.accept (peer_handler->peer ()) == -1) {   6     delete peer_handler;   7     return -1;   8   } else if (peer_handler->open () == -1) {   9     peer_handler->handle_close ();  10     return -1;  11   }  12   return 0;  13 } 

Lines 2 “4 Create a new Logging_Event_Handler that will process the new client's logging session. Sidebar 12 (page 60) describes the ACE _ NEW _ RETURN macro and other ACE memory management macros. The new Logging_Event_Handler receives this object's ACE_Reactor pointer, which ensures that the new handler registers with the reactor that dispatched this hook method, thereby joining the logging server's event loop.

Lines 5 “7 Accept the new connection into the socket handle of the Logging_Event_Handler , deleting the peer_handler and returning -1 if an error occurs. As discussed on page 51, when -1 is returned from an event handler's handle_input() the reactor will automatically invoke the handler's handle_close() hook method, which is defined as follows for the Logging_Acceptor :

 int Logging_Acceptor::handle_close (ACE_HANDLE,                                      ACE_Reactor_Mask) {    acceptor_.close ();    delete this;    return 0;  } 

Since we always use our Logging_Acceptor class in circumstances that require it to delete itself, we allocate it dynamically in the various examples in the book.

Lines 8 “10 Activate the connected peer_handler by calling its open() method. If this method returns -1 we close the peer_handler , which deletes itself in Logging_Event_Handler::handle_close() (page 60). The open() method is shown on the following page.

 1 int Logging_Event_Handler::open () {   2   static const char LOGFILE_SUFFIX[] = ".log";   3   char filename[MAXHOSTNAMELEN + sizeof (LOGFILE_SUFFIX)];   4   ACE_INET_Addr logging_peer_addr;   5   6   logging_handler_.peer ().get_remote_addr (logging_peer_addr);   7   logging_peer_addr.get_host_name (filename, MAXHOSTNAMELEN);   8   ACE_OS_String::strcat (filename, LOGFILE_SUFFIX);   9  10   ACE_FILE_Connector connector;  11   connector.connect (log_file_,  12                      ACE_FILE_Addr (filename),  13                      0, // No timeout.  14                      ACE_Addr::sap_any, // Ignored.  15                      0, // Don't try to reuse the addr.  16                      O_RDWRO_CREATO_APPEND,  17                      ACE_DEFAULT_FILE_PERMS);  18  19   return reactor ()->register_handler  20            (this, ACE_Event_Handler::READ_MASK);  21 } 

Lines 3 “8 Determine the connected client's hostname and use this as the logfile's name.

Lines 10 “17 Create or open the file that stores log records from a connected client.

Lines 19 “20 Use the ACE_Reactor::register_handler() method (page 73) to register this event handler for READ events with the Logging_Acceptor 's reactor.

When log records arrive from clients , the reactor will automatically dispatch the following Logging_Event_Handler::handle_input() method:

 int Logging_Event_Handler::handle_input (ACE_HANDLE)  { return logging_handler_.log_record (); } 

This method processes a log record by calling Logging_Handler::log_record() , which reads the record from the socket and writes it to the log file associated with the client connection. Since logging_handler_ maintains its own socket handle, the handle_input() method simply ignores its ACE_HANDLE parameter.

Whenever an error occurs or a client closes a connection to the logging server, the log_record() method returns -1, which the handle_input() method then passes back to the reactor that dispatched it (Sidebar 13 on page 61 discusses strategies for handling peers that simply stop communicating). This value causes the reactor to dispatch the Logging_Event_Handler::handle_close() hook method, which closes both the socket to the client and the log file and then deletes itself, as follows:

Sidebar 12: The ACE Memory Management Macros

C++NPv1 and this book solve many problems related to differences between OS APIs. Another problem area is differences between C++ compilers. The C++ operatornew() dynamic memory allocator is a good example. Early C++ runtimes returned a NULL pointer when an allocation failed, whereas newer runtimes throw an exception. ACE defines macros that unify the behavior and return a NULL pointer regardless of the compiler's behavior. ACE uses these macros to ensure consistent, portable behavior; your applications can use them as well.

If memory allocation fails, all of the ACE memory management macros set the specified pointer to NULL and set errno to ENOMEM . The ACE _ NEW _ RETURN macro returns a specified value from the current method on failure, whereas the ACE _ NEW macro simply returns and the ACE _ NEW _ NORETURN macro continues to execute in the current method. These macros enable applications to work portably, regardless of the C++ compiler memory allocation error handling policies. For example, the ACE _ NEW _ RETURN macro is defined as follows for compilers that throw the std::bad_alloc C++ exception when new fails:

 #define ACE_NEW_RETURN(POINTER,CTOR,RET_VAL) \   do { try { POINTER = new CTOR; } catch (std::bad_alloc) \     { errno = ENOMEM; POINTER = 0; return RET_VAL; } \   } while (0) 

In contrast, ACE _ NEW _ RETURN is defined as follows for compiler configurations that offer a nothrow variant of operator new :

 #define ACE_NEW_RETURN(POINTER,CTOR,RET_VAL) \   do { POINTER = new (ACE_nothrow) CTOR; \     if (POINTER == 0) { errno = ENOMEM; return RET_VAL; } \   } while (0) 
 int Logging_Event_Handler::handle_close (ACE_HANDLE,                                           ACE_Reactor_Mask) {    logging_handler_.close ();    log_file_.close ();    delete this;    return 0;  } 

This method can safely delete this since the Logging_Event_Handler object is allocated dynamically and won't be used by the reactor or any other part of the program. The Example in Section 3.5 illustrates this method in the context of the complete Reactor framework.

Sidebar 13: Handling Silent Peers

If a client disconnects, either gracefully or abruptly, its socket will become readable. A reactor can detect this event and dispatch the handle_input() hook method on the event handler that's associated with the socket handle. The handler will then determine that the connection has closed, which is usually revealed by a recv() or read() call returning 0 or -1. If the client simply stops communicating altogether, however, there could be a number of causes, including

  • An Ethernet cable's being pulled out of its connector, which may be plugged back in shortly and the connection continues or

  • The host crashed without the opportunity to close any connections, so the local endpoint of the connection is stranded and will never continue.

In these cases there are no events for a reactor to detect.

Depending on the needs of your application services and the application-level protocol(s) used, there are several ways to handle a silent peer. These include:

  • Wait until the TCP keepalive mechanism abandons the peer and closes the connection, which will trigger an event on the socket that can be handled just as if the client closed the connection. Unfortunately, this may take a long time ” maybe hours ”as described in [SW95].

  • Implement an application-level policy or protocol, such as a "heartbeat" message or periodic "are you there?" message. If the peer fails to send a heartbeat message or answer the "are you there?" message within an application defined period of time, abort the connection unilaterally. The application may then attempt to reopen the connection at a later time.

  • Implement a policy that if the peer does not send any data for some determined amount of time, the connection will be abandoned . This type of policy is used by the Logging_Event_Handler_Ex class (page 68).

Ru-Brd


C++ Network Programming
C++ Network Programming, Volume I: Mastering Complexity with ACE and Patterns
ISBN: 0201604647
EAN: 2147483647
Year: 2002
Pages: 65

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net