不断地找回股票的价格而仅仅是为了检查其价格是否发生了改变这样做并不是有效和可伸缩的方案。当股价改变时我们就可以得到通知的话那么我们就可以执行恰当的动作。尽管我们可以自己设计回调机制,但解决这样的问题使用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 (); }
实现接收股价更新事件的消费者。
已提供了头文件 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
向上面那样配置,但是这次运行多个客户端和服务端:
$ 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