一. 用TAO创建股票报价系统
用TAO创建股票报价系统
Building a Stock Quoter with TAO- A Tutorial
本教程是围绕一个单一的应用程序组织的,它允许客户端程序可以通过报价服务获取股票报价。该应用程序是基于Doug Schmidt和Steve Vinoski为C++ Report杂志的对象互联专栏的序列文章而开发的。
这 篇教程从简单的客户程序及服务程序到剖析TAO的高级特性而逐步构建,这些高级特性包括了:asynchronous method invocation,reliable oneways, real-time Evnet Service, Interoperable Naming Service等等。
在您阅读本教程时,您可以自由地从提供的链接处获取到源文件。但是,如果您选择了编译和运行示例程序,这些源文件是作为您下载的TAO源文件的一部分。您可以从这下目录
$TAO_ROOT/docs/tutorials/Quoter
找到源代码,里面还包含了编译时需要的Makefile文件。由于本在线教有可能使用了与您不同版本的ACE+TAO源代码,因此单独下载这些源代码可能不能正确编译。
1.介绍—— 一个很简单的客户端
我 们将从一个相当简单的IDL接口开始我们的教程:我们想要创建一个股票报价服务,可以通过某些接口查询股票的价格。为了使学习之旅更加有趣,我们将用不同 的CORBA对象表示不同的股票。哦,这看上去有些夸张,但是这些趣味性会激发我们更多的学习欲望,尤其在学习开始的时候。
定义IDL接口
对于股票报价系统,最起码的操当属查询股票价格的操作了,表示如下:
interface Stock
{
double price();
};
但通常,股票有符号和全称,于是我们再增加两个属性来便于查询:
interface Stock
{
double price();
readonly attribute string symbol;
readonly attribute string full_name;
};
我们还需要某个接口,通过它可以根据股票的符号访问它的对象引用。习惯上,我们把这种接口称其为工厂(Factory),它看上去像这样:
interface Stock_Factory
{
Stock get_stock (in string stock_symbole);
};
请注意参数怎样具有方向性的:方向性用in,out, inout表示。仅输入用(in)表示,仅输出用(out)表示,既输入也输出用(inout)表示。到这里我们还有一个问题,如果股票的符号无效会怎么办呢?CORBA的做法是抛出一个异常:
exception Invalid_Stock_Symbol{};
我们再把异常作为Stock_Factory接口的一部分:
interface Stock_Factory
{
Stock get_stock (in string stock_symbole)
raise (Invalid_Stock_Symbol);
};
最后,我们把上面所有的IDL组织在一起并放进一个模块中以避免对名字空间的污染,于是得到了Quoter.idl 文件。
//filename: Quoter.idl
module Quoter
{
exception Invalid_Stock_Symbol {};
// Used to report an invalid stock name
// Forward declare the Stock interface
interface Stock;
interface Stock_Factory
{
// = TITLE
// A factory class for the stock quoter interfaces
//
// = DESCRIPTION
// Return the Quoter interfaces based on their names
//
Stock get_stock (in string stock_symbol)
raises (Invalid_Stock_Symbol);
};
interface Stock
{
// = TITLE
// A simple interface to query the name and price of stock
//
// = DESCRIPTION
// Return the price and name of a single stock
//
readonly attribute string symbol;
// Get the stock symbol.
readonly attribute string full_name;
// Get the name.
double price ();
// Get the price
};
};
生成的文件
让我们花几分钟来查看生成的代码。你无须经常这样做,事实上你根本无须这样做。但是我们这样做是为了学习的需要,并帮助您打消对IDL编译器的神密感。
为了生成代码,你必须调用IDL编译器,操作如下:
$ACE_ROOT/TAO/TAO_IDL/tao_idl Quoter.idl。
在Windows下,tao_idl.exe在%ACE_ROOT%/bin中。
%ACE_ROOT%/bin/tao_idl Quoter.idl
如果把%ACE_ROOT%/bin 添加至环境变量PATH中,则可以简化为
tao_idl Quoter.idl
IDL编译器完整的文档和选项都包含在compiler.html 中。
TAO为每个IDL文件生成9个文件。
QuoterC.h, QuoterC.inl 和 QuoterC.cpp 包含了客户端的接口。注意,内联(inline)函数放在独立的文件中,这样你可以有选择的编译它们,以便生成更小的代码。单纯的客户端只需要链接从QuoterC.cpp生成的目标文件。
与之相似,
QuoterS.h, QuoterS.inl 和 QuoterS.cpp 包含了服务端的skeletons。服务端必须链接从QuoterS.cpp和QuoterC.cpp生成的目标文件。
最后,
QuoterT.h, QuoterT.inl 和 QuoterT.cpp 包含TIE类。有基于组合的方式代替基于继承的skeleton标 准(在CORBA 2.2规范之后)。把它们分离在不同的文件中仅仅是因为有的编译器不能处理中同一个源代码文件中同时混合了模板和非模板的代码。因此你根据不需要在所有平 台上编译这些文件。然后,编译QuoterS.cpp需要这些文件。还要注意的事,如果您的平台不支持命字空间,那么对于有的IDL接口不能使用TIE方 法。
上面提到的扩展名和后缀可以通过使用IDL编译器的选择来修改,要知道更多的细节,可以查阅文档。另外还需要注意的事情是您需要在您的项目中使用一致的扩展名,否则在你的IDL源文件中使用某些#include指令时会出现问题。
创建一个简单的客户端
在IDL接口就绪之后,我们想要编写简单的客户端了。在所有的CORBA客户端或服务端要做的第一件事就是初始化ORB:
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! */
);
IDL支持在编译时长度未知的变长类型,因此在运行时需要动态分配它们。_var 类型减化了我们对变长类型显示内存管理,还隐藏了定长和变长结构化的类型之间的区别。
ORB 初始化可能失败,实际上,所有的CORBA操作都可能抛出CORBA::SystemException 异常,我们用 try/catch语句块来检查可能的失败。 不用说,这是非常天真的;某些失败是临时的,我们需要更好的方法从错误中恢复,但在我们的示例中这已经足够了。因此,在main()函数的最后,我们捕获 了CORBA的所有类型的异常。
}
catch (CORBA::Exception &ex)
{
std::cerr << "CORBA exception raised!" << std::endl;
}
return 0;
}
我们不能忘记ORB是一种资源,应用程序必须释放它。直到CORBA 2.3,并没有标准的方法来处理释放ORB这件中。TAO已经采纳了新的规范,于是,我们的客户程序将如下所示:
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! */);
// the real code goes here!
orb->destroy ();
}
catch (CORBA::Exception &ex) {
std::cerr << "CORBA exception raised!" << std::endl;
}
return 0;
}
关 于ORB名字,仅需要几句话来说明:如果在CORBA::init中使用了相同的ORB标识,规范要求ORB返回相同的ORB指针,并且,即使使用了不同 的标识,返回相同指针的实现也是自由的。通常这不是问题,这是由于大多数应用程序只实例化单一的ORB。TAO是实际支持多个ORB指针中少数CORBA 实现之一。这一点对于real-time应用程序非常重要,在这些应用程序里,每一个ORB可以在与不同的优先级执行。
既然我们已经拥 有了ORB指针,我们可以启动此应用程序了。正常情况下,我们使用Naming Service, Trading Service或者Interoperable Naming Service来定位股票工厂,但这里为了简化起见,让我们在第一个参数中使用IOR字符串。
最简单的方法是用第一个参数来得到字符串,然后用string_to_object()函数把它转换成对象引用。
CORBA::Object_var factory_object =
orb->string_to_object (argv[1]);
Quoter::Stock_Factory_var factory =
Quoter::Stock_Factory::_narrow (factory_object.in ());
_narrow()函数用于测试一个引用是否为指定的类型。如果引用是指定的类型,它返回一个非空的引用,否则返回为空。
关 于TAO有些有趣的事情:第一,对于对象引用,它支持file:scheme,因此,第一个参数可以是file://a_file_name. 在这种情形下,ORB将打开名为"a_file_name"的文件并从文件中读取IOR。TAO还支持corbaloc:scheme,例如
corbaloc:iiop:[email protected]:12345/Stock_Factory。
因此使用字符串可能是非常强大的引导协议。
如果我们要使用从IDL编译器生成的接口,那就就必须包含正确的头文件。
#include "QuoterC.h"
注意,这是您仅需要包含的头一个文件; IDL 编译器生成的代码包含了所有需要的内部头文件。当我们使用TAO服务时,也不要忘了包含那些适当的头文件。
TAO 另一个有趣的特性是它支持 _unchecked_narrow()。 这是CORBA Messaging规范的一部分,在本质上它与_narrow()执行同样的工作,但不同的时,它并不远程的检查类型。如果你在编译时有必要的信息能确定 narrow操作的正确性,那么使用不检查的版本会更高效。
现在我们可以使用剩余的参数来找回股票对象:
for (int i = 2; i != argc; ++i) {
try {
// Get the stock object
Quoter::Stock_var stock =
factory->get_stock (argv[i]);
练习1
完成客户端的实现。这一点应该很容易,但是它给了你机会来建立你的开发环境并能让你熟悉构建TAO应用程序的基础知识。
解决方案
查看 client.cpp文件;和你的代码比应该没有多少不同。在你的客户端代码、idl文件和QuoterC.*文件中分别数一下代码的行数,你会想再一次重写所有的代码吗?
测试
若要测试本程序,我们还需要服务端能工作,这是我们进入本教程下一课最好的理由
二. 简介——非常简单的服务端
2.简介——非常简单的服务端
既然我们已经知道如何实现一个简单的客户端,那么现在我们得实现一个服务端来测试它。我们必须提供Stock和Stock_Factory这两个接口的实现,并且创建可执行的应用程序把这两个接口的实现组合在一起。
实现Stock (股票)接口
为了简单起见,让我们用固定的价格来实现Stock对象。构造函数将接收所有的参数:
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_;
};
在一个服务器中,用编程语言的数据和函数来实现和表示CORBA对象和函数。这些用于实现和表示CORBA对象的编程实体称为伺服代码(servants)。对象适配器(Object Adapters )把CORBA对象的世界和编程语言的伺服代码连接在一起,还提供CORBA对象的创建服务和它们的对象引用以及将请求分发到适当的伺服代码之上。
请 注意基类的名称。TAO实现了CORBA 2.2版本的规范,这个规范包含了Portable Object Adapter(因此前缀为POA)。这个新的对象适配器更正了以前版本的CORBA规范的许多问题,之前使用的的对象适配器于是被称为Basic Object Adapter。不幸的事,该规范含混不清,导致了不兼容的实现。由于遵循CORBA 2.2规范,基于POA的代码几乎是完全可移植的,不可移植的地方仅仅是生成的头文件的名称和其它细微的地方。这些问题很容易通过使用帮助类中进行包装来 解决,大多数情况下,生成的文件名可以通过控制IDL编译选项的方式得以处理。
一个服务端程序可能包含了多个POA实例,但所有的服务端程序至少有一个称为RootPOA的POA。
我们必须实现的操作和属性。
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 ());
}
其它属性和成员与此相似,因此我们没有在这里列出来。
对参数的内存管理规则
因 为ORB将调用CORBA::string_free释放字符串,所以在返回前复制它们是很重要的。 基本的原理是通过网络,字符串必定会被某种方式进行复制,因此,客户端必须担当起释入接收到的字符串的责任。当客户端和服务端程序在同一地址空间的时 候,ORB可以优化路径和直接调用服务端的操作而无须编码和解码。如果客户端同时与本地和远程服务端同时工作,它总会期待它自己的字符串。因此,服务端的 实现必段分配一份拷贝并返回此拷贝,这是因为服务端对本地或远程服务端一视同仁。虽然在CORBA的世界里,对内存管理的规则比较狡猾,但这里有一些可以 遵照的简单原则:
完整的内存管理规则可以在Henning 和Vinoski的书中找到,也可以在CORBA规范中找到。
录入所有的代码看上去很繁琐,可否让IDL编译器帮助我们?毕意,看上去方法的声明已被作了详细的说明。答案是肯定的。TAO的IDL编译器可以生成空白的实现,而你要做的只是简单在其上作修改。生成空白的实现只需要使用-GI 选项。
$ $ACE_ROOT/TAO/TAO_IDL/tao_idl -GI Quoter.idl
空白的实现生成在QuoterI.h 和QuoterI.cpp文件中。要注意的是用了 -GI选项后在每次生成文件时总会覆盖这些文件,所以好的方法是把它拷贝成别的名称。
Stock Factory的实现
我们工厂类的第一次实现将只为两支股票提供服务,一个是“红帽公司(RHAT)”的,一个是“微软公司的(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_;
};
get_stock()方法实现很简单,仅仅是比较符号名并返回适当的对象引用。
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 ();
}
那 这里的 _this()方法是干什么的呢?在POA的映射中,通过继承,客户端的Stub和服务端的skeleton是互不关联的。你必须要么用显示的方式激活伺 服(servant)(你实现的对象),要么用_this(),通过默认的OA._this()在RootPOA下面创建和注意CORBA对象,然后为新 对象返回对象的引用。在后面我们还为就显式和隐式对象激活展开更多的讨论,移除关于转换对象到伺服再到对象引用或反过来都会使服务端不能正常工作,明确这 这一点上是很重要的。
实现工个CORBA服务端
现在在适当的位置我们已经有了对象的实现,下一步就是要创建服务端的可执行文件。我们从ORB的初始化开始。
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! */);
一开始,ORB开始于 POA的 {holding state},直到POA激活前,所有收到的请求都不会被处理。同时,这些请求保存于一些实现的限制中。TAO把这个限制设为0,因为在实时系统中把请求放入队列是过载和不可预测的重要来源。
对我们来说这意味着什么呢?恩,那我们必须激活POA。这个过程有点繁琐。首先我们需要得到RootROA:
CORBA::Object_var poa_object =
orb->resolve_initial_references ("RootPOA");
PortableServer::POA_var poa =
PortableServer::POA::_narrow (poa_object.in ());
resolve_initial_references()用于启动所有类型的服务,比如:Naming Service和Trading Service,同时还用于获取其它的ORB接口,比如 RootPOA,Current Oject和Policy Manager。
既然我们获得了Root POA,我们就必须取回它的POA管理器。POA管理器提供激活和休息一个或多个POA的接口。
PortableServer::POAManager_var poa_manager =
poa->the_POAManager ();
那么,我们激活POA:
poa_manager->activate ();
之后的处理过程与客户端相似,但现在要记住的是销毁POA,把所有的代码合在一起,我们得到:
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;
}
现在我们创建一个股票工厂实现的实例并用_this()来激活它。
Quoter_Stock_Factory_i stock_factory_i;
Quoter::Stock_Factory_var stock_factory =
stock_factory_i._this ();
接下来把对象引用转换为IOR字符串,客户端会使用这个字符串。
CORBA::String_var ior = orb->object_to_string (stock_factory.in ());
std::cerr << ior.in () << std::endl;
这里只有一个最后的细节,我们必须运行ORB的事件循环来开始处理来自客户端的请求。
orb->run ();
从这个服务端我们拿掉了去多细节,比如如何中止事件循,如何执行伺服的内丰是,按顺序使伺服休眠,实际上这非常不灵活,但我们已经涵盖了许多其它的事情,最重要一点是我们可以测试客户端了。
练习 1
把实现丰满直民来。你无须从0开始,我们提供了这些文件:Stock_i.h, Stock_i.cpp, Stock_Factory_i.h Stock_Factory_i.cpp, Quoter.idl 以及非常有用的MPC file 。
解决方案:
你的解决方案同Server.cpp文件作比较。
测试
我们需要客户端来测试这个应用程序。我们只需要同时运行:
$ server > ior_file
$ client file://ior_file MSFT RHAT
再测试一下无效的符号!
三:介绍-改进服务端,通过POA策略之显示激活和用户自定义对象ID
3.介绍-改进服务端,通过POA策略之显示激活和用户自定义对象ID
介绍——改善服务端
在本节,我们将在之前提到的“简单服务器”之上进行改进。我们将会讨论如何使把POA策略赋给对象ID。
在 先前的示例中,我们用了 Quoter_Stock_Factory_i 的两个域来表示两支股票。如果我们想创建上百支股票,那么这种方法就缺少扩展性。我们需要用某个集合来保持对股票对象的跟踪,比如为股票符号建立索引。一 种解决办法是使用STL map或与之差不多的集合类,但这样明显太浪费了。毕意,ORB已经以对象ID为索引建立了对象的集合。如果仅能自己选择ID,那么我们的问题就能得已解 决。一个好消息是:POA允许这样做;一个坏消息是我们必须创建子POA。那这是为什么呢?这是因为RootPOA的ID是被ORB分配了的,我们不想要 与它们冲突。此外,创建一个独立的POA比管理它更容易,因为多个应用程序的组件可以获得它们自己的POA,它们能被视为私有的命字空间。
子POA的创建
如前所述,我们可以访问RootPOA:
CORBA::Object_var poa_object =
orb->resolve_initial_references ("RootPOA");
PortableServer::POA_var poa =
PortableServer::POA::_narrow (poa_object.in ());
现在我们为子POA创建策略。在这个应用场景下,我们想使用USER_ID策略,这样就可以把它赋自己的这些ID。我们还想使用 NO_IMPLICIT_ACTIVATION 策略,以便于对我们的POA有更多的控制。POA具有许多我们可以控制的策略,但我们在示例中只使用默认的策略。在$TAO_ROOT/examples/POA/目录下有许多示例演示了如何使用这些策略。
策略存储在序列中的,所以我们首先创建序列并初始化它的长度。
CORBA::PolicyList policies (2);
policies.length (2);
接下来我们创建策略:
policies[0] =
poa->create_id_assignment_policy (PortableServer::USER_ID);
policies[1] =
poa->create_implicit_activation_policy (PortableServer::NO_IMPLICIT_ACTIVATION);
再然后我们创建子POA:
PortableServer::POA_var stock_factory_poa =
poa->create_POA ("Stock_Factory_POA",
poa_manager.in (),
policies);
注意的是,我们与RootPOA共用了POA管理器,于是我们只需要用一个POA管理就可以控制这两个POA的状态。新的POA复制了策略,所以我们为了避免内存泄漏,所以需要销毁它们。
for (CORBA::ULong i = 0; i != policies.length (); ++i)
{
policies[i]->destroy ();
}
在子POA中激活对象
现在我们必须使用这个POA来激活股票对象了。为了使示例简化,我们假设我们可以从标准输入设备(stdin)中读入这些股票信息,比如这样:
while (!std::cin.eof () && std::cin.peek () != EOF)
{
const int max_symbol_length = 8;
char symbol[max_symbol_length];
const int max_full_name_length = 64;
char full_name[max_full_name_length];
double price;
std::cin.getline (symbol, max_symbol_length, '/n');
std::cin.getline (full_name, max_full_name, '/n');
std::cin >> price;
std::cin.ignore (1, '/n');
// The interesting stuff goes here!
}
为每个符号、全称和价格的三元组中, 我们创建股票实现的对象:
PortableServer::ServantBase_var servant =
new Quoter_Stock_i (symbol, full_name, price);
ServantBase_var 充当的角色很象自动指针,在发生异常的情况下,它可以小心的管理对象的回收操作。这次我们不能使用_this() 来激活对象,这是因数我们要想创建自己对象ID:
PortableServer::ObjectId_var oid =
PortableServer::string_to_ObjectId (symbol);
然后我们通过id来激活对象:
stock_factory_poa->activate_object_with_id (oid.in (),
servant.in ());
请小心,不要在这些对象上调用_this(),如果这样就会在RootPOA中激活它们,被激活了两次!虽然在不同的POA中(甚于有时还在同一个POA中)多次激活一个对象是完全合法的,它是在现在的应用情景中我们并不需要这样做。
修改Stock Factory
现在我们必须实现Stock Factory的不同版本。我们传一个子POA的引用到构造函数中并维护这个引用:
class Quoter_Stock_Factory_i : public POA_Quoter::Stock_Factory
{
public:
Quoter_Stock_Factory (PortableServer::POA_ptr stock_factory_poa)
: stock_factory_poa_ (PortableServer::POA::_duplicate (stock_factory_poa))
{}
Quoter::Stock_ptr get_stock (const char *symbol)
throw (Quoter::Invalid_Stock_Symbol);
private:
PortableServer::POA_var stock_factory_poa_;
};
注意,我们复制了POA,这样才遵从惯用的CORBA对输入参数的内存管理规则。尽管构造函数并不是CORBA操作,这样做我们可以把这个规则使用得更广泛,如果我们坚持使用CORBA这套规则时可以减少误解。
get_stock 操作的实现更为有趣。首先我们依据符号创建对象的ID:
Quoter::Stock_ptr
Quoter_Stock_Factory_i::get_stock (const char *symbol)
throw (Quoter::Invalid_Stock_Symbol)
{
PortableServer::ObjectId_var oid =
PortableServer::string_to_ObjectId (symbol);
接下来在POA中查找对象的ID:
try {
CORBA::Object_var tmp =
this->stock_factory_poa_->id_to_reference (oid.in ());
最后将对象的引用narrow到正常的类型并返回它:
return Quoter::Stock::_narrow (tmp.in ());
}
要是符号无效,POA就找不对正确的对象ID,只好抛出异常:
catch (PortableServer::POA::ObjectNotActive &) {
throw Quoter::Invalid_Stock_Symbol ();
}
}
股票对象的内存管理
迄 今为止,我们还尚为讨论伺服代码(servants)的内存管理问题。现在是讨论它的最好时机了,因为股票对象已完全授控于POA了。POA为伺服代码提 供了引用计数。你并不需要使用引用计数,如果你遵从了这一点,那绝大多数的内存管理就相当的简单了。那为什么不是所有的时候都可以不使用引用计数呢?那是 因为有的应用程序不需要它。举例来说,我们先前的简单服务不需要任何复杂的对象管理,于是所有的对象都是在栈(stack)上创建的。
如果想在POA中使用引用计数,你必须重载_add_ref() 和remove_ref() 这两个方法用来增加或减少引用的数目。 一旦数目回到0,你就可以安全的删除对象(但要记住,计数数目是从1开始的!)。
把这些方法实现为线程安全是一件繁琐的工作。为了减化这个工作,我们混合使用了PortableServer::RefCountServantBase 这个作为基类,像这样:
class Quoter_Stock_i
: public virtual POA_Quoter::Stock,
public virtual PortableServer::RefCountServantBase
{
public:
Quoter_Stock_i (const char *symbol,
const char *full_name,
CORBA::Double price);
// as before
};
TAO中PortableServer::RefCountServantBase 的实现也是线程安全的,于是你可以用这个技术在你的多线程服务中动态的销毁对象。您简单的委托POA来控制,一旦所有线程调用了对象中止,当休眠该对象 时,POA将会调用 _remove_ref(),这样如果这个对象还在使用中就不会被删除。也请记住如果你要使用对象请增加它的引用记数。
练习
从简单服务的文件作如下修改:
你能够使用相同的Quoter.idl, Stock_i.cpp 和MPC 文件.
解决方案
在你的解决方案在同下面的文件作比较:
Does this solution scale when the number of stock symbols is in the thousands or millions? Find out about Servant Locators and Servant Activators in the POA!
Testing
有一个简单的输入文件可用. 你可以使用简单客户端来检查结果:
$ server < stock_list.txt > ior_file
$ client file://ior_file AAAA BBBB MSFT RHAT CCCC
也测试一下无效的符号!
More Reading
The Henning and Vinoski CORBA book discusses POA policies in detail. Likewise, the Schmidt and Vinoski columns in C++ Report also include several articles about the POA. Finally, the TAO distribution includes examples that illustrate how to use the POA policies.
四:改进服务端之通过POA策略实现持久化的对象引用
改进服务端之通过POA策略实现持久化的对象引用
介绍-改进服务器
在本节中,我们将改进之前写的简单服务器(第二篇)。我们要利用POA策略来创建具有持久化对象引用的对象。
在 创建POA时为其指定策略可以用于控制POA的特性。POA的策回略都具有相同的形式:在创建时使用枚举类型为它指定值。在我们的示例中,我们要使用 LifeSpanPolicy这个策略控制对象引用的生命期和创建它的对象的POA的生命期之间的关系;我们还要使用 IdAssignmentPolicy,它可以控制如何给对象ID赋值。
CORBA对象存在的进程如果与创建它或激活它的进程无关,我们称这种对象为持久化的对象。同样地,如果CORBA对象的生命期被绑定在创建它的POA所在的进程中,这种对象被称为暂时对象。 注意,这一点与对象的状态没有关系:一个应用程序可以创建暂时对象来访问在数据库中维护的持久化的信息。例如,有这样的对象可以用来表示不同的连接会话或 数据的交易视图。类似的,有的持久化对象可能没有状态或持久的状态。例如,因为日志设备是持久化的,所以它总有效,但它可以不必维护状态或只是简单的为当 前的活动缓存一些状态而已。总而言之,具有持久化状态的对象总是通过持久化对象的引用来访问的。
RootPOA的标准生命期策略是短暂 的(TRANSIEN)。这意味着如果应用程序试图支持持久化对象必须至少创建另外一个支持持久生命期策略的POA。在我们的例子中,我们将为子POA创 建两个策略。一个策略是LifeSpanPolicy,它会被设为持久化的(PERSISTENT)。习惯上,创建持久对象引用的应用程序还会设置 IdAssignmentPolicy,所以这些应用程序可以以一种可预测的方式分配对象ID,与服务端的激活一致。使系统ID具有持久化对象引用虽然也 是可以的,但这样用非常少见。
子POA的创建
和以前一样,我们先初始化ORB,再为RootPOA找回引用。
CORBA::ORB_var orb = CORBA::ORB_init (argc, argv);
CORBA::Object_var poa_object =
orb->resolve_initial_references ("RootPOA");
PortableServer::POA_var poa =
PortableServer::POA::_narrow (poa_object.in ());
接下来我们找回RootPOA的POA管理器好用它来激活RootPOA。
PortableServer::POAManager_var poa_manager =
poa->the_POAManager ();
poa_manager->activate ();
然后我们用PERSISTENT来创建LifeSpanPolicy 对象。
// Create a PERSISTENT LifespanPolicy object
PortableServer::LifespanPolicy_var lifespan =
poa->create_lifespan_policy (PortableServer::PERSISTENT);
and next we create an IdAssignmentPolicy object with the USER_ID value:
// Create a USER_ID IdAssignmentPolicy object
PortableServer::IdAssignmentPolicy_var idassignment =
poa->create_id_assignment_policy (PortableServer::USER_ID);
再一步,我们初始化策略的序列:
CORBA::PolicyList polices (2);
policies.length (2);
policies[0] =
PortableServer::IdAssignmentPolicy::_duplicate (idassignment);
policies[1] =
PortableServer::LifespanPolicy::_duplicate (lifespan);
在父POA上使用create_POA操作创建子POA。
PortableServer::POA_var child_poa =
poa->create_POA ("childPOA",
poa_manager.in (),
policies);
传给create_POA操作的参数是:子POA的名称、子POA的管理器和CORBA策略列表(CORBA::PolicyList)。我们能够 通过传递空引用(nil reference)创建一个被新的POA管理器(POAManager)控制的子POA,但通常情况下使用父POA的POA管理器。
最后一步,我们可以销毁生命期策略和ID分配策略,因为我们不必在用它们了。create_POA操作将在策略列表中复制对象,并且新创建POA将引用传递给create_POA的对象的拷贝。
idassignment->destroy ();
lifespan->destroy ();
激活在子POA的对象
既然我们已经创建了新的POA,那让我们所用这个POA来激活股票对象。第一步将要创建股票工厂实现的实例。
// Create a servant of class Quoter_Stock_Factory_i
Quoter_Stock_Factory_i stock_factory_i;
可以使用activate_object_with_id () 显示的激活对象。这个对象有两个输入参数:对象的ID和实现它的伺服代码的指针。
PortableServer::ObjectId_var oid =
PortableServer::string_to_ObjectId ("Stock_Factory");
第二步,我们可以激活“股票工厂”对象:
child_poa->activate_object_with_id (oid.in (),
&stock_factory_i);
这个操作并不返回新对象的对象引用,但我们可以用id_to_reference 操作找该对象的引用:
CORBA::Object_var stock_factory =
child_poa->id_to_reference (oid.in ());
与之前一样,我们把对象引用转换成IOR字符串以便客户端可以使用它。
CORBA::String_var ior = orb->object_to_string (stock_factory.in ());
std::cout << ior.in () << std::endl;
我们已经知道了,在能处理客户端的请求的最后一步是要运行ORB事件循环。直到中止时析构时最后我们销毁POA。
orb->run ();
// Destroy the POA
poa->destroy (1,1);
orb->destroy ();
练习
修改在简单服务器中的 server.cpp 文件来创建持久化的子POA。你还可以使用相同的Quoter.idl Stock_i.h Stcok_i.cpp Stock_Factory_i.h Stock_Factory_i.cpp 文件和使用MPC file文件。
解决方案
你的文件和server.cpp 文件作比较.
测试
你可以使用client.cpp 来检查结果,如下操作:
$ ./server -ORBEndPoint iiop://localhost:12345 > server.ref &
正常地,ORB随机的选择侦听的终端。这一点对于具有持久化对象引用的应用程序就不适合了。这是因为如果服务器重启会选择一个新的侦听端口,原来的 引用就变为无效了。在TAO中,我们可以使用 -ORBEndPoint选项来控制侦听的端口。比如,对于IIOP协议,终端信息包括了主机的机器名字或IP地址和一个可用的TCP端口号。在下一节 中,我们将学习使用实现仓库,它和持久化的对象一起工作,这样就不必显示地设置侦听的端口了。
客户端还是和以前那样执行:
$ ./client file://server.ref MSFT RHAT
为了测试POA的持久化,让我们关掉服务器,然后将对象引用转化为新的foo.ref。
$ kill %1
$ ./server -ORBEndPoint iiop://localhost:12345 > foo.ref &
[2] 6941
如果我们再次运行客户端,我们必定会从服务器得到与先前一样的结果。
$ ./client file://server.ref MSFT RHAT
如果我们不告之服务器在相同的端口上侦听会发生什么呢?让我们和之前一样来运行新的服务端:
$ ./server > server.ref &
[1] 23897
$ ./client file://server.ref MSFT RHAT
The price of a stock in "RedHat, Inc." is $210
The price of a stock in "Microsoft, Inc." is $91
$ kill %1
$ ./server > foo.ref &
[2] 23908
$ ./client file://server.ref MSFT RHAT
CORBA exception raised!TRANSIENT (IDL:omg.org/CORBA/TRANSIENT:1.0)
一个CORBA TRANSIENT 异常抛出来了。这表明执行请求必须的某些资源不可用。在这种情况下客户端的ORB不能在期望的端口号上找到服务端。没有实现仓库,客户端ORB不能在新的 端口里定位到服务器。必须假设服务端会临时关掉或在后来重启,这都会引发TRANSIENT异常。
More Reading
The Henning and Vinoski CORBA book discusses POA policies in detail. Likewise, the Schmidt and Vinoski columns in C++ Report also include several articles about the POA. Finally, the TAO distribution includes several examples that illustrate how to use the POA policies.
五:实现库(Implementation Repository)
实现库(Implementation Repository)
在先前的示例中,客户端与服务端必须至少联系一次。如果服务器被移动到不同的主机或端口,或服务器已关掉,服务器与客户端之间的绑就定会失败。通过外在的定位代理间接绑定,像实现仓库可以解决上述问题。
实现仓库维护一个数据结构,这个数据结构作为保存跟踪服务器的服务表。它维护已知服务的注册表、正在运行的服务器的记录和对应的端口号,并且如果服务器已注册,还可以根据需要启动服务器。
当 服务器创建一个持久化的时候,它设置地址和端口号到IOR的profile body中,这是为了实现响应服务器的实现仓库。当客户端使用这个IOR的时候,如果实现仓库没有关掉,它就与这个实现仓库进行连接。仓库解码它的IOR 和使用根据对象key从服务表在索引到的POA的名称。仓库返回当前实际服务器的地址信息。
在这个示例中,让我们继续修改我们先前的股票工厂来支持通过实现仓库的间接绑定。
我们必须做的唯一事情是把我们的子POA与实现仓库注册在注册。
orb->_tao_add_to_IOR_table ("childPOA", stock_factory.in ());
然后,我们和往常一样将对象引用字符串化后打印出来。
CORBA::String_var ior = orb->object_to_string (stock_factory.in ());
练习
修改在简单服务器中的 server.cpp 文件用于创建持久化的子POA。您可以使用相同的 Quoter.idl Stock_i.h Stock_i.cpp Stock_Factory_i.h Stock_Factory_i.cpp Client.cpp ,您可以使用MPC 文件。.
解决方案
把您的server.cpp 与 server.cpp 文件作比较。
测试
为了测试您的修改,您需要运行四个程序。第一步是启动TAO提供的实现仓库。实现仓库被包括至少一个定位器和一个激活器。定位品为期望使用仓库的应用程序(在这里指客户端)提供可见的接口,而激活器执行激活和维护服务器的实际工作。
首先,我们启动定位器。我们需要输出ImplRepo_Service的IOR到一个文件中,以便于激活器和客户端都能找到定位器。
$ $TAO_ROOT/orbsvcs/ImplRepo_Service/ImplRepo_Service -o implrepo.ior -d 0 -ORBobjrefstyle URL &
其次,我们启动激活器,指向定位器的IOR文件。
$ $TAO_ROOT/orbsvcs/ImplRepo_Service/ImR_Activator -ORBInitRef ImplRepoService=file://implrepo.ior
ImR Activator: Starting doc.ece.uci.edu
ImR Activator: Registered with ImR.
再次,我们要用实现库注册我们的服务器。我们可以使用TAO提供应用程序 $TAO_ROOT/orbsvcs/ImplRepo_Service/tao_imr 来添加我们的服务器到实现仓库中的服务器数据库中。
$ $TAO_ROOT/orbsvcs/ImplRepo_Service/tao_imr -ORBInitRef
ImplRepoService=file://implrepo.ior add childPOA -c
"./server -ORBUseIMR 1 -ORBobjrefstyle URL -ORBInitRef
ImplRepoService=file://implrepo.ior"
Successfully registered server
在Windows平台中,用下面的命令行往实现仓库中注册服务器。
$ACE_ROOT/bin/tao_imr -ORBInitRef ImplRepoService=file://implrepo.ior
add childPOA -c "./server -ORBUseIMR 1 -ORBobjrefstyle URL
-ORBInitRef ImplRepoService=file://implrepo.ior"
我们指定-ORBInitRef选项来使用在implrepo.ior 文件中的IOR和指定-ORBUseIMR 选项分辩服务器启动还是关闭的通知而使用的IMR。
第二步是为与IMR一起使用的我们的服务器生成简单的IOR,使用ior选项,并写进stock_factory.ior文件。
$tao_imr -ORBInitRef ImplRepoService=file://implrepo.ior ior
childPOA -f stock_factory.ior
corbaloc:iiop:[email protected]:2690/childPOA
从现在开始,实现库用于对我们服务器的跟踪已全部设置好了并执行我们服务器必要的有关方法调用。
现在,向以前哪样执行客户端。
./client file://stock_factory.ior MSFT RHAT
The price of a stock in "Microsoft, Inc." is $91
The price of a stock in "RedHat, Inc." is $210
为了测试POA的持久化,让我们关掉服务器然后运行客户端。
$tao_imr -ORBInitRef ImplRepoService=file://implrepo.ior
shutdown childPOA
Successfully shutdown server
./client file://stock_factory.ior MSFT RHAT
The price of a stock in "RedHat, Inc." is $210
The price of a stock in "Microsoft, Inc." is $91
更多的阅读资料
欲知更多的实现库,请点这里 here。
The Henning and Vinoski CORBA book discusses POA policies in detail. Likewise, the Schmidt and Vinoski columns in C++ Report also include several articles about the POA. Finally, the TAO distribution includes examples that illustrate how to use the POA policies.
六:TAO的命名服务
TAO的命名服务
迄今为止我们是用的string_to_object() 和object_to_string() 来引导客户端启动的。如果系统要用于真正的分布式环境,我们就不能依靠共享分件系统或让用户输入IOR来初始化客户端了。为了达到这个目的,CORBA提 供了几种定位服务。其中最简单的服务是CORBA命名服务。有许多好的教程讲述了如何使用命名服务,另外在Henning 的Vinoski 的书中还详细讨论了命名服务的问题。
在本节中,我们只会涉及到命名服务的简单使用,会集中到如何配置和引导命名服务的引导启动上。包括了TAO支持的可交互式命名服务(Interoperable Naming Service)。
在命名服务中注册对象
首先我们修改服务器以便于把股票工厂和命名服务注册。这需要包含正确的头文件:
#include "orbsvcs/CosNamingC.h"
我们使用下面的方法重新激活股票工厂:
// Activate it to obtain the object reference
Quoter::Stock_Factory_var stock_factory =
stock_factory_i._this ();
我们需要找回命名对象的引用,这步操作使用resolve_initial_references() 来完成。
CORBA::Object_var naming_context_object =
orb->resolve_initial_references ("NameService");
CosNaming::NamingContext_var naming_context =
CosNaming::NamingContext::_narrow (naming_context_object.in ());
接下来我们初始化要为对象赋值的名字。命名服务服为名字使用结构的序列——可以想像为把路径分解为目录名。在本例中我们使用简单的名字。在产品化的环境中使用有更好组织层次的命名服务可能是更需要的。第一步是创建和初始化序列:
CosNaming::Name name (1);
name.length (1);
接下来初始化名字:
name[0].id = CORBA::string_dup ("Stock_Factory");
现在我们准备把对象引用注册到命名服务中:
naming_context->bind (name, stock_factory.in ());
注意,如果在命名服务中已注册了这个名字,那么 bind() 会失败。这时,您可以使用 rebind()覆盖原来的值。
查找对象
现在我们可以通过命名服务来定位对象。用于替代对命令行的依赖,我们必须执行相同的调用来定位命名服务和初始化我们要查找的对象的名名字:
CORBA::Object_var naming_context_object =
orb->resolve_initial_references ("NameService");
CosNaming::NamingContext_var naming_context =
CosNaming::NamingContext::_narrow (naming_context_object.in ());
CosNaming::Name name (1);
name.length (1);
name[0].id = CORBA::string_dup ("Stock_Factory");
接下来我们可以解析这个名字:
CORBA::Object_var factory_object =
naming_context->resolve (name);
Quoter::Stock_Factory_var factory =
Quoter::Stock_Factory::_narrow (factory_object.in ());
到这一步我们就可以像以前那样使用它了。
练习 1
完成对文件 server.cpp的变更。
为了完成和测试你的实现,您可以使用下面的文件: Quoter.idl, Makefile, Stock_i.h, Stock_i.cpp, Stock_Factory_i.h Stock_Factory_i.cpp。为了更有趣,您也可以修改原始的 client.cpp 文件。第一个参数怎么样?现在我们还需要IOR么?
解决方案
把您的解决方案与文件 client.cpp 和server.cpp作比较。它们应该很相似。
测试
为了测试您的变更需要运行四个程序。第一个在您的局域网中配置TAO的命名服务查寻协议以便使用唯一的端口。这个端口基于您的用户ID是一个好主意。例如:
$ setenv NameServicePort `expr 10000 + $uid`
现在我们可以启动TAO提供的命名服务了:
$ $TAO_ROOT/orbsvcs/Naming_Service/Naming_Service
以及您的服务端:
$ server
最后是您的客户端:
$ client MSFT RHAT RHAT MSFT
找寻命名服务
那么TAO怎么找寻命名服务的呢?直到近期也没有标准的方式来配置命名服务器如何启动。在TAO中我们决定使用多播来定位服务端。多播协议虽然简 单,但在不是有许多命名服务运行的小的局域网中工作还是工作得非常好。为了避免当在同一个局域网中运行多个命名服务的问题时,像上面那样,您必须为每个服 务指定不同的多播端口。
不幸的是,上面的协议并不能在跨多个局域网中工作,并且很难保证在多播端口的分配上不冲突。TAO支持交互多命名服务规范,该规范提供了许多机制用 于控制resolve_initial_references() 调用的行为。例如:您可以让命名服务输出它的IOR到文件中,像下面这样。
$ $TAO_ROOT/orbsvcs/Naming_Service/Naming_Service -o ns.ior
然后用-ORBInitRef 用那个IOR文件代替多播协议:
$ server -ORBInitRef NameService=`cat ns.ior`
或者更好的方法使用file:scheme的方式直接读取文件:
$ server -ORBInitRef NameService=file://ns.ior
但这依然假定在主机之间存在共享的文件系统,或者需要用户跨过网络复制文件。如果我们知道接受IIOP请求的命名服务使用的主机和端口,那么我们就可以使用corbaloc: scheme 方式:
$ server -ORBInitRef NameService=corbaloc:iiop:ace.cs.wustl.edu:12345/NameService
实际上,对任务TAO程序(包括了命名服务)的主机和端口的控制很简单。仅仅要求您使用 -ORBEndPoint option选项就行了。
$TAO_ROOT/orbsvcs/Naming_Service/Naming_Service -ORBEndPoint iiop://ace.cs.wustl.edu:12345
当然,它只能够当您在主机ace.cs.wustl.edu 上运行您的程序,并且端口12345可用的前提下或以工作。您可以使用神奇的端口号0让ORB为您寻找一个可用的端口,实际上这正好是TAO默认这样执行的。
最好,你可以使用多个-ORBEndPoint 选项在多个端点上侦听。在对一有多个地址的主机非常有用。
练习2
试着使用不同的方法查寻命名服务。再试着为命名服务指派一个无效的IOR运行服务器。如果服务器或客户端通过多播协议选取不同的命名服务会怎么样呢?如果他们的配置与期望的命名服务器不一致会怎么样呢?
持久化问题
如果命名服务在对象注册和查询之间关掉了会怎么样呢?默认情况下TAO的命名服务不是持久化的,但解决办法是使用标记把状态存放文件中:
$ $TAO_ROOT/orbsvcs/Naming_Service/Naming_Service -f name_service.dat
注意:除非您的服务使用了持久化,或者它可以自动重启,本节的示例就没有多大的用处。解决持久化是实现仓库的职责。
七:异步方法调用——针对急迫的( impatient )客户端的CORBA解决方案
异步方法调用——针对急迫的( 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的开发者指南中。
八:按需激活
伺服(Servant)管理器
在先前的示例中,我们对使用同步方法调用的简单客户端进行了扩展,用于处理异步请求:通过使用回复处理器的异步方向调用。
在应用程序中有许多对象,没有必要在同时激活所有的对象,并且,如果同时激活所有对象会导致占用过多的内存或过多的数据库查询。为了解决这类应用程序面临的问题,POA提供了一个选项让应用程序提供伺服管理器,伺服管理器可以在每一个请求上动态的提供伺服。
伺 服管理器是一个回调对象,应用程序把它和POA注册在一起。当POA试图确定与特定请求关联的伺服是,它回调应用程序的伺服管理器来找回伺服。想要把伺服 管理器与POA注册在一起,就要把控制请求与伺服关系的这个RequestProcessingPolicyValue设置为 USE_SERVANT_MANAGER。
根据POA是否把对象与伺服的关联是否保存在它的主动对象映射表(Active Object Map),我们把伺服管理器分为两种类型。这取决于当POA创建时为ServantRetentionPolicy设的值。如果设为 RETAIN,那么POA保留关联,如果设为NON_RETAIN,POA则不在对象和伺服中保留任何关联。
在POA是具有RETAIN 的情况下伺服管理器必须激活与对象关联的伺服。这需要伺服管理器对象支持ServantActivator 接口。在POA是具有NON_RETAIN 的情况下,我们的伺服管理器对象将能够为请求的对象找到伺服并调用它。
在本示例中,让我们使用伺服的定位器来当在象被调用时来定位与我们的对Stock_Factory对象关联的伺服。
The Stock Factory 定位器的实现
我们的Stock_Factory_Locator_i将帮助我们找到Quoter_Stock_Factory伺服。
伺 服定位接口提供两个操作:调用前和调用后(preinvoke 和postinvoke)。preinvoke 操作被调用时用于找回伺服来分发请求。preinvode返回的伺服仅用于单一的请求。postinvode操作后来被调用,用于销毁被 preinvode操作创建的伺服。
#include "tao/corba.h"
class Quoter_Stock_Factory_Locator_i : public POA_PortableServer::ServantLocator
{
public:
Quoter_Stock_Factory_Locator_i (CORBA::ORB_ptr orb);
// Preinvoke function
virtual PortableServer::Servant preinvoke (const PortableServer::ObjectId &oid,
PortableServer::POA_ptr poa,
const char * operation,
void * & cookie);
// Postinvoke function
virtual void postinvoke (const PortableServer::ObjectId & oid,
PortableServer::POA_ptr poa,
const char * operation,
void * cookie,
PortableServer::Servant servant);
private:
CORBA::ORB_var orb_;
};
In the implementation of the preinvoke operation, we check if the object ID is valid and then check for the servant we want to create and create and return the requested servant.
在 preinvoke 操作的实现中,我们检查对象的ID 是否有效,然后检查我们想要创建的伺服和返回请求的伺服。
PortableServer::Servant
Quoter_Stock_Factory_Locator_i::preinvoke (const PortableServer::ObjectId &oid,
PortableServer::POA_ptr poa,
const char * operation,
void * & cookie)
{
try {
// Get the ObjectID in string format
CORBA::String_var oid_str =
PortableServer::ObjectId_to_string (oid);
// Check if the ObjectId is valid
if (strcmp (oid_str.in (), "Quoter/Stock_Factory") != 0) {
// Create the requested servant.
PortableServer::Servant servant =
new Quoter_Stock_Factory_i ();
cookie = servant;
return servant;
}
else {
throw CORBA::OBJECT_NOT_EXIST ();
}
}catch (const CORBA::BAD_PARAM &) {
throw CORBA::OBJECT_NOT_EXIST ();
}
postinvoke 操作的实现很简单。我们仅仅是销毁在preinvoke 操作中创建的伺服。在这两个操作中的参数 Cookie IDL type帮助把preinvoke的调用和postinvoke 操作关联在一起。
void
Quoter_Stock_Factory_Locator_i::postinvoke (const PortableServer::ObjectId &oid,
PortableServer::POA_ptr poa,
const char * operation,
void * cookie,
PortableServer::Servant servant)
{
// Delete the servant as it is no longer needed.
PortableServer::Servant my_servant = (PortableServer::Servant) cookie;
if (servant == my_servant)
delete servant;
}
服务端的实现
第一平上从RootPOA创建一个新的POA,这个新的POA具有以下特性: RequestProcessingPolicy的值设为USE_SERVANT_MANAGER ,并且ServantRetentionPolicy的值设为NON_RETAIN。
CORBA::PolicyList policies (3);
policies.length (3);
// Assign the polices
policies [0] =
poa->create_id_assignment_policy (PortableServer::USER_ID);
policies [1] =
poa->create_request_processing_policy
(PortableServer::USE_SERVANT_MANAGER);
policies [2] =
poa->create_servant_retention_policy (PortableServer::NON_RETAIN);
// Create the POA with these policies
PortableServer::POA_var child_poa =
poa->create_POA ("childPOA",
poa_manager.in (),
policies);
// Destroy the policy objects
for (CORBA::ULong i = 0; i != policies.length (); ++i) {
policies[i]->destroy ();
}
把策略赋值后,根据这些策略创建了childPOA,因为这些策略对象在create_POA中复制了一个拷贝,所以之后我们就不必再使用它们了,因此把它们删除。
由于我们拥有支持伺服管理器的POA,所以接下来为服务定位器对象创建伺服,激活它以找回它的引用, 然后给childPOA设置伺服管理器。
// Create a Stock_Factory_Locator servant
Quoter_Stock_Factory_Locator_i servant_locator_i(orb.in ());
// Need to activate a servant_manager object in the Root POA
PortableServer::ServantLocator_var servant_locator =
servant_locator_i._this ();
// Set the SM with the childPOA
child_poa->set_servant_manager (servant_locator.in ());
由于我们给childPOA设置了伺服管理器,接下来通过在childPOA中创建的用户自创建ID 来创建引用,
这个childPOA使用了Quoter_Stock_Factory_Locator_i。create_reference_with_id 操作
让我们创建了需要要的对象而无须实际的创建它的伺服。应用程序提供 了ObjectID,这个对象标识在应用程序领域更具有语义。
// Get the Object Id
PortableServer::ObjectId_var child_oid =
PortableServer::string_to_ObjectId ("childFoo");
//Create the Object without creating the servants
CORBA::Object_var stock_factory =
child_poa->create_reference_with_id (child_oid.in (),
"IDL:Quoter/Stock_Factory:1.0");
在这之后,和以前一样,我们把对角引用转为IOR字符串,并打印出来。
// Put the object reference as an IOR string
CORBA::String_var ior = orb->object_to_string (stock_factory.in ());
// Print it out!
std::cout << ior.in () << std::endl;
练习
Modify the server.cpp in
the simple server to use servant managers and locators. Use
these files to help complete the implementation.
Stock_Factory_locator_i.h Stock_Factory_locator_i.cpp
Makefile.
解决方案
Look at the server.cpp file. It should not
be much different from yours.
测试
A client which uses request handlers is provided:
client.cpp. As before the
following files are provided.
Quoter.idl Stock_i.h Stock_i.cpp Stock_Factory_i.h
Stock_Factory_i.cpp Handler_i.h and
Handler_i.cpp.
More Reading
The
The Henning and
Vinoski
CORBA book
discusses POA policies in detail. Likewise, the Schmidt and Vinoski
columns
in C++ Report also include several articles about the POA. Finally,
the TAO
distribution includes
examples that illustrate how to use the POA policies.
TAO的COS事件服务
不断地找回股票的价格而仅仅是为了检查其价格是否发生了改变这样做并不是有效和可伸缩的方案。当股价改变时我们就可以得到通知的话那么我们就可以执行恰当的动作。尽管我们可以自己设计回调机制,但解决这样的问题使用CORBA事件服务就可以轻松搞定。
定义事件类型
我们需要定义一个IDL结构体来承载我们的事件数据。自然而然,我们需要在事件中包括股票的价格,符号和全称。
struct Event {
double price;
string symbol;
string full_name;
};
我们也可以对Stock接口作一些扩展,这样修改股票的价格:
interface Modify_Stock : Stock {
void set_price (in double new_price);
};
获取股价的变化
作消费建立连接
作为消费者建立连接是个相似的过程,但是我们将使用更为传统的基于继承的方法代替TIE。先让我们定义消费者对象:
class Stock_Consumer : public POA_CosEventComm::PushConsumer {
public:
Stock_Consumer ();
void push (const CORBA::Any& data);
void disconnect_push_consumer void);
// details omitted
};
当事件服务断开时,它将调用disconnect_push_consumer()方法,比如,因为它被关闭前,消费者可以获得一个时机来断开连 接。 无论什么时候提供者发出某个事件,事件服务都会调用push()方法。让我们查看一下这个方法,我们先得从任意类型 (any)中抽取事件数据:
void
Stock_Consumer::push (const CORBA::Any& data)
{
Quoter::Event *event;
if ((data >>= event) == 0)
return; // Invalid event
注意,这个抽取操作可能失败:任意对象(any)可以存储所有的IDL数据类型,并且仅在抽取时才检查数据类型。同时再注意我们使用指向事件的指 针,CORBA规则是不定长的结构,也就是说,结构体包含不宽长的元素,比如字符串,通过引用被抽取出来。我们不需要管理这个内存,ORB将为我们消费 它。现在我们可以把新的股票价格打印出来:
std::cout << "The new price for one stock in /""
<< event->full_name.in ()
<< "/" (" << event->symbol.in ()
<< ") is " << event->price << std::endl;
}
回顾我们的示例,当事件通道断开时我们也会接收到回调。在这一点上我们需要忘掉原始的连接:
void
Stock_Consumer::disconnect_push_consumer void)
{
this->supplier_proxy_ = CosEventChannelAdmin::ProxyPushSupplier::_nil ();
}
但是为什么在最先的地方我们需要有一个与事件通道的连接?我们所需要的所有就是接收事件。与事件通道的连日接将使你能友好的关闭连接,于是事件通道不必为旧的消费者维护资源。例如,我们可以实现的方法如下:
void
Stock_Consumer::disconnect ()
{
// Do not receive any more events...
this->supplier_proxy_->disconnect_push_supplier ();
}
怎样连接到事件通道
连接到事件通道分三个步骤:第一步我们获取被所有想要连接的消费者使用的工厂。第二步我们获取提供者代理,于是当不需要任何更多事件的时候我们可以作出报告。最后一上我们连接到代理并开始接收事件。
我们假定我们使用了命名服务或与之类似的服务可以儿取引用给事件服务:
CORBA::Object_var tmp = naming_context->resolve (name);
CosEventChannelAdmin::EventChannel_var event_channel =
CosEventChannelAdmin::EventChannel::_narrow (tmp);
现在我们使用事件通道来获取为消费者连接使用的工厂:
CosEventChannelAdmin::ConsumerAdmin_var consumer_admin =
event_channel->for_consumers ();
并命名用工厂获取代理:
void
Stock_Consumer::connect (CosEventChannelAdmin::ConsumerAdmin_ptr consumer_admin)
{
this->supplier_proxy_ =
consumer_admin->obtain_push_supplier ();
And finally we connect:
CosEventComm::PushConsumer_var myself = this->_this ();
this->supplier_proxy_->connect_push_consumer (myself.in ());
}
通告股价的变化
现在我们将要检查提供者如何生成事件的。让我们查看Modify_Stock接口的实现:
class Quoter_Modify_Stock_i : public POA_Quoter::Modify_Stock {
public:
Quoter_Modify_Stock_i (const char *symbol,
const char *full_name,
CORBA::Double price);
void set_price (CORBA::Double new_price);
private:
Quoter::Event data_;
CosEventChannelAdmin::ProxyPushConsumer_var consumer_proxy_;
};
注意我们是如何用IDL结构来维护数据的。这仅是使代码更小更短。 consumer_proxy_ 对象正像上面讨论的supplier_proxy_ 对象,除了我们也用它来发送事件。set_price()方法的开始像下面这样:
void
Quoter_Stock_i::set_price (CORBA::Double new_price)
{
this->data_.price = new_price;
接下来我们准备事件。COS事件服务使用CORBA的any来发送所有的数据,如下:
CORBA::Any event;
event <<= this->data_;
最后我们向消费者发送事件:
this->consumer_proxy_->push (event);
}
作为提供者连接到事件服务
发送事件已很容易。作为提供者连到事件通道与作为消费者连接非常相似。我们将需要CosEventComm::PushSupplier 对象。这是一个TIE对象的好的应用程序:
class Quoter_Stock_i : public POA_Quoter::Modify_Stock {
public:
// some details removed...
void disconnect_push_supplier (void);
private:
POA_CosEventComm::PushSupplier_tie < Quoter_Stock_i > supplier_personality_;
};
PushSupplier_tie是由IDL编译器生成的模板。它实现了CosEventComm::PushSupplier 接口,但它实际上仅仅是转发所有的连接到它的单一模板参数。例如,在这个情况下disconnect_push_supplier 调用实现如下:
template void
POA_CosEventComm::PushSupplier_tie < T >::disconnect_push_supplier ()
{
this->ptr_->disconnect_push_supplier ();
}
ptr_成员变量实际上是指向模板参数的指针,因此我们不必实现一个单独的类仅用于接收断开连接的回调,我们可以使用相同的Modify_Stock_i类来处理它。
回过一建立连接的代码,首先我们获得对事件服务的访问,比如使用命名服务:
CORBA::Object_var tmp = naming_context->resolve (name);
CosEventChannelAdmin::EventChannel_var event_channel =
CosEventChannelAdmin::EventChannel::_narrow (tmp);
现在我们用事件通道获取被提供者连接使用的工厂:
CosEventChannelAdmin::SupplierAdmin_var supplier_admin =
event_channel->for_suppliers ();
并且命名用这个工厂获得代理:
this->consumer_proxy_ =
supplier_admin->obtain_push_consumer ();
然后我们使用提供者个性化地与消费者代理建立连接:
CosEventComm::PushSupplier_var supplier =
this->supplier_personality_._this ();
this->consumer_proxy_->connect_push_supplier (supplier);
最后我们实现断开连接的回调:
void
Quoter_Stock_i::disconnect_push_supplier (void)
{
// Forget about the consumer. It is not there anymore
this->consumer_proxy_ =
CosEventChannelAdmin::ProxyPushConsumer::_nil ();
}
练习 1
实现接收股价更新事件的消费者。
已提供了头文件 header file , 一起的还有client.cpp. 还提供了这些文件Quoter.idl, Makefile, Stock_i.h, Stock_i.cpp, Stock_Factory_i.h, Stock_Factory_i.cpp, 和server.cpp.
解决方案
用您的方案与Stock_Consumer.cpp比较。
测试
要测试您的变化您需要运行四个程序,先要运行TAO的命名服务:
$ $TAO_ROOT/orbsvcs/Naming_Service/Naming_Service -m 1
接下来是CORBA 事件服务
$ $TAO_ROOT/orbsvcs/CosEvent_Service/CosEvent_Service
再然后运行您的客户端
$ client
最后运行服务端
$ server AAAA MSFT RHAT < stock_list.txt
练习 2
向上面那样配置,但是这次运行多个客户端和服务端:
$ client
$ client
$ server AAAA BBBB < stock_list1.txt
$ server ZZZZ YYYY < stock_list2.txt
客户端从两个服务端都接收所有的事件吗?如果您不想接收所有的事件将怎么样?举例来说,因为您只对特定的某些股票感兴趣。
这是stock_list1.txt 和stock_list2.txt 文件。
不用多播的方式启动命令服务和事件服务的方式:
start %TAO_ROOT%/orbsvcs/Naming_Service/Naming_Service -ORBEndpoint iiop://localhost:2809
start %TAO_ROOT%/orbsvcs/CosEvent_Service/CosEvent_Service -ORBInitRef NameService=corbaloc::localhost:2809/NameService
启动客户端的方式
start client -ORBInitRef NameService=corbaloc::localhost:2809/NameService
启动服务端的方式
start server AAAA BBBB < stock_list1.txt -ORBInitRef NameService=corbaloc::localhost:2809/NameService
十:TAO的实时事件服务
TAO的实时事件服务
我 们已探研了如何使用TAO的COS事件服务来接收更新过的股票的价格,但是如果我们并不关心所有的股票又怎么样呢?一个方法是使用多个事件通道,每个通道 承载不同的消息容量。例如,每个事件通道仅携带股票其中的一部分。在本节中,我们将探讨另一个广案,即使用TAO实事事件服务来为我们执行过滤。TAO的 实时事件服务可做许多其它事情,像保存具有优先级的点对点,使用多播来节省网络资源,产生超时和间隔的事件,以及它可以与TAO的调度服务协作用于分析您 系统的可调度性。
取回股价的变化
在本例中,我们将会使用我们前一章用过的相同的数据结构,也就是事件将是相同的。TAO的实时事件服务可被配置用于以类型安全的方式携带事件,或你可以使用自定义的编码来发送非在事件中非IDL结构体,但第一点它可以像COS 事件服务一样简单的使用。
作为消费者连接
作为消费者连接是很相似的。一些基类和签名变化了,但它基本上具有相同的思想:先让我们定义消费者对象:
class Stock_Consumer : public POA_RtecEventComm::PushConsumer {
public:
Stock_Consumer ();
void push (const RtecEventComm::EventSet& data);
void disconnect_push_consumer (void);
// details omitted
};
注意的是我们接收一个事件集合代替单个事件。事件通道可以使用这个特征来把多个事件放入队列和把它们放进一个单个操作。首先我们需要从any中抽取事件数据:
void
Stock_Consumer::push (const RtecEventComm::EventSet &data)
{
for (CORBA::ULong i = 0; i != data.length (); ++i) {
RtecEventComm::Event &e = data[i];
Quoter::Event *event;
if ((e.data.any_value >>= event) == 0)
continue; // Invalid event
注意事件有多个结构,它们有区分明显的事件头和事件数据体,并且事件数据体比一个any还多。事件头用于提供过滤,事件数据体字段可配置为在编译时携带无论哪种您想要的IDL结构体。现在我们可以输出新的股票价格了:
std::cout << "The new price for one stock in /""
<< event->full_name.in ()
<< "/" (" << event->symbol.in ()
<< ") is " << event->price << std::endl;
}
我们也必须实现断开连接的回调:
void
Stock_Consumer::disconnect_push_consumer (void)
{
this->supplier_proxy_ = CosEventChannelAdmin::ProxyPushSupplier::_nil ();
}
As with the COS Event Channel we can voluntarily disconnect, too:
与COS 事件通道一样我们也可以自愿断开连接。
void
Stock_Consumer::disconnect ()
{
// Do not receive any more events...
this->supplier_proxy_->disconnect_push_supplier ();
}
如何连接到实时事件通道
连接到实时事件通道与连接到正规的事件通道非常的相似。只有一点不同,就是我们必须指定为想要接收的事件。这一点使用相当复杂的IDL结构来描述,但是TAO提供了一个帮助类来生产它。我们将假定我们使用命名服务或其它相似的服务来获取一个事件服务的引用:
CORBA::Object_var tmp = naming_context->resolve (name);
RtecEventChannelAdmin::EventChannel_var event_channel =
RtecEventChannelAdmin::EventChannel::_narrow (tmp);
现在我们用事件通道来获取消费都连接使用的工厂:
RtecEventChannelAdmin::ConsumerAdmin_var consumer_admin =
event_channel->for_consumers ();
再用工厂找回代理:
void
Stock_Consumer::connect (RtecEventChannelAdmin::ConsumerAdmin_ptr consumer_admin)
{
this->supplier_proxy_ =
consumer_admin->obtain_push_supplier ();
现在我们列出我们想接收的事件。我们用简单的算法来给事件类型赋给每支股票:
CORBA::ULong rhat_event_type =
(int('R') << 24) | (int('H') << 16) | (int('A') << 8) | int('T');
CORBA::ULong aaaa_event_type =
(int('A') << 24) | (int('A') << 16) | (int('A') << 8) | int('A');
然后我们创建订阅:
ACE_ConsumerQOS_Factory subscriptions;
subscriptions.insert_type (rhat_event_type, 0);
subscriptions.insert_type (aaaa_event_type, 0);
和连接到代理:
RtecEventComm::PushConsumer_var myself = this->_this ();
this->supplier_proxy_->connect_push_consumer (
myself.in (),
subscriptions.get_ConsumerQOS ());
}
通知股价的变化
As with the COS Event Channel example we will make our implementation of the Modify_Stock interface generate events whenever the price changes:
与COS事件通道示例一样无论什么时候只要股价变化我们都将创建Modify_Stock接口的实现来创建事件。
class Quoter_Modify_Stock_i : public POA_Quoter::Modify_Stock {
public:
Quoter_Modify_Stock_i (const char *symbol,
const char *full_name,
CORBA::Double price);
void set_price (CORBA::Double new_price);
void disconnect_push_supplier (void);
private:
Quoter::Event data_;
RtecEventChannelAdmin::ProxyPushConsumer_var consumer_proxy_;
POA_RtecEventComm::PushSupplier_tie < Quoter_Stock_i > supplier_personality_;
};
set_price()方法的实现非常相似。第一我们存储新的价格:
void
Quoter_Stock_i::set_price (CORBA::Double new_price)
{
this->data_.price = new_price;
下一步我们准备事件。这时我们必须创建一个序列,但我们仅有在里面有一个元素:
RtecEventComm::EventSet event (1);
event.length (1);
We set the event type based on the stock symbol:
RtecEventComm::Event &e = event[0];
const char *symbol = this->data_.symbol;
e.header.type =
((int(symbol[0]) << 24)
| (int(symbol[1]) << 16)
| (int(symbol[2]) << 8)
| int(symbol[3]));
e.header.source = 1;
在本示例中没有使用事件源,但它必须是非0的。现在我们可以设置事件数据体:
e.data.any_value <<= this->data_;
and send the event to the event channel:
this->consumer_proxy_->push (event);
}
作为提供者连接到事件服务
作为在COS事件通道的情形下,我们需要提一个供者特征与它连接。我们获得事件服务的访问方法,例如使用命名服务:
CORBA::Object_var tmp = naming_context->resolve (name);
RtecEventChannelAdmin::EventChannel_var event_channel =
RtecEventChannelAdmin::EventChannel::_narrow (tmp);
接下来我们用事件通到来获得提供者连接使用的工厂:
RtecEventChannelAdmin::SupplierAdmin_var supplier_admin =
event_channel->for_suppliers ();
和用工厂获得一个代理:
this->consumer_proxy_ =
supplier_admin->obtain_push_consumer ();
我们构建发布器因此事件通可以在基于他们通用的事件之的消费者和提供者之间进行匹配:
const char *symbol = this->data_.symbol;
CORBA::ULong type =
((int(symbol[0]) << 24)
| (int(symbol[1]) << 16)
| (int(symbol[2]) << 8)
| int(symbol[3]));
CORBA::ULong source = 1;
ACE_SupplierQOS_Factory publications;
publications.insert_type (type, source, 0, 1);
最后我们连接到消费者代理上:
RtecEventComm::PushSupplier_var supplier =
this->supplier_personality_._this ();
this->consumer_proxy_->connect_push_supplier (supplier);
最后我们实现断开连接的回调:
void
Quoter_Stock_i::disconnect_push_supplier (void)
{
// Forget about the consumer it is not there anymore
this->consumer_proxy_ =
RtecEventChannelAdmin::ProxyPushConsumer::_nil ();
}
练习1
实现接收股价更新事件的消费者,
已提供了头文件 header file , 一起的还有client.cpp. 还提供了这些文件Quoter.idl, Makefile, Stock_i.h, Stock_i.cpp, Stock_Factory_i.h, Stock_Factory_i.cpp, 和server.cpp.
解决方案
用您的方案与 Stock_Consumer.cpp比较。
测试
要测试您的变化您需要运行三个程序,先要运行TAO的命名服务:
$ $TAO_ROOT/orbsvcs/Naming_Service/Naming_Service
然后运行TAO的实时事件服务
$ $TAO_ROOT/orbsvcs/Event_Service/Event_Service
再运行您的客户端:
$ client AAAA CCCC
最后运行服务端:
$ server MSFT BBBB CCCC < stock_list.txt
Here is the stock_list.txt file.
Exercise 2
向上面那样配置,但是这次运行多个客户端和服务端:
$ client AAAA MSFT
$ client PPPP
$ server AAAA < stock_list1.txt
$ server QQQQ < stock_list2.txt
客户端能接收来来自两个服务端的所有事件吗?
这是 stock_list1.txt 和stock_list2.txt 文件。