5.2 The ACE_Service_Object Class

Ru-Brd

Motivation

Service configuration and life cycle management involves the following aspects that we've alluded to briefly above:

  • Initialization. A service must be initialized , which may involve creating one or more objects or invoking a factory method. Configuration parameters are passed to the service at this time.

  • Execution control. Certain applications require the ability to suspend and resume services. Offering this capability therefore requires a mechanism by which a management application can locate the desired services and then contact the services to request or force the suspend/resume operation.

  • Reporting. Mission-critical services often require the ability to respond to requests for information concerning their status and availability in a uniform way.

  • Termination. Orderly shutdown processes are required to ensure that a service's resources are released properly, any necessary status information is updated, and that service shutdown is ordered properly to avoid improper service interactions.

Designing and implementing these capabilities in an ad hoc manner often produces tightly coupled data structures and classes, which are hard to evolve and reuse in future projects.

Moreover, if multiple projects or development groups undertake similar efforts, the primary benefits of service configuration will be lost because it's highly unlikely that multiple designs will interoperate at either the service or management level.

Since service configuration and management are largely application-independent they are good candidates to incorporate into a framework. Enforcing a uniform interface across all networked services makes it easier to configure and manage them consistently. In turn , this consistency simplifies application development and deployment by mitigating key challenges inherent in creating reusable administrative configuration tools. To provide a uniform interface between the ACE Service Configurator framework and the application-defined services, each service must be a descendant of a common base class called ACE_Service_Object .

Class Capabilities

ACE_Service_Object provides a uniform interface that allows service implementations to be configured and managed by the ACE Service Configurator framework. This class provides the following capabilities:

  • It provides hook methods that initialize a service (e.g., allocating its resources) and shut a service down (e.g., cleaning up its resources).

  • It provides hook methods to suspend service execution temporarily and to resume execution of a suspended service.

  • It provides a hook method that reports key service information, such as its purpose, current status, and the port number where it listens for client connections.

These methods are generally invoked as callbacks from the ACE Service Configurator framework when it interprets the configuration directives described on page 141.

The interface for ACE_Service_Object is shown in Figure 5.2 (page 120). By inheriting from ACE_Event_Handler and ACE_Shared_Object , subclasses of ACE_Service_Object can be dispatched by the ACE Reactor framework and can be linked and unlinked from a DLL dynamically, respectively. The key configuration- related hook methods of ACE_Service_Object are outlined in the following table:

Figure 5.2. The ACE_Service_Object Class

Method

Description

init()

Used by the framework to instruct a service to initialize itself. " argc / argv "-style arguments can be passed to init() to control service initialization.

fini()

Used by the framework to instruct a service to shut itself down. This method typically performs termination operations that release a service's resources, such as memory, synchronization locks, or I/O handles.

suspend()

resume()

Used by the framework to instruct a service to suspend and resume execution.

info ()

Used to query a service for certain information about itself, such as its name , purpose, and network address. Clients can query a server to retrieve this information and use it to contact a particular service running in a server.

These hook methods collectively impose a uniform interface between the ACE Service Configurator framework and the application-defined services that it manages .

Application services that inherit from ACE_Service_Object can selectively override its hook methods, which are called back at the appropriate time by the ACE Service Configurator framework in response to specific events. For example, a service object's init() hook method is called when the Service Configurator framework executes a directive to activate the service (both the dynamic and static directives activate a service, as shown on page 141). The init() hook method must return 0 if initialization succeeds and -1 if it fails. If (and only if) init() succeeds, the corresponding fini() method will be called on the service object when the ACE Service Configurator framework executes the remove directive for the service, or shuts down all services.

The Service Configurator is the first ACE framework we've studied that has extensive interaction with administrators or applications. These interactions introduce the need to operate with local character sets. Figure 5.2 shows the ACE_TCHAR type, which helps ACE deal with non-ASCII character sets portably. ACE's facilities for handling wide-character and Unicode characters are described in Sidebar 28. We'll use this facility to handle character strings in the remainder of this book.

Example

To illustrate the ACE_Service_Object class, we reimplement our reactive logging server from the Example portion of Section 3.5. This revision can be configured dynamically by the ACE Service Configurator framework, rather than configured statically into the main() program shown on page 84. To accomplish this, we'll apply the Adapter pat-tern [GoF] to create the following template class in the Reactor_Logging_Server_Adapter.h header file:

Sidebar 28: How ACE Deals with Narrow and Wide Characters

Developers outside the United States are acutely aware that many character sets in use today require more than one byte, or octet, to represent each character. Characters that require more than one octet are referred to as "wide characters." The most popular multiple octet standard is ISO/IEC 10646, the Universal Multiple-Octet Coded Character Set (UCS). Unicode is a separate standard, but is essentially a restricted subset of UCS that uses two octets for each character (UCS-2). Many Windows programmers are familiar with Unicode.

C++ represents wide characters with the wchar_t type, which enables methods to offer multiple signatures that are differentiated by their character type. Wide characters have a separate set of C string manipulation functions, however, and existing C++ code, such as string literals, requires change for wide-character usage. As a result, programming applications to use wide-character strings can become expensive, especially when applications written initially for U.S. markets must be internationalized for other countries . To improve portability and ease of use, ACE uses C++ method overloading and the macros described below to use different character types without changing APIs:

