Ru-Brd |
MotivationService configuration and life cycle management involves the following aspects that we've alluded to briefly above:
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 CapabilitiesACE_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:
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
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. ExampleTo 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:
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.
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 |