Ru-Brd |
MotivationThe 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 CapabilitiesACE_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:
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
The ACE_Service_Repository binds the following entities together:
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:
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
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]. ExampleThis 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 :
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.
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)
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:
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:
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 |