Macro

Usage

ACE _ HAS _ WCHAR

Configuration setting to build ACE with its wide-character methods

ACE _ USES _ WCHAR

Configuration setting that directs ACE to use wide characters internally

ACE _ TCHAR

Defined as either char or wchar_t , to match ACE's internal character width

ACE _ TEXT (str)

Defines the string literal str correctly based on ACE _ USES _ WCHAR

ACE _ TEXT _ CHAR _ TO _ TCHAR (str)

Converts a char * string to ACE _ TCHAR format, if needed

ACE _ TEXT _ ALWAYS _ CHAR (str)

Converts an ACE _ TCHAR string to char * format, if needed

ACE must be built with the ACE _ HAS _ WCHAR configuration setting for applications to use wide characters. Moreover, ACE must be built with the ACE _ USES _ WCHAR setting if ACE should also use wide characters internally. The ACE _ TCHAR and ACE _ TEXT macros are illustrated in examples throughout this book.

ACE also supplies two string classes, ACE_CString and ACE_WString , which hold narrow and wide characters, respectively. These classes are analogous to the standard C++ string class, but can be configured to use custom memory allocators and are more portable. ACE_TString is a typedef for one of the two string types depending on the ACE _ USES _ WCHAR configuration setting.

 template <class ACCEPTOR>  class Reactor_Logging_Server_Adapter : public ACE_Service_Object {  public:    // Hook methods inherited from <ACE_Service_Object>.    virtual int init (int argc, ACE_TCHAR *argv[]);    virtual int fini ();    virtual int info (ACE_TCHAR **, size_t) const;    virtual int suspend ();    virtual int resume ();  private:    Reactor_Logging_Server<ACCEPTOR> *server_;  }; 

This template inherits from the ACE_Service_Object class and contains a pointer to a Reactor_Logging_Server object (page 83). We instantiated this template with the ACCEPTOR class parameter to defer our choice of the acceptor factory until later in the design cycle. The Adapter pattern is a good choice here because it allows reuse of our existing Reactor_Logging_Server class. If we were designing this example from scratch with the ability to be configured as a service, however, a more direct approach would be to derive Reactor_Logging_Server from ACE_Service_Object instead of from ACE_Event_Handler . In that case, the adapter class would not be needed, and we could still defer the choice of the acceptor factory until later.

Figure 5.3 illustrates the lifecycle of the objects in this example when an instance of Reactor_Logging_Server_Adapter is configured dynamically. When this service is configured into the address space of an application, the ACE Service Configurator framework creates an instance of Reactor_Logging_Server_Adapter and invokes the following init() hook method automatically:

Figure 5.3. Life Cycle of the Dynamic Reactor Logging Server

 1 template <class ACCEPTOR> int   2 Reactor_Logging_Server_Adapter<ACCEPTOR>::init   3   (int argc, ACE_TCHAR *argv[])   4 {   5   int i;   6   char **array = 0;   7   ACE_NEW_RETURN (array, char*[argc], -1);   8   ACE_Auto_Array_Ptr<char *> char_argv (array);   9  10   for (i = 0; i < argc; ++i)  11     char_argv[i] = ACE::strnew (ACE_TEXT_ALWAYS_CHAR(argv[i]));  12   ACE_NEW_NORETURN (server_, Reactor_Logging_Server<ACCEPTOR>  13                                (i, char_argv.get (),  14                                 ACE_Reactor::instance ()));  15   for (i = 0; i < argc; ++i) ACE::strdelete (char_argv[i]);  16   return server_ == 0 ? -1 : 0;  17 } 

Lines 5 “11 The ACE Service Configurator framework passes argv as an array of ACE_TCHAR pointers, but the Reactor_Logging_Server constructor accepts a char * array. The init() method therefore uses the ACE _ TEXT _ ALWAYS _ CHAR macro to convert to the char format where needed. This macro creates a temporary object with the transformed string, which is then copied via ACE::strnew() to preserve it through the Reactor_Logging_Server constructor. Sidebar 29 (page 125) describes the ACE::strnew() and ACE::strdelete() methods.

Lines 12 “14 Dynamically allocate an instance of the Reactor_Logging_Server that contains the desired reactor, acceptor, and handlers.

Line 15 Free the memory used for the converted argv strings.

When instructed to remove the dynamically configured logging service, the ACE Service Configurator framework invokes the Reactor_Logging_Server_Adapter::fini() hook method shown below:

 template <class ACCEPTOR> int  Reactor_Logging_Server_Adapter<ACCEPTOR>::fini ()  { server_->handle_close (); server_ = 0; return 0; } 

This method calls Reactor_Logging_Server::handle_close() , which deletes the Reactor_Logging_Server object allocated by init() . The ACE Service Configurator framework uses the "gobbler" function (page 137) to delete a service object after calling its fini() hook method. We therefore must not call delete this in fini() .

The info() hook method reports service-specific information when the framework requests it. Our info() method formats a string containing the TCP port it's listening on:

 1 template <class ACCEPTOR> int   2 Reactor_Logging_Server_Adapter<ACCEPTOR>::info   3     (ACE_TCHAR **bufferp, size_t length) const {   4   ACE_TYPENAME ACCEPTOR::PEER_ADDR local_addr;   5   server_->acceptor ().get_local_addr (local_addr);   6   7   ACE_TCHAR buf[BUFSIZ];   8   ACE_OS::sprintf (buf,   9                    ACE_TEXT ("%hu"),  10                    local_addr.get_port_number ());  11   ACE_OS_String::strcat  12     (buf, ACE_TEXT ("/tcp # Reactive logging server\n"));  13   if (*bufferp == 0) *bufferp = ACE::strnew (buf);  14   else ACE_OS_String::strncpy (*bufferp, buf, length);  15   return ACE_OS_String::strlen (*bufferp);  16 } 

Lines 4 “5 Obtain the network address from the instance of ACE_SOCK_Acceptor that's used by the Reactor_Logging_Server .

Lines 7 “12 Format a message that explains what the service does and how to contact it.

Line 13 If the caller didn't supply a buffer to hold the formatted message, allocate a buffer and copy the message into it using ACE::strnew() . In this case, the caller must use ACE::strdelete() to free the buffer. ACE does not specify how an implementation of info() must allocate memory. Developers writing an implementation of info() must therefore define and clearly document the policy for their implementations. It's strongly recommended that developers use ACE::strnew() to allocate the string, and require their users to call ACE::strdelete() to free the memory. Sidebar 29 describes the motivation for these methods.

Line 14 If the caller did supply a buffer for the message, copy the formatted message into it, limited to the length passed by the caller.

Line 15 Return the length of the message.

Sidebar 29: Portable Heap Operations with ACE

Library functions and classes, such as Reactor_Logging_Server_Adapter::info() (page 124), often allocate memory dynamically. Memory allocated dynamically in C++ programs should eventually be freed. To write portable C++ programs, it's important to match these allocate and free operations to avoid corrupting the heap (also known as the freestore).

A surprisingly common misconception is that simply ensuring the proper matching of calls to operator new() and operator delete() (or calls to malloc() and free() ) is sufficient for correct heap management. This strategy relies on the implicit assumption that there's one universal heap per process. In practice, however, a heap is simply a memory area managed by some run-time component, such as the C or C++ run-time library. If an executing program is exposed to multiple run-time library instances, it's likely there will be multiple heaps as well.

For example, Windows supplies multiple variants of the C/C++ run-time library, such as Debug versus Release and Multithreaded versus Single-threaded. Each of these variants maintains its own heap. Memory allocated from one heap must be released back to the same heap. Thus, correct heap management requires not only matching the proper method/function calls, but also making them through the same run-time library. It's easy to violate these requirements when code from one subsystem or provider frees memory allocated by another.

To assist in managing dynamic memory portably, ACE offers matching allocate and free methods listed in the table below:

Method

Usage

ACE::strnew()

Allocates memory for a copy of a character string and copies the string into it.

ACE::strdelete()

Releases memory allocated by strnew() .

ACE_OS_Memory::malloc()

Allocates a memory block of specified size.

ACE_OS_Memory:: calloc ()

Allocates a memory block to hold a specified number of objects, each of a given size. The memory contents are explicitly initialized to 0.

ACE_OS_Memory::realloc()

Changes the size of a memory block allocated via ACE_OS_Memory::malloc() .

ACE_OS_Memory::free()

Releases memory allocated via any of the above three ACE_OS_Memory methods.

As long as developers match the correct ACE allocation and deallocation methods, ACE ensures the correct run-time library functions will be called on the correct heaps on all platforms. For complete details on these methods, see the online ACE reference documentation at http://ace.ece.uci.edu/Doxygen/ and http://www.riverace.com/docs/.

Unlike the other ACE_Service_Object hook methods shown in Figure 5.2 (page 120), an info() method isn't always invoked by the ACE Service Configurator framework, though it can be. Instead, it's often called directly by a server program, as shown in the Service_Reporter::handle_input() method (page 133). Moreover, application developers can determine the most useful content of the message since info() doesn't mandate a particular format.

The suspend() and resume() hook methods are similar to each other:

 template <class ACCEPTOR> int  Reactor_Logging_Server_Adapter<ACCEPTOR>::suspend ()  { return server_->reactor ()->suspend_handler (server_); }  template <class ACCEPTOR> int  Reactor_Logging_Server_Adapter<ACCEPTOR>::resume ()  { return server_->reactor ()->resume_handler (server_); } 

Since the Reactor_Logging_Server class descends from ACE_Event_Handler , the server_ object can be passed to the singleton reactor's suspend_handler() and resume_handler() methods (page 73). Both methods double-dispatch to Reactor_Logging_Server::get_handle() to extract the underlying passive-mode socket handle. This socket handle is then temporarily removed from or replaced in the list of socket handles handled by the singleton reactor. The Example portion of Section 5.4 shows how the Reactor_Logging_Server_Adapter can be configured into and out of a generic server application dynamically.

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