`
tibaloga
  • 浏览: 870495 次
文章分类
社区版块
存档分类
最新评论

(ORBA与分布式应用)TAO股票报价系统例用

 
阅读更多

. TAO创建股票报价系统

TAO创建股票报价系统

Building a Stock Quoter with TAO- A Tutorial

本教程是围绕一个单一的应用程序组织的,它允许客户端程序可以通过报价服务获取股票报价。该应用程序是基于Doug SchmidtSteve VinoskiC++ Report杂志的对象互联专栏的序列文章而开发的。

篇教程从简单的客户程序及服务程序到剖析TAO的高级特性而逐步构建,这些高级特性包括了:asynchronous method invocation,reliable oneways, real-time Evnet Service, Interoperable Naming Service等等。

在您阅读本教程时,您可以自由地从提供的链接处获取到源文件。但是,如果您选择了编译和运行示例程序,这些源文件是作为您下载的TAO源文件的一部分。您可以从这下目录

$TAO_ROOT/docs/tutorials/Quoter

找到源代码,里面还包含了编译时需要的Makefile文件。由于本在线教有可能使用了与您不同版本的ACE+TAO源代码,因此单独下载这些源代码可能不能正确编译。

  1. 介绍- 一个很简单的客户端
  2. 介绍-一个很简单的服务端
  3. 介绍-改进服务端,通过POA策略之显示激活和用户自定义对象ID
  4. 介绍-改进服务端,通过POA策略之持久化对象引用
  5. 实现仓库
  6. TAO的命名服务
  7. 异步方法调用-CORBA为没有耐心的客户端提供的解决方案
  8. 按需激活
  9. TAOCos事件服务
  10. TAORT事件服务
  11. RTCORBA

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.cppQuoterC.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指针,并且,即使使用了不同 的标识,返回相同指针的实现也是自由的。通常这不是问题,这是由于大多数应用程序只实例化单一的ORBTAO是实际支持多个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"的文件并从文件中读取IORTAO还支持corbaloc:scheme,例如

corbaloc:iiop:1.1@ace.cs.wustl.edu: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.简介——非常简单的服务端

既然我们已经知道如何实现一个简单的客户端,那么现在我们得实现一个服务端来测试它。我们必须提供StockStock_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实例,但所有的服务端程序至少有一个称为RootPOAPOA

我们必须实现的操作和属性。

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编译器帮助我们?毕意,看上去方法的声明已被作了详细的说明。答案是肯定的。TAOIDL编译器可以生成空白的实现,而你要做的只是简单在其上作修改。生成空白的实现只需要使用-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 ServiceTrading Service,同时还用于获取其它的ORB接口,比如 RootPOACurrent OjectPolicy 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。那这是为什么呢?这是因为RootPOAID是被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
};

TAOPortableServer::RefCountServantBase 的实现也是线程安全的,于是你可以用这个技术在你的多线程服务中动态的销毁对象。您简单的委托POA来控制,一旦所有线程调用了对象中止,当休眠该对象 时,POA将会调用 _remove_ref(),这样如果这个对象还在使用中就不会被删除。也请记住如果你要使用对象请增加它的引用记数。

练习

从简单服务的文件作如下修改:

  • Stock_i.h: 使用引用计数控制伺服代码的内存管理。
  • Stock_Factory_i.h: Apply the changes described above to use a child POA with the appropriate policies.
  • Stock_Factory_i.cpp: Apply the changes described above to use a child POA with the appropriate policies.
  • server.cpp: Create the child POA, initialize the stock objects from the stdin, and create the right stock factory class.

你能够使用相同的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 ());

接下来我们找回RootPOAPOA管理器好用它来激活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,但通常情况下使用父POAPOA管理器。

最后一步,我们可以销毁生命期策略和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)

在先前的示例中,客户端与服务端必须至少联系一次。如果服务器被移动到不同的主机或端口,或服务器已关掉,服务器与客户端之间的绑就定会失败。通过外在的定位代理间接绑定,像实现仓库可以解决上述问题。

实现仓库维护一个数据结构,这个数据结构作为保存跟踪服务器的服务表。它维护已知服务的注册表、正在运行的服务器的记录和对应的端口号,并且如果服务器已注册,还可以根据需要启动服务器。

服务器创建一个持久化的时候,它设置地址和端口号到IORprofile 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_ServiceIOR到一个文件中,以便于激活器和客户端都能找到定位器。

$ $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:1.1@doc.ece.uci.edu: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可用的前提下或以工作。您可以使用神奇的端口号0ORB为您寻找一个可用的端口,实际上这正好是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);
};

这将使一些示例得以简化。

第一步是生成支持回调AMIstubsskeletions。我们使用-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.htmlconfigurations.html 和来自OCI的开发者指南中。

八:按需激活

伺服(Servant)管理器

在先前的示例中,我们对使用同步方法调用的简单客户端进行了扩展,用于处理异步请求:通过使用回复处理器的异步方向调用。

在应用程序中有许多对象,没有必要在同时激活所有的对象,并且,如果同时激活所有对象会导致占用过多的内存或过多的数据库查询。为了解决这类应用程序面临的问题,POA提供了一个选项让应用程序提供伺服管理器,伺服管理器可以在每一个请求上动态的提供伺服。

服管理器是一个回调对象,应用程序把它和POA注册在一起。当POA试图确定与特定请求关联的伺服是,它回调应用程序的伺服管理器来找回伺服。想要把伺服 管理器与POA注册在一起,就要把控制请求与伺服关系的这个RequestProcessingPolicyValue设置为 USE_SERVANT_MANAGER

根据POA是否把对象与伺服的关联是否保存在它的主动对象映射表(Active Object Map),我们把伺服管理器分为两种类型。这取决于当POA创建时为ServantRetentionPolicy设的值。如果设为 RETAIN,那么POA保留关联,如果设为NON_RETAINPOA则不在对象和伺服中保留任何关联。

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_icreate_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.

九:TAOCOS事件服务

TAOCOS事件服务

不断地找回股票的价格而仅仅是为了检查其价格是否发生了改变这样做并不是有效和可伸缩的方案。当股价改变时我们就可以得到通知的话那么我们就可以执行恰当的动作。尽管我们可以自己设计回调机制,但解决这样的问题使用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事件服务使用CORBAany来发送所有的数据,如下:

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

这是 stock_list.txt file.

练习 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的实时事件服务

们已探研了如何使用TAOCOS事件服务来接收更新过的股票的价格,但是如果我们并不关心所有的股票又怎么样呢?一个方法是使用多个事件通道,每个通道 承载不同的消息容量。例如,每个事件通道仅携带股票其中的一部分。在本节中,我们将探讨另一个广案,即使用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 文件。

分享到:
评论

相关推荐

    使用 CORBA 和 Java IDL.doc

    介绍有关java 开发corba 的一些步骤和技术

    实时CORBA中ORB核心的优化

    主要介绍实时corba技术以及orba核心的优化

    magento2-module-style-guide

    借助DI和主题继承系统,可以轻松自定义样式指南的每个项目。 从样式指南中添加或删除项目非常容易。 该库是开源的。 随时贡献! 2.安装 推荐的安装此软件包的方法是通过 。 composer require orba/magento2-module...

    node-v16.13.1-darwin-arm64.tar.xz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    【前端素材】大数据-大数据可视化系统数据分析通用模版.zip

    大数据技术指的是用于处理和分析大规模数据集的技术和工具。以下是一些常见的大数据技术和工具: Hadoop:Apache Hadoop是一个用于分布式存储和处理大规模数据的开源框架。它包括Hadoop Distributed File System(HDFS)用于数据存储和MapReduce用于数据处理。 Spark:Apache Spark是一个快速、通用的集群计算系统,提供了比MapReduce更快的数据处理能力。它支持内存计算和更多复杂的数据处理流程。 NoSQL数据库:NoSQL数据库(如MongoDB、Cassandra等)则更适用于处理这类数据。 数据仓库:数据仓库是一个用于集成和分析大规模数据的存储系统,一些知名的数据仓库包括Snowflake、Amazon Redshift等。 数据湖:数据湖是一个存储结构化和非结构化数据的存储池,用于支持数据分析和机器学习应用。 机器学习:大数据技术也广泛应用于机器学习领域,支持大规模数据的模型训练和预测分析。 流式处理:针对实时数据处理需求,流式处理技术(如Apache Kafka、Apache Flink)可以实时。

    node-v8.3.0.tar.xz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    云计算存储架构dr.pptx

    云计算存储架构dr.pptx

    【前端素材】大数据-兰州智慧消防大数据平台.zip

    大数据技术指的是用于处理和分析大规模数据集的技术和工具。以下是一些常见的大数据技术和工具: Hadoop:Apache Hadoop是一个用于分布式存储和处理大规模数据的开源框架。它包括Hadoop Distributed File System(HDFS)用于数据存储和MapReduce用于数据处理。 Spark:Apache Spark是一个快速、通用的集群计算系统,提供了比MapReduce更快的数据处理能力。它支持内存计算和更多复杂的数据处理流程。 NoSQL数据库:NoSQL数据库(如MongoDB、Cassandra等)则更适用于处理这类数据。 数据仓库:数据仓库是一个用于集成和分析大规模数据的存储系统,一些知名的数据仓库包括Snowflake、Amazon Redshift等。 数据湖:数据湖是一个存储结构化和非结构化数据的存储池,用于支持数据分析和机器学习应用。 机器学习:大数据技术也广泛应用于机器学习领域,支持大规模数据的模型训练和预测分析。 流式处理:针对实时数据处理需求,流式处理技术(如Apache Kafka、Apache Flink)可以实时。

    基于java的-152-springboot大学生体质测试管理系统--LW-源码.zip

    提供的源码资源涵盖了Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!

    培训材料-云计算架构比较和关键技术讲解dr.ppt

    培训材料-云计算架构比较和关键技术讲解dr.ppt

    BP神经网络工具箱.doc

    数学模型算法

    基于python的-17-物业信息管理系统--LW-源码.zip

    提供的源码资源涵盖了python应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!

    【前端素材】大数据-运营大数据.zip

    大数据技术指的是用于处理和分析大规模数据集的技术和工具。以下是一些常见的大数据技术和工具: Hadoop:Apache Hadoop是一个用于分布式存储和处理大规模数据的开源框架。它包括Hadoop Distributed File System(HDFS)用于数据存储和MapReduce用于数据处理。 Spark:Apache Spark是一个快速、通用的集群计算系统,提供了比MapReduce更快的数据处理能力。它支持内存计算和更多复杂的数据处理流程。 NoSQL数据库:NoSQL数据库(如MongoDB、Cassandra等)则更适用于处理这类数据。 数据仓库:数据仓库是一个用于集成和分析大规模数据的存储系统,一些知名的数据仓库包括Snowflake、Amazon Redshift等。 数据湖:数据湖是一个存储结构化和非结构化数据的存储池,用于支持数据分析和机器学习应用。 机器学习:大数据技术也广泛应用于机器学习领域,支持大规模数据的模型训练和预测分析。 流式处理:针对实时数据处理需求,流式处理技术(如Apache Kafka、Apache Flink)可以实时。

    node-v8.1.1.tar.xz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    node-v8.1.2.tar.xz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    基于java的-160-springboot农机电招平台--LW-源码.zip

    提供的源码资源涵盖了Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!

    小程序-58-童心党史小程序-源码.zip

    提供的源码资源涵盖了小程序应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!

    云计算基础课件—云安全dr.pptx

    云计算基础课件—云安全dr.pptx

    基于python开发的手写数字识别+源码(期末大作业&课程设计&项目开发)

    基于python开发的手写数字识别+源码,适合期末大作业、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用~ 基于python开发的手写数字识别+源码,适合期末大作业、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用~ 基于python开发的手写数字识别+源码,适合期末大作业、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用~ 基于python开发的手写数字识别+源码,适合期末大作业、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用~

    Zest是一款基于Spring的易于使用的单元测试工具(高分毕设).zip

    Java SSM项目是一种使用Java语言和SSM框架(Spring + Spring MVC + MyBatis)开发的Web应用程序。SSM是一种常用的Java开发框架组合,它结合了Spring框架、Spring MVC框架和MyBatis框架的优点,能够快速构建可靠、高效的企业级应用。 1. Spring框架:Spring是一个轻量级的Java开发框架,提供了丰富的功能和模块,用于开发企业级应用。它包括IoC(Inverse of Control,控制反转)容器、AOP(Aspect-Oriented Programming,面向切面编程)等特性,可以简化开发过程、提高代码的可维护性和可测试性。 2. Spring MVC框架:Spring MVC是基于Spring框架的Web框架,用于开发Web应用程序。它采用MVC(Model-View-Controller,模型-视图-控制器)的架构模式,将应用程序分为模型层、视图层和控制器层,提供了处理请求、渲染视图和管理流程的功能。 3. MyBatis框架:MyBatis是一个持久层框架,用于与数据库进行交互。它提供了一种将数据库操作与Java对象映射起来的方式,避免了手动编写繁琐的SQL语句,并提供了事务管理和缓存等功能,简化了数据库访问的过程

Global site tag (gtag.js) - Google Analytics