Now that we know how to implement a
simple client, we need to
implement a server to test it out.
We have to provide implementations for both the Stock
and Stock_Factory
interfaces,
and then create an executable that incorporates those implementations.
Stock
interfaceTo keep things simple, let's implement a Stock
object with a fixed price. The constructor will receive all the
parameters:
class Quoter_Stock_i : public POA_Quoter::Stock { public: Quoter_Stock_i (const char *symbol, const char *full_name, CORBA::Double price); private: std::string symbol_; std::string full_name_; CORBA::Double price_; };
In a server, CORBA objects and functions are implemented and represented by programming language data and functions. These programming entities that implement and represent CORBA objects are called servants. Object Adapters link the world of CORBA objects to the world of programming language servants. They provide services for creation of CORBA objects and their object references and for dispatching requests to the appropriate servants.
Notice the name of the base class. TAO implements the CORBA 2.2 specification, which includes the Portable Object Adapter (hence the POA prefix). This new Object Adapter fixes many problems with the previous versions of the CORBA specification, where the so-called Basic Object Adapter was used. Unfortunately, the specification was ambiguous and lead to incompatible (yet compliant) implementations. Code based on the POA, and conforming to the CORBA 2.2 specification, is almost completely portable, the only incompatibilities arising from the names of the generated header files and and other minor things. Those problems can be easily wrapped in helper classes, and the file names can be controlled through IDL compiler options in most cases.
A server application may contain multiple POA instances, but all server applications have at least one POA called the RootPOA.
We have to implement the operations and attributes:
class Quoter_Stock_i : public POA_Quoter::Stock { public: // some details omitted char *symbol () throw (CORBA::SystemException); char *full_name () throw (CORBA::SystemException); CORBA::Double price () throw (CORBA::SystemException); }; // In the .cpp file: char * Quoter_Stock_i::symbol () throw (CORBA::SystemException) { return CORBA::string_dup (this->symbol_.c_str ()); }
The other attributes and methods are similar, so we don't reproduce them here.
The first question is what are those throw()
specifications doing there? Any CORBA operation or attribute
can raise a CORBA::SystemException
. The IDL compiler
must generate a throw specification that includes the CORBA
system exceptions and any user exceptions listed in the
raise
clause. In turn, the C++ specification requires
that any derived classes cannot add exceptions to a throw
specification, so you must have a throw specification in your
methods, either including CORBA::SystemException
or
empty.
It is important to copy the strings before returning them,
because the ORB will use CORBA::string_free
to release
them. The rationale is that over the network, the string must be
copied anyway, hence, the client must be responsible for releasing
the received string. When both client and servers are in the same
address space the ORB can optimize the path and invoke the server
operation directly, without marshaling or demarshaling. If the
client is going to work with both local and remote servers, it
should always expect to own the string. In consequence, the server
implementation must always allocate a copy and return the copy,
because the server-side must also work identically for local and
remote clients. The memory management rules in CORBA are a bit
subtle, but there are some simple rules to follow:
Typing all this code seems tedious. Can't the IDL compiler help with this? After all, it seems as if the method declarations are completely specified! The answer is yes, TAO's IDL compiler can generate empty implementations that you can modify. Simply use the -GI option:
$ $ACE_ROOT/TAO/TAO_IDL/tao_idl -GI Quoter.idlThe empty implementations are generated in the
QuoterI.h
and QuoterI.cpp
files.
Be advised that the -GI
option overwrites these files
every time, so it is better to copy your implementation to
another file.
Our first implementation of the factory will serve only two stocks, "RHAT" and "MSFT":
class Quoter_Stock_Factory_i : public POA_Quoter::Stock_Factory { public: Quoter_Stock_Factory (); Quoter::Stock_ptr get_stock (const char *symbol) throw (Quoter::Invalid_Stock_Symbol); private: Quoter_Stock_i rhat_; Quoter_Stock_i msft_; };
The implementation of the get_stock()
method is
simple, just compare the symbol name and return the appropriate
object reference:
Quoter::Stock_ptr Quoter_Stock_Factory_i::get_stock (const char *symbol) throw (Quoter::Invalid_Stock_Symbol) { if (strcmp (symbol, "RHAT") == 0) { return this->rhat_._this(); } else if (strcmp (symbol, "MSFT") == 0) { return this->msft_._this (); } throw Quoter::Invalid_Stock_Symbol (); }
So what is that _this()
method? In the POA mapping
the client-side stubs and server-side skeletons are not related
through inheritance. You must either explicitly activate the
servant (your implementation object) or use _this()
to
activate it with its default POA. _this()
creates and
registers a CORBA object under the RootPOA, and returns the created
object reference for the new object. We will discuss more about
explicit and implicit activation later, but at this point it is
important to remove any thoughts about converting pointers to
servants to object references or vice-versa, it just does not
work that way.
Now that we have all the object implementations in place, we must create the server executable. We start with the ORB initialization:
int main (int argc, char* argv[]) { try { // First initialize the ORB, that will remove some arguments... CORBA::ORB_var orb = CORBA::ORB_init (argc, argv, "" /* the ORB name, it can be anything! */);
On startup, the ORB starts the POA in the {holding state}, where all requests received are not processed until the POA is activated. Meanwhile the requests are stored to some implementation limit. TAO sets this limit to 0, because queueing is a severe source of overhead and unpredictability in real-time systems.
What does this means for us? Well, we have to activate the POA. The process is a bit tedious. First we gain access to the RootPOA:
CORBA::Object_var poa_object = orb->resolve_initial_references ("RootPOA"); PortableServer::POA_var poa = PortableServer::POA::_narrow (poa_object.in ());
resolve_initial_references()
is used to bootstrap
all kinds of services, like the Naming Service and the Trading
Service, but it is also used to gain access to other ORB
interfaces, such as the RootPOA, the Current objects, and the
Policy Managers.
Now that we have gained access to the Root POA, we must obtain its POA manager. The POA managers provide interfaces to activate and deactivate one or more POAs:
PortableServer::POAManager_var poa_manager = poa->the_POAManager ();
and now we activate the POA:
poa_manager->activate ();
The shutdown process is similar to the client side, but now we must also remember to destroy the POA. Putting all the code above together, we get:
int main (int argc, char* argv[]) { try { // First initialize the ORB, that will remove some arguments... CORBA::ORB_var orb = CORBA::ORB_init (argc, argv, "" /* the ORB name, it can be anything! */); CORBA::Object_var poa_object = orb->resolve_initial_references ("RootPOA"); PortableServer::POA_var poa = PortableServer::POA::_narrow (poa_object.in ()); PortableServer::POAManager_var poa_manager = poa->the_POAManager (); poa_manager->activate (); // The application code goes here! // Destroy the POA, waiting until the destruction terminates poa->destroy (1, 1); orb->destroy (); } catch (CORBA::Exception &ex) { std::cerr << "CORBA exception raised!" << std::endl; } return 0; }
Now we create an instance of our stock factory implementation
and activate it using _this()
Quoter_Stock_Factory_i stock_factory_i; Quoter::Stock_Factory_var stock_factory = stock_factory_i._this ();
Next we convert the object reference into an IOR string, so it can be used by the client:
CORBA::String_var ior = orb->object_to_string (stock_factory.in ()); std::cerr << ior.in () << std::endl;
There is only one final detail. We must run the ORB event loop to start processing requests from the client:
orb->run ();
There are many details that we have left out from this server, such as how to terminate the event loop, how to perform servant memory management, orderly deactivation of servants, and the fact is that it is not very flexible, but we have covered a number of other things, and more importantly we can test the client now!
Flesh out the implementation. You don't have to do it from scratch, as we provide you with the following files: Stock_i.h, Stock_i.cpp, Stock_Factory_i.h Stock_Factory_i.cpp, Quoter.idl and the always useful Makefile
Compare your solution with server.cpp.
To test this application we need a client. We just run both of them:
$ server > ior_file $ client file://ior_file MSFT RHATAlso test invalid symbols!