Introduction - A very simple server

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.

Implementing the Stock interface

To 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 ();
  char *full_name ();
  CORBA::Double price ();
};

// In the .cpp file:
char *
Quoter_Stock_i::symbol ()
{
  return CORBA::string_dup (this->symbol_.c_str ());
}

The other attributes and methods are similar, so we don't reproduce them here.

Memory management rules for arguments

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:

The complete memory management rules can be found in the Henning and Vinoski book or the CORBA specification.

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:

$ $TAO_ROOT/TAO_IDL/tao_idl -GI Quoter.idl
The 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.

The Stock Factory implementation

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);

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)
{
  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.

Implementing a CORBA server

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 (true, true);
    orb->destroy ();
  }
  catch (const 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!

Exercise 1

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 MPC file

Solution

Compare your solution with server.cpp.

Testing

To test this application we need a client. We just run both of them:

$ server > ior_file
$ client file://ior_file MSFT RHAT
Also test invalid symbols!


Carlos O'Ryan
Last modified: Sun Apr 1 15:03:38 PDT 2001