8.4 The Proactive Acceptor-Connector Classes

Ru-Brd

Motivation

TCP / IP connection establishment is a two-step process:

  1. The application either binds a listening socket to a port and listens, or learns of a listening application and initiates an active connection request.

  2. The connect operation completes after OS-mediated TCP protocol exchanges open the new connection.

This two-step process is often performed using either a reactive or synchronous I/O model, as shown in Chapter 3 of C++NPv1 and in Chapter 7 of this book. However, the initiate/complete protocol of TCP connection establishment lends itself well to the proactive model. Networked applications that benefit from asynchronous I/O can therefore also benefit from asynchronous connection establishment capabilities.

OS support for asynchronous connection establishment varies. For example, Windows supports asynchronous connection establishment, whereas POSIX.4 AIO does not. It's possible, however, to emulate asynchronous connection establishment where it doesn't exist by using other OS mechanisms, such as multithreading (Sidebar 57 on page 283 discusses the ACE Proactor framework's emulation for POSIX). Since redesigning and rewriting code to encapsulate or emulate asynchronous connection establishment for each project or platform is tedious and error prone, the ACE Proactor framework provides the ACE_Asynch_Acceptor , ACE_Asynch_Connector , and ACE_Service_Handler classes.

Class Capabilities

ACE_Asynch_Acceptor is another implementation of the acceptor role in the Acceptor-Connector pattern [POSA2]. This class provides the following capabilities:

  • It initiates asynchronous passive connection establishment.

  • It acts as a factory, creating a new service handler for each accepted connection.

  • It can cancel a previously initiated asynchronous accept() operation.

  • It provides a hook method to obtain the peer's address when the new connection is established.

  • It provides a hook method to validate the peer before initializing the new service handler.

ACE_Asynch_Connector plays the connector role in the ACE Proactor framework's implementation of the Acceptor-Connector pattern. This class provides the following capabilities:

  • It initiates asynchronous active connection establishment.

  • It acts as a factory, creating a new service handler for each completed connection.

  • It can cancel a previously initiated asynchronous connect() operation.

  • It provides a hook method to obtain the peer's address when the new connection is established.

  • It provides a hook method to validate the peer before initializing the new service handler.

Unlike the ACE Acceptor-Connector framework described in Chapter 7, these two classes only establish TCP / IP connections. As discussed in Section 8.2, the ACE Proactor framework focuses on encapsulating operations, not I/O handles, and these classes encapsulate operations to establish TCP / IP connections. Connectionless IPC mechanisms (for example, UDP and file I/O) don't require a connection setup, so they can be used directly with the ACE Proactor framework's I/O factory classes.

Similar to ACE_Acceptor and ACE_Connector , ACE_Asynch_Acceptor and ACE_Asynch_Connector are template class factories that can create a service handler to execute a service on the new connection. The template parameter for both ACE_Asynch_Acceptor and ACE_Asynch_Connector is the service class the factory generates, known as ACE_Service_Handler . This class acts as the target of connection completions from ACE_Asynch_Acceptor and ACE_Asynch_Connector .

ACE_Service_Handler provides the following capabilities:

  • It provides the basis for initializing and implementing a networked application service, acting as the target of the ACE_Asynch_Connector and ACE_Asynch_Acceptor connection factories.

  • It receives the connected peer's address, which is important on Windows since this address isn't available after an asynchronous connection completes.

  • It inherits the ability to handle asynchronous completion events since it derives from ACE_Handler .

Sidebar 56 (page 280) discusses the rationale behind the decision to not reuse ACE_Svc_Handler for the ACE Proactor framework.

The interfaces for all three of the classes in the Proactive Acceptor-Connector mechanism are shown in Figure 8.7 (page 281). The following table outlines the key methods in the ACE_Asynch_Acceptor class:

Figure 8.7. The Proactive Acceptor, Connector, and Service Handler Classes

Method

Description

open()

Initialize and issue one or more asynchronous accept() operations.

cancel()

Cancels all asynchronous accept() operations initiated by the acceptor.

validate_connection()

Hook method to validate the peer address before opening a service for the new connection.

make_handler()

Hook method to obtain a service handler object for the new connection.

Sidebar 56: ACE_Service ”uscoreHandler versus ACE_Svc_Handler

The ACE_Service_Handler class plays a role analogous to that of the ACE Acceptor-Connector framework's ACE_Svc_Handler class covered in Section 7.2. Although the ACE Proactor framework could have reused ACE_Svc_Handler as the target of ACE_Asynch_Acceptor and ACE_Asynch_Connector , a separate class was chosen for the following reasons:

  • Networked applications that use proactive connection establishment also often use proactive I/O. The target of asynchronous connection completions should therefore be a class that can participate seamlessly with the rest of the ACE Proactor framework.

  • ACE_Svc_Handler encapsulates an IPC object. The ACE Proactor framework uses I/O handles internally, so the additional IPC object could be confusing.

  • ACE_Svc_Handler is designed for use with the ACE Reactor framework since it descends from ACE_Event_Handler . ACE maintains separation in its frameworks to avoid unnecessary coupling and faciliate ACE toolkit subsets .

For use cases in which an ACE_Service_Handler benefits by using the ACE Task framework, a common design is to add ACE_Task as a base class to the service class derived from ACE_Service_Handler . ACE_Svc_Handler can also be used since it's derived from ACE_Task , but use of ACE_Task is more common. In fact, this use case is illustrated in the AIO_Output_Handler class (page 266).

The open() method initializes the listening TCP socket and initiates one or more asynchronous accept() operations. If the argument for reissue_accept is 1 (the default), a new accept() operation will automatically be started as needed.

ACE_Asynch_Acceptor implements the ACE_Handler::handle_accept() method (Figure 8.5 on page 272) to process each accept() completion as follows

  • Collect the ACE_INET_Addr representing each endpoint of the new connection.

  • If the validate_new_connection parameter to open() was 1, invoke the validate_connection() method, passing the connected peer's address. If validate_connection() returns -1, the connection is aborted.

  • Call the make_handler() hook method to obtain the service handler for the new connection. The default implementation uses operator new to allocate a new handler dynamically.

  • Set the new handler's ACE_Proactor pointer.

  • If the pass_address parameter to open() was 1, call the ACE_Service_Handler::addresses() method with both the local and peer addresses.

  • Set the new connection's I/O handle and call the new service handler's open() method.

The ACE_Asynch_Connector class provides methods that are similar to those in ACE_Asynch_Acceptor and are outlined in the following table:

Method

Description

open()

Initializes information for the active connection factory

connect()

Initiates an asynchronous connect() operation

cancel()

Cancels all asynchronous connect() operations

validate_connection()

Hook method to learn the connect disposition and validate the peer address before opening a service for the new connection

make_handler()

Hook method to obtain a service handler object for the new connection.

The open() method accepts fewer arguments than ACE_Asynch_Acceptor::open() . In particular, since addressing information can be different on each connect() operation, it's specified in parameters to the connect() method.

ACE_Asynch_Connector implements ACE_Handler::handle_connect() (Figure 8.5 on page 272) to process each connection completion. The processing steps are the same as for ACE_Asynch_Acceptor , above.

Each networked application service class in the ACE Proactor framework derives from ACE_Service_Handler . Its key methods are shown in the following table:

Method

Description

open()

Hook method called to initialize the service after establishing a new connection

addresses()

Hook method to capture the local and remote addresses for the service connection

As mentioned above, ACE_Asynch_Acceptor and ACE_Asynch_Connector both call the ACE_Service_Handler::open() hook method for each new connection established. The handle argument is the handle for the connected socket. The ACE_Message_Block argument may contain data from the peer if the bytes_to_read parameter to ACE_Asynch_Acceptor::open() was greater than 0. Since this Windows-specific facility is often used with non-IP protocols (e.g., X.25), we don't discuss its use here. The ACE Proactor framework manages the ACE_Message_Block , so the service need not be concerned with it.

If the service handler requires either the local or peer addresses on the new connection, it must implement the addresses() hook method to capture them when the connection is established. The ACE Proactor framework calls this method if the pass_address argument to the asynchronous connection factory was 1. This method is more significant on Windows because the connection addresses cannot be obtained any other way when asynchronous connection establishment is used.

Sidebar 57: Emulating Asynchronous Connections on POSIX

Windows has native capability for asynchronously connecting sockets. In contrast, the POSIX.4 AIO facility was designed primarily for use with disk I/O, so it doesn't include any capability for asynchronous TCP / IP connection establishment. To provide uniform capability across all asynchronous I/O-enabled platforms, ACE emulates asynchronous connection establishment where needed.

To emulate asynchronous connection establishment, active and passive connection requests are begun in nonblocking mode by the ACE_Asynch_Acceptor and ACE_Asynch_Connector . If the connection doesn't complete immediately (which is always the case for passive connections), the socket handle is registered with an instance of ACE_Select_Reactor managed privately by the framework. An ACE Proactor framework-spawned thread (unseen by the application) runs the private reactor's event loop. When the connection request completes, the framework regains control via a reactor callback and posts the completion event. The original application thread receives the completion event back in the ACE_Asynch_Acceptor or ACE_Asynch_Connector class, as appropriate.

Example

As with the client logging daemons in Chapters 6 and 7, the classes in the proactive implementation are separated into separate input and output roles that are explained below.

Input role. The input role of the proactive client logging daemon is performed by the AIO_CLD_Acceptor and AIO_Input_Handler classes. AIO_Input_Handler was described on page 273, so here we focus on AIO_CLD_Acceptor , which derives from ACE_Asynch_Acceptor as shown in Figure 8.3 (page 267). The class definition for AIO_CLD_Acceptor is shown below:

 class AIO_CLD_Acceptor    : public ACE_Asynch_Acceptor<AIO_Input_Handler> {  public:    // Cancel accept and close all clients.    void close (void);    // Remove handler from client set.    void remove (AIO_Input_Handler *ih)    { clients_.remove (ih); }  protected:    // Service handler factory method.    virtual AIO_Input_Handler *make_handler (void);    // Set of all connected clients.    ACE_Unbounded_Set<AIO_Input_Handler *> clients_;  }; 

Since the ACE Proactor framework only keeps track of active I/O operations, it doesn't maintain a set of registered handlers like the ACE Reactor framework does. Applications must therefore locate and clean up handlers when necessary. In this chapter's example, the AIO_Input_Handler objects are allocated dynamically, and they must be readily accessible when the service shuts down. To satisfy this requirement, the AIO_CLD_Acceptor::clients_ member is an ACE_Unbounded_Set that holds pointers to all active AIO_Input_Handler objects. When a logging client connects to this server, ACE_Asynch_Acceptor::handle_accept() calls the following factory method:

 AIO_Input_Handler * AIO_CLD_Acceptor::make_handler (void) {    AIO_Input_Handler *ih;    ACE_NEW_RETURN (ih, AIO_Input_Handler (this), 0);    if (clients_.insert (ih) == -1) { delete ih; return 0; }    return ih;  } 

AIO_CLD_Acceptor reimplements the make_handler() factory method that keeps track of each allocated service handler's pointer in clients_ . If the new handler's pointer can't be inserted for some reason, it's deleted; returning 0 will force the ACE Proactor framework to close the newly accepted connection.

The make_handler() hook method passes its object pointer to each dynamically allocated AIO_Input_Handler (page 273). When AIO_Input_Handler detects a failed read() (most likely because the logging client closed the connection), its handle_read_stream() method (page 274) simply deletes itself. The AIO_Input_Handler destructor cleans up all held resources, and calls the AIO_CLD_Acceptor::remove() method (page 283) to remove itself from the clients_ set, as shown below:

 AIO_Input_Handler::AIO_Input_Handler () {    reader_.cancel ();    ACE_OS::closesocket (handle ());    if (mblk_ != 0) mblk_->release ();    mblk_ = 0;    acceptor_->remove (this);  } 

When this service shuts down in the AIO_Client_Logging_Daemon::svc() method (page 295), all the remaining AIO_Input_Handler connections and objects are cleaned up by calling the close() method below:

 void AIO_CLD_Acceptor::close (void) {    ACE_Unbounded_Set_Iterator<AIO_Input_Handler *>      iter (clients_.begin ());    AIO_Input_Handler **ih;    while (iter.next (ih)) delete *ih;  } 

This method simply iterates through all of the active AIO_Input_Handler objects, deleting each one.

Output role. The output role of the proactive client logging daemon is performed by the AIO_CLD_Connector and AIO_Output_Handler classes. The client logging daemon uses the AIO_CLD_Connector to

  • Establish (and, when necessary, reestablish) a TCP connection to a server logging daemon

  • Implement the connector half of the SSL authentication with the server logging daemon (the server logging daemon's SSL authentication is shown on page 224)

It then uses the AIO_Output_Handler to asychronously forward log records from connected logging clients to the server logging daemon.

Part of the AIO_CLD_Connector class definition is below:

 class AIO_CLD_Connector    : public ACE_Asynch_Connector<AIO_Output_Handler> {  public:    enum { INITIAL_RETRY_DELAY = 3, MAX_RETRY_DELAY = 60 };    // Constructor.    AIO_CLD_Connector ()      : retry_delay_ (INITIAL_RETRY_DELAY), ssl_ctx_ (0), ssl_ (0)    { open (); }    // Hook method to detect failure and validate peer before    // opening handler.    virtual int validate_connection      (const ACE_Asynch_Connect::Result &result,       const ACE_INET_Addr &remote, const ACE_INET_Addr &local);  protected:    // Template method to create a new handler    virtual AIO_Output_Handler *make_handler (void)    { return OUTPUT_HANDLER::instance (); }    // Address at which logging server listens for connections.    ACE_INET_Addr remote_addr_;    // Seconds to wait before trying the next connect    int retry_delay_;    // The SSL "context" data structure.    SSL_CTX *ssl_ctx_;    // The SSL data structure corresponding to authenticated    // SSL connections.    SSL *ssl_;  };  typedef ACE_Unmanaged_Singleton<AIO_CLD_Connector, ACE_Null_Mutex>          CLD_CONNECTOR; 

The AIO_CLD_Connector class is accessed as an unmanaged singleton (see Sidebar 45 on page 194) via the CLD_CONNECTOR typedef . When AIO_CLD_Connector is instantiated , its constructor calls the ACE_Asynch_Connector::open() method. By default, the validate_connection() method (page 293) will be called on completion of each connect() attempt.

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