异步方法调用——针对急迫的( impatient )客户端的CORBA解决方案
我们的简单服务详细阐述了如何通过传统CORBA同步方向调用来查询股票的价格的。假定,举例来说,一个复杂的市场分析工具的初始化时,我们必须对数百支股票进行价格查询。在这种情况下按顺序发送请求会严重影响性能;由于在发送后一条查询请求之前我们需要等待上一条查询的返回,所以我们不能利用分布式的系统本身的并行性。针对这个问题传统的解决方法是使用oneway调用或者使用多线程。这两种方式都是可行的,但都有缺点:多线程编程非常困难并且容易出错,oneway不可靠并且需要回调接口返回股票的值。最近,OMG核准了CORBA Messaging规范来扩展基本的调用模型以支持异步调用。这以之前的同步调用模型不同,新的模型使用IDL编译器和SII来达到类型安全和提高的性能,并且应用程序不会在等待影响时阻塞。该模型提供的ORB的一个引用来返回处理器,这个处理器将异步的接收响应。该规范还定义了投递(polling)接口,由于TAO未实现此接口,所以这里不作讨论。
为了解决上面的问题我们扩展IDL接口来包含新的操作:
interface Single_Query_Stock : Stock { double get_price_and_names (out string symbol, out string full_name); };
这将使一些示例得以简化。
第一步是生成支持回调AMI的stubs和skeletions。我们使用-GC标记:
$ $ACE_ROOT/TAO/TAO_IDL/tao_idl -GC Quoter.idl
您或许想简单的查看生成的客户端的接口。IDL编译器添加新的方法到 Quoter::Stock
接口。请特别注意下面的方法:
virtual void sendc_get_price_and_names ( AMI_Single_Query_StockHandler_ptr ami_handler );
这就是用于发送异步请求的操作。处理器对象用于接收响应。下面是一个正规的CORBA对象,此对象带有下面的IDL接口:
interface AMI_Single_Query_StockHandler { void get_price_and_names (in double ami_return_val, in string symbol, in string full_name); };
你无须编写IDL接口。该接口由IDL编译器自动从原始的IDL中生成,所以也称它为隐式IDL。请注意参数是如何生成的,第一个参数是简单的返回值,后面是输出参数,由于处理器必须接收返回,所以设为input。
实现返回处理器
我们必须为新的IDL接口实现伺服代码(servant),以便于我们可以接收到返回值,这正如我们的服务端:
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) { std::cout << "The price of one stock in /"" << full_name << "/" (" << symbol << ") is " << ami_return_val << std::endl; *this->response_count_++; } private: int *response_count_; };
response_count_
字段用来当所有的应答都收到之后中止客户端。
发送异步方向调用
和其它CORBA对象一样激活处理器伺服器(servant):
int response_count = 0; Single_Query_Stock_Handler_i handler_i (&response_count); Quoter::AMI_Single_Query_StockHandler_var handler = handler_i._this ();
然后我们修改循环用来立即发送所有的请求:
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++; }
在循环结束后我们一直等待,直到收回所有的响应:
while (response_count < request_count && orb->work_pending ()) { orb->perform_work (); }
练习1
完成client.cpp
文件。这个客户端是否扮演服务端的角色?如果不是,该角色涉及到处理器伺服的什么?如果您认为这也是服务端,那么关于POA您做了些什么?
您可以使用下面的文件完成您的实现:Quoter.idl, Handler_i.h, Handler_i.cpp. 记住简单客户端的主函数(here))是一个不错的开端。
解决方案
查看 client.cpp 文件。这与您的应该不会有太大的差异。
测试
在基于在介绍一节简单服务端之上提供了简单服务器。和以前一样,您需要下面的文件 Stock_i.h, Stock_i.cpp, Stock_Factory_i.h Stock_Factory_i.cpp 和server.cpp.
配置
迄今为止我们在TAO中使用的是默认的配置,但是通过好的调整能让AMI可以工作得更好。例如,默认的TAO为每个显示的请求使用单独的连接。这对于同步方向调用(SMI)来说是一个好的策略,因为用单独的线程发送并发的请求不需要用到共享的资源,但这个方法对于异步方法调用(AMI)来说缺少可伸缩性,这是因为它需要创建过多的连接。本解决方案改变策略用于使连接可被共享。为了达到这个目的,我们需要创建一个下面内容的svc.conf文件:
static Client_Strategy_Factory "-ORBTransportMuxStrategy MUXED"
还有许多其它的配置选项,这些都描述在文档 Options.html,configurations.html 和来自OCI的开发者指南中。