5.3 The ACE_Service_Repository Classes

Ru-Brd

Motivation

The ACE Service Configurator framework supports the configuration of both single-service and multiservice servers. Section 5.2 explained why the goals of initialization, execution control, reporting, and termination require application services to be based on a common framework class. For the framework to leverage the accessibility provided by ACE_Service_Object effectively, it must store service information in a well-known repository and be able to access and control these service objects individually or collectively.

Application services in multiservice servers also may require access to each other. To avoid tightly coupling these services, and to preserve the benefits of delayed configuration decisions, services should be able to locate each other at run time. Therefore, to satisfy the needs of the framework and applications without requiring developers to provide these capabilities in an ad hoc way, the ACE Service Configurator framework provides the ACE_Service_Repository and ACE_Service_Repository_Iterator classes.

Class Capabilities

ACE_Service_Repository implements the Manager pattern [Som98] to control the life cycle of, and the access to, service objects configured by the ACE Service Configurator framework. This class provides the following capabilities:

  • It keeps track of all service implementations that are configured into an application and maintains each service's status, such as whether it's active or suspended .

  • It provides the mechanism by which the ACE Service Configurator framework inserts , manages , and removes services.

  • It provides a convenient mechanism to terminate all services, in reverse order of their initialization.

  • It allows an individual service to be located by its name .

The interface for ACE_Service_Repository is shown in Figure 5.4 (page 128) and its key methods are outlined in the following table:

Figure 5.4. The ACE_Service_Repository Class

Method

Description

ACE_Service_Repository() open ()

Initialize the repository and allocate its dynamic resources.

ACE_Service_Repository() close()

Close down the repository and release its dynamically allocated resources.

insert()

Add a new service into the repository.

find()

Locate an entry in the repository.

remove()

Remove an existing service from the repository.

suspend()

Suspend a service in the repository.

resume()

Resume a suspended service in the repository.

instance()

A static method that returns a pointer to a singleton ACE_Service_Repository .

The ACE_Service_Repository binds the following entities together:

  • The name of a service, which is represented as a character string, and

  • An instance of ACE_Service_Type , which is the class used by the ACE Service Configurator framework to link, initialize, suspend, resume, remove, and unlink services from a server statically or dynamically.

The ACE_Service_Type class provides the framework with the operations necessary to act on the configured services. The ACE Service Configurator framework can be used to configure dynamic and static services, as well as the ACE_Module and ACE_Stream capabilities covered in Sections 9.2 and 9.3, respectively.

The ACE_Service_Type class uses the Bridge pattern to allow type-specific data and behavior in service types to evolve without impacting the class. The ACE_Service_Type class plays the Abstraction role in this pattern and the ACE_Service_Type_Impl class plays the Implementor role. The following classes each play the ConcreteImplementor role, representing the types of services that can be recorded in the service repository:

  1. ACE_Service_Object_Type ”The object() method returns a pointer to the associated ACE_Service_Object described in Section 5.2.

  2. ACE_Module_Type ”The object() method returns a pointer to the associated ACE_Module described in Section 9.2.

  3. ACE_Stream_Type ”The object() method returns a pointer to the associated ACE_Stream described in Section 9.3.

For dynamically linked service objects, ACE_Service_Type also stores the handle of the DLL that contains the service's executable code. The ACE Service Configurator framework uses this handle to unlink and unload a service object from a running server when the service it offers is no longer needed. Sidebar 30 (page 131) shows how a program can use ACE_Dynamic_Service and ACE_Service_Type to retrieve services from ACE_Service_Repository programmatically.

ACE_Service_Repository_Iterator implements the Iterator pattern [GoF] to provide applications with a way to sequentially access the ACE_Service_Type items in an ACE_Service_Repository without exposing its internal representation. The interface for ACE_Service_Repository_Iterator is shown in Figure 5.5 (page 130) and its key methods are outlined in the following table:

Figure 5.5. The ACE_Service_Repository_Iterator Class

Method

Description

ACE_Service_Repository_Iterator()

Initialize the iterator.

next ()

Pass back a pointer to the next ACE_Service_Type in the repository.

done()

Returns 1 when all items have been seen.

advance()

Move ahead one item in the repository.

Never delete entries from an ACE_Service_Repository that's being iterated over since the ACE_Service_Repository_Iterator is not a robust iterator [Kof93].

Example

This example illustrates how the ACE_Service_Repository and ACE_Service_Repository_Iterator classes can be used to implement a Service_Reporter class. This class provides a "meta-service" that clients can use to obtain information on all services that the ACE Service Configurator framework has configured into an application statically or dynamically. A client interacts with a Service_Reporter as follows :

  • The client establishes a TCP connection to the Service_Reporter object.

  • The Service_Reporter returns a list of all the server's services to the client.

  • The Service_Reporter closes the TCP / IP connection.

Sidebar 31 (page 132) describes ACE_Service_Manager , which is a class bundled with the ACE toolkit that provides a superset of Service_Reporter features.

The Service_Reporter class is described below. We first create a file called Service_Reporter.h that contains the following class definition:

 class Service_Reporter : public ACE_Service_Object {  public:    Service_Reporter (ACE_Reactor *r = ACE_Reactor::instance ())      : ACE_Service_Object (r) {}    // 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 ();  protected:    // Reactor hook methods.    virtual int handle_input (ACE_HANDLE);    virtual ACE_HANDLE get_handle () const    { return acceptor_.get_handle (); }  private:    ACE_SOCK_Acceptor acceptor_; // Acceptor instance.    enum { DEFAULT_PORT = 9411 };  }; 

Since Service_Reporter inherits from ACE_Service_Object , it can be configured by the ACE Service Configurator framework. The ACE Service Configurator framework will create an instance of this class at run time, so the constructor must be public.

Sidebar 30: The ACE_Dynamic_Service Template

The ACE_Dynamic_Service class template provides a type-safe way to access the ACE_Service_Repository programmatically. An application process can use this template to retrieve services registered with its local ACE_Service_Repository . As shown below, the TYPE template parameter ensures that a pointer to the appropriate type of service is returned from the static instance() method:

 template <class TYPE>  class ACE_Dynamic_Service {  public:    // Use <name> to search the <ACE_Service_Repository>.    static TYPE *instance (const ACE_TCHAR *name) {      const ACE_Service_Type *svc_rec;      if (ACE_Service_Repository::instance ()->find            (name, &svc_rec) == -1) return 0;      const ACE_Service_Type_Impl *type = svc_rec->type ();      if (type == 0) return 0;      ACE_Service_Object *obj =        ACE_static_cast (ACE_Service_Object *, type->object ());      return ACE_dynamic_cast (TYPE *, obj);    }  }; 

If an instance of the Server_Logging_Daemon service has been linked dynamically and initialized by the ACE Service Configurator framework, an application can use the ACE_Dynamic_Service template to access the service programmatically as shown below:

 typedef Reactor_Logging_Server_Adapter<Logging_Acceptor>          Server_Logging_Daemon;  Server_Logging_Daemon *logging_server =    ACE_Dynamic_Service<Server_Logging_Daemon>::instance      (ACE_TEXT ("Server_Logging_Daemon"));  ACE_TCHAR *service_info = 0;  logging_server->info (&service_info);  ACE_DEBUG ((LM_DEBUG, "%s\n", service_info));  ACE::strdelete (service_info); 

Note that this example assumes info() allocates string memory via the ACE::strnew() method discussed in Sidebar 29 (page 125).

Sidebar 31: The ACE_Service_Manager Class

ACE_Service_Manager provides clients with access to administrative commands to access and manage the services currently offered by a network server. These commands "externalize" certain internal attributes of the services configured into a server. During server configuration, an ACE_Service_Manager is typically registered at a well-known communication port, for example, port 9411. Clients can connect to an ACE_Service_Manager at that port and issue one of the following commands.

  • help ” a list of all services configured into an application via the ACE Service Configurator framework is returned to the client.

  • reconfigure ” a reconfiguration is triggered to reread the local service configuration file.

If a client sends anything other than these two commands, its input is passed to ACE_Service_Config::process_directive() (page 141), which enables remote configuration of servers via command-line instructions such as

 % echo "suspend My_Service"  telnet hostname 9411 

It's therefore important to use the ACE_Service_Manager only if your application runs in a trusted environment since a malicious attacker can use it to deny access to legitimate services or configure rogue services in a Trojan Horse manner. For this reason, ACE_Service_Manager is a static service that ACE disables by default.

An application can direct ACE to load its static services, including the ACE_Service_Manager , with the ACE_Service_Config::open() method (page 141) in either of two ways:

  1. At compile time, by passing a 0 for the ignore_static_svcs argument

  2. At run time, by including the '-y' option with the argc / argv pair; this overrides the ignore_static_svcs value.

The implementations of the Service_Reporter hook methods are placed into the Service_Reporter.cpp file. The ACE Service Configurator framework calls the following Service_Reporter::init() hook method when a Service_Reporter is configured into an application:

 1 int Service_Reporter::init (int argc, ACE_TCHAR *argv[]) {   2   ACE_INET_Addr local_addr (Service_Reporter::DEFAULT_PORT);   3   ACE_Get_Opt get_opt (argc, argv, ACE_TEXT ("p:"), 0);   4   get_opt.long_option (ACE_TEXT ("port"),   5                        'p', ACE_Get_Opt::ARG_REQUIRED);   6   for (int c; (c = get_opt ()) != -1;)   7     if (c == 'p') local_addr.set_port_number   8                     (ACE_OS::atoi (get_opt.opt_arg ()));   9   acceptor_.open (local_addr);  10   return reactor ()->register_handler  11           (this,  12           ACE_Event_Handler::ACCEPT_MASK);  13 } 

Line 2 Initialize local_addr to the Service_Reporter 's default TCP port number.

Lines 3 “8 Parse the service configuration options using the ACE_Get_Opt class described in Sidebar 8 (page 47). We start parsing at argv[0] rather than argv[1] , which is the default. If the -p , or the long version --port , option is passed into init() , the local_addr port number is reset to that value. Since ACE_Get_Opt always returns the corresponding short option for any long options it encounters, it's sufficient to test only for 'p' in the loop iterator.

Lines 9 “12 Initialize the ACE_SOCK_Acceptor to listen on the local_addr port number and register the instance of Service_Reporter with the reactor for ACCEPT events. When a connection request arrives from a client, the reactor dispatches the following Service_Reporter::handle_input() hook method:

 1 int Service_Reporter::handle_input (ACE_HANDLE) {   2   ACE_SOCK_Stream peer_stream;   3   acceptor_.accept (peer_stream);   4   5   ACE_Service_Repository_Iterator iterator   6     (*ACE_Service_Repository::instance (), 0);   7   8   for (const ACE_Service_Type *st;   9        iterator.next (st) != 0;  10        iterator.advance ()) {  11     iovec iov[3];  12     iov[0].iov_base = ACE_const_cast (char *, st->name ());  13     iov[0].iov_len =  14       ACE_OS_String::strlen (st->name ()) * sizeof (ACE_TCHAR);  15     const ACE_TCHAR *state = st->active () ?  16            ACE_TEXT (" (active) ") : ACE_TEXT (" (paused) ");  17     iov[1].iov_base = ACE_const_cast (char *, state);  18     iov[1].iov_len =  19       ACE_OS_String::strlen (state) * sizeof (ACE_TCHAR);  20     ACE_TCHAR *report = 0; // Ask info() to allocate buffer.  21     int len = st->type ()->info (&report, 0);  22     iov[2].iov_base = ACE_static_cast (char *, report);  23     iov[2].iov_len = ACE_static_cast (size_t, len);  24     iov[2].iov_len *= sizeof (ACE_TCHAR);  25     peer_stream.sendv_n (iov, 3);  26     ACE::strdelete (report);  27   }  28  29   peer_stream.close ();  30   return 0;  31 } 

Lines 2 “3 Accept a new client connection. The Service_Reporter is an iterative service that only handles one client at a time.

Lines 5 “6 Initialize an ACE_Service_Repository_Iterator , which we'll use to report all the active and suspended services offered by the server. Passing a 0 as the second argument to this constructor instructs it to also return information on suspended services, which are ignored by default.

Lines 8 “27 For each service, invoke its info() method to obtain a descriptive synopsis of the service, and send this information back to the client via the connected socket. The sendv_n() gather-write method transfers all data buffers in the array of iovec structures efficiently using a single system function call, as discussed by Sidebar 6 in Chapter 3 of C++NPv1. Since there are no record boundaries in a TCP stream, the client may not be able to find the end of each line of text. It's therefore polite to code info() methods to include a newline at the end of the message. Note that this code can work with either narrow or wide characters , as discussed in Sidebar 28 (page 121). The text received by the client will be in the character set and width of the Service_Reporter . Designing a mechanism to handle this properly is left as an exercise for the reader.

Line 29 Close down the connection to the client and release the socket handle.

The Service_Reporter::info() hook method passes back a string that tells which TCP port number it's listening on and what the service does:

 int Service_Reporter::info (ACE_TCHAR **bufferp,                              size_t length) const {    ACE_INET_Addr local_addr;    acceptor_.get_local_addr (local_addr);    ACE_TCHAR buf[BUFSIZ];    ACE_OS::sprintf      (buf, ACE_TEXT ("%hu"), local_addr.get_port_number ());    ACE_OS_String::strcat      (buf, ACE_TEXT ("/tcp # lists services in daemon\n"));    if (*bufferp == 0) *bufferp = ACE::strnew (buf);    else ACE_OS_String::strncpy (*bufferp, buf, length);    return ACE_OS_String::strlen (*bufferp);  } 

As with the Reactor_Logging_Server_Adapter::info() method (page 124), the caller must delete the dynamically allocated buffer using ACE::strdelete() .

The Service_Reporter 's suspend() and resume() hook methods forward to the corresponding methods in the reactor singleton, as follows:

 int Service_Reporter::suspend ()  { return reactor ()->suspend_handler (this); }  int Service_Reporter::resume ()  { return reactor ()->resume_handler (this); } 

The Service_Reporter::fini() method is shown below:

 int Service_Reporter::fini () {    reactor ()->remove_handler      (this,      ACE_Event_Handler::ACCEPT_MASK       ACE_Event_Handler::DONT_CALL);    return acceptor_.close ();  } 

This method closes the ACE_SOCK_Acceptor endpoint and removes the Service_Reporter from the singleton reactor. The ACE Service Configurator framework is responsible for deleting a service object after calling its fini() hook method. We therefore don't need to delete this object in handle_close() , so we pass the DONT _ CALL flag to prevent the reactor from invoking this callback.

Finally, we must supply the ACE Service Configurator framework with some "bookkeeping" information regarding this new service. Although the code for this service will be statically linked into the example program, we want the framework to instantiate a Service_Reporter object to execute the service when it's activated. We therefore add the necessary ACE service macros to the Service_Reporter implementation file. These macros create a Service_Reporter and register it with the ACE_Service_Repository , as described in Sidebar 32 (page 136).

 1 ACE_FACTORY_DEFINE (ACE_Local_Service, Service_Reporter)   2   3 ACE_STATIC_SVC_DEFINE (   4   Reporter_Descriptor,   5   ACE_TEXT ("Service_Reporter"),   6   ACE_SVC_OBJ_T,   7   &ACE_SVC_NAME (Service_Reporter),   8   ACE_Service_Type::DELETE_THIS   9    ACE_Service_Type::DELETE_OBJ,  10   0 // This object is not initially active.  11 )  12  13 ACE_STATIC_SVC_REQUIRE (Reporter_Descriptor) 

Sidebar 32: The ACE Service Factory Macros

Applications can use the following macros defined in ace/OS.h to simplify the creation and use of factory functions and static service registration. With the exception of ACE _ STATIC _ SVC _ REGISTER , these macros should be used at file scope, rather than in a namespace, class, or method.

Factory and gobbler function macros. Static and dynamic services must supply a factory function to create the service object and a "gobbler" function to delete it. ACE provides the following three macros to help generate and use these functions:

  • ACE _ FACTORY _ DEFINE ( LIB , CLASS ) ” Used in an implementation file to define the factory and gobbler functions for a service. LIB is the ACE export macro prefix (see Sidebar 37 on page 150) used with the library containing the factory function. It can be ACE_Local_Service if the function needn't be exported from a DLL. CLASS is the type of service object the factory must create.

  • ACE _ FACTORY _ DECLARE ( LIB , CLASS ) ” Declares the factory function defined by the ACE _ FACTORY _ DEFINE macro. Use this macro to generate a reference to the factory function from a compilation unit other than the one containing the ACE _ FACTORY _ DEFINE macro.

  • ACE _ SVC _ NAME ( CLASS ) ” Generates the name of the factory function defined via the ACE _ FACTORY _ DEFINE macro. The generated name can be used to get the function address at compile time, such as for the ACE _ STATIC _ SVC _ DEFINE macro, below.

Static service information macro. ACE provides the following macro to generate static service registration information. It defines the service name, type, and a pointer to the factory function the framework calls to create a service instance:

  • ACE _ STATIC _ SVC _ DEFINE ( REG , NAME , TYPE , FUNC _ ADDR , FLAGS , ACTIVE ) ” Used in an implementation file to define static service information. REG forms the name of the information object, which must match the parameter passed to ACE _ STATIC _ SVC _ REQUIRE and ACE _ STATIC _ SVC _ REGISTER . Other parameters set ACE_Static_Svc_Descriptor attributes.

    Static service registration macros. The static service registration information must be passed to the ACE Service Configurator framework at program startup. The following two macros cooperate to perform this registration:

  • ACE _ STATIC _ SVC _ REQUIRE ( REG ) ” Used in the service implementation file to define a static object whose constructor will add the static service registration information to the framework's list of known static services.

  • ACE _ STATIC _ SVC _ REGISTER ( REG ) ” Used at the start of the main program to ensure the object defined in ACE _ STATIC _ SVC _ REQUIRE registers the static service no later than the point this macro appears.

Line 1 The ACE _ FACTORY _ DEFINE macro generates the following functions:

 void _gobble_Service_Reporter (void *arg) {    ACE_Service_Object *svcobj =      ACE_static_cast (ACE_Service_Object *, arg);    delete svcobj;  }  extern "C" ACE_Service_Object *  _make_Service_Reporter (void (**gobbler) (void *)) {    if (gobbler != 0) *gobbler = _gobble_Service_Reporter;    return new Service_Reporter;  } 

The ACE _ FACTORY _ DEFINE macro simplifies the use of the ACE Service Configurator framework as follows:

  • It generates compiler-independent factory functions. This macro generates the _make_Service_Reporter() factory function with extern "C" linkage, allowing the framework to locate this function in a DLL's symbol table without knowing the C++ compiler's name-mangling scheme.

  • It manages dynamic memory consistently. To ensure correct behavior across platforms, it's important that memory allocated in a DLL be deallocated in the same DLL, as described in Sidebar 29 (page 125). The gobbler function passed to _make_Service_Reporter() therefore enables the ACE Service Configurator framework to ensure memory is allocated and deleted within the same heap.

Lines 3 “11 The ACE _ STATIC _ SVC _ DEFINE macro is used to initialize an instance of ACE_Static_Svc_Descriptor . This object stores the information needed to describe the statically configured service reporter service. Service_Reporter is the service object's class name and "Service_Reporter" is the name used to identify the service in the ACE_Service_Repository . ACE _ SVC _ OBJ _ T is the type of the service object container. We use the ACE _ SVC _ NAME macro in conjunction with the C++ "address-of" operator to obtain the address of the _make_Service_Reporter() factory function that creates an instance of Service_Reporter . DELETE _ THIS and DELETE _ OBJ are enumerated literals defined in the ACE_Service_Types class that effect processing after the service's fini() hook method is called, as follows:

  • DELETE _ THIS directs ACE to delete the ACE_Service_Object_Type object representing the service and

  • DELETE _ OBJ causes the gobbler function to be called so the Service_Reporter object can be cleaned up.

Line 13 The ACE _ STATIC _ SVC _ REQUIRE macro defines an object that registers the instance of the Service_Reporter 's ACE_Static_Svc_Descriptor object with the ACE_Service_Repository . On many platforms, this macro also ensures the object is instantiated . Some platforms, however, also require the ACE _ STATIC _ SVC _ REGISTER macro in the main() function of the program this service is linked into.

The Example portion of Section 5.4 shows how a Service_Reporter can be configured statically into a server application.

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