Our simple
client illustrated how to use the traditional CORBA synchronous method
invocation to query a number of stock prices. Suppose that we had to query
hundreds of stock prices, for example, during the initialization of a complex
market analysis tool. In that case sending the requests in sequence is going to
yield poor performance; we are not taking advantage of the natural parallelism
in distributed systems, since we are waiting for the first response to come back
before sending the next query. Traditionally this problem has been attacked
using either oneway
calls or multiple threads. Both approaches can
work, but they have some disadvantages: multi-threading programming can be hard
and error-prone, oneways are unreliable and require callback interfaces to
return the stock value. Recently the OMG approved the CORBA Messaging
specification that extends the basic invocation model to include asynchronous
calls. Unlike the old deferred synchronous model, the new model uses the IDL
compiler and the SII to achieve type safety and improved performance, but the
application does not block waiting for a response. Instead, it gives the ORB a
reference to a reply handler that will receive the response asynchronously. The
specification also defines a polling interface that we will not discuss as TAO
does not implement it.
For this problem we will extend the IDL interface to include a new operation:
interface Single_Query_Stock : Stock { double get_price_and_names (out string symbol, out string full_name); };
This will simplify some of the examples.
The first step is to generate the stubs and skeletons with support for the
callback AMI. We do this with the -GC
flag:
$ $TAO_ROOT/bin/tao_idl -GC Quoter.idl
You may want to take a brief look at the generated client side interface. The
IDL compiler adds new methods to the Quoter::Stock
interface. In
particular pay attention to this operation:
virtual void sendc_get_price_and_names ( AMI_Single_Query_StockHandler_ptr ami_handler ) ACE_THROW_SPEC (( CORBA::SystemException ));
This is the operation used to send a request asynchronously. The response is received in the handler object. This is a regular CORBA object with the following IDL interface:
interface AMI_Single_Query_StockHandler { void get_price_and_names (in double ami_return_val, in string symbol, in string full_name); };
You don't have to write this IDL interface. The IDL compiler automatically generates the so-called implied IDL constructs from your original IDL. Notice how the arguments are generated. The first argument is simply the return value, then the output arguments show up, but as input only since the handler has to receive the reply.
We will have to implement a servant for this new IDL interface so we can receive the reply, exactly as we do for servers:
class Single_Query_Stock_Handler_i : public POA_Quoter::AMI_Single_Query_StockHandler { public: Single_Query_Stock_Handler_i (int *response_count) : response_count_ (response_count) {} void get_price_and_names (CORBA::Double ami_return_val, const char *symbol, const char *full_name) throw (CORBA::SystemException) { std::cout << "The price of one stock in \"" << full_name << "\" (" << symbol << ") is " << ami_return_val << std::endl; *this->response_count_++; } private: int *response_count_; };
The response_count_
field will be used to terminate the client
when all the responses are received.
The handler servant is activated as any other CORBA object:
int response_count = 0; Single_Query_Stock_Handler_i handler_i (&response_count); Quoter::AMI_Single_Query_StockHandler_var handler = handler_i._this ();
and now we change the loop to send all the requests at once:
int request_count = 0; for (int i = 2; i != argc; ++i) { try { // Get the stock object Quoter::Stock_var tmp = factory->get_stock (argv[i]); Quoter::Single_Query_Stock_var stock = Quoter::Single_Query_Stock::_narrow (tmp.in ()); stock->sendc_get_price_and_names (handler.in ()); request_count++; }
after the loop we wait until all the responses have arrived:
while (response_count < request_count && orb->work_pending ()) { orb->perform_work (); }
Complete the client.cpp
file. Does this client play the server
role too? If not, what is the role with respect to the handler servant? If you
think it is a server too, what should you do about the POA?
You can use the following files to complete your implementation: the Quoter.idl, Handler_i.h, Handler_i.cpp, Makefile. Remember that the simple client main program (located here) is a good start.
Look at client.cpp file. It should not be much different from yours.
A simple server is provided, based on the simple server from the introduction. As before, you need the following files: Stock_i.h, Stock_i.cpp, Stock_Factory_i.h Stock_Factory_i.cpp and server.cpp.
So far we have used the default configuration in TAO, but AMI works better with some fine tuning. For example, by default TAO uses a separate connection for each outstanding request. With SMI this is a very good strategy, as separate threads can send concurrent requests without any shared resources, but this approach does not scale well with AMI, as it would create too many connections. The solution is to change the strategy to share connections. All we need to do is create a svc.conf file with the following contents:
static Client_Strategy_Factory "-ORBTransportMuxStrategy MUXED"
There are many other configuration options, some of them documented in Options.html.