(本文转载自软件工程专家网www.21cmm.com)
于国良
本文主要讨论了的CORBA的异步传输机制ONEWAY以及事件服务。同时给出用DELPHIL利用ONEWAY以及事件服务异步传输机制实现的简单模型。
我们通常讨论的CORBA模型往往从客户机激发远程方法的角度来讨论C O R B A系统。象通常的客户机和服务器模型:客户机和服务器组件分别运行在不同的机器上,客户机发出一个请求,然后客户机阻塞。服务器主动监听从客户机来的请求。当收到一个请求后,服务器处理这个请求,并把结果返回给发出请求的客户机。客户机在等待回应时是阻塞的,只有在它接收到回答后才能继续处理。
在很多场合中,这种模型正好是用户所希望的。而且这种模型也符合传统的C/S模型,有问有答。但是在很多场合中并不需要这种模型,象股票系统中,普通股民眼中的价格更新,SCADA电力系统中的现场数据的显示等,都是不需要客户机发出请求,服务器就把结果返回给客户。还有在很多场合中,客户机发出一个请求后,并不希望得到服务器的回答,它需要的只是给服务器一个通知,象SCADA电力系统中的前置机往服务器上发数据,前置机并不需要等待这个服务器上的回答返回。为解决上述问题,CORBA提出了ONEWAY以及事件服务异步传输机制。
ONEWAY异步传输,顾名思义,ONEWAY就是"单向",即客户机发出它们的激发,然后继续处理,而用不着在发出一请求后阻塞,直到结果返回,当服务器完成对该请求的处理后,它可以通过向客户机发回一相应的单向激发把结?quot;返回",也可以不返回结果。
利用ONEWAY异步传输比较简单,它的一般步骤与普通CORBA应用一样: 1. 首先定义接口的IDL文件。 2. 编译IDL文件。 3. 编写服务器端程序。 4. 编写客户端程序。
这里我以DELPHI6(在DELPHI6中CORBA规范是由VisiBroker产品来实现的)为开发工具,来实现ONEWAY异步传输:
1. 首先定义接口的IDL文件:为了简单,我这里只定义了一个ONEWAY的方法。
module Pro { interface IOnewDemo; interface IOnewDemo { oneway void onewaya(in long aa); }; interface OnewDemoFactory { IOnewDemo CreateInstance(in string InstanceName); }; };
2.和3在DELPHI6中,第二步和第三步是和在一起的。
选FILE-NEW-OTHER-CORBA-CORBA Server Application 在出现的IDL2Pas Create Server Dialog中按Add按钮加入刚才定义的IDL文件,按OK按钮以后,由IDL2Pas编译器来对它进行编译,生成Pro_c.pas,Pro-i.Pas,Pro_Impl.pas,Pro_s.pas四个文件,在Pro_Impl.pas文件中定义
procedure TIOnewDemo.onewaya ( const aa : Integer); begin form1.ListBox1.Items.Clear; form1.ListBox1.Items.Add(inttostr(aa)); end;
在程序启动时进行初始化
procedure TForm1.FormCreate(Sender: TObject); var Acct: IOnewDemo; begin CorbaInitialize; // Add CORBA server code here like this Acct := TIOnewDemoSkeleton.Create('ygl', TIOnewDemo.Create); BOA.ObjIsReady(Acct as _Object); end;
编译生成服务器端程序。
4. 编写客户端程序:
在程序启动时进行初始化 procedure TForm1.FormCreate(Sender: TObject); var Acct: IOnewDemo; begin CorbaInitialize; // Bind to the Corba server like this Acct := TIOnewDemoHelper.bind; end;
调用接口定义方法 procedure TForm1.Timer1Timer(Sender: TObject);//utilize the mothod of interface var i:integer; begin randomize; i:=random(20); acct.onewaya(i); end;
编译生成客户器端程序。
CORBA中的事件服务是基于以下的原因定义的:
事件发送者和事件接收者的关系是松偶合的:事件发送者发送消息时,并不关心谁会收到消息。事件接收者接收消息时,也并不关心谁发送的消息。这里事件发送者称为事件供应者,事件接收者称为事件消费者。
在VisiBroker的事件实现中,它定义了两种事件模型:PUSH模型和PULL模型:如果一个事件是由供应者主动发出,消费者被动接收,就称为PUSH模型,如果一个事件是由消费者主动索取,供应者被动提供,就称为PULL模型。
PUSH模型可以简单表示为:
DELPHI6中在COSEVENT单元中定义了通用的接口:
PushConsumer , PushSupplier, PullSupplier , PullConsumer, ProxyPushConsumer ProxyPullSupplier , ProxyPullConsumer , ProxyPushSupplier , ConsumerAdmin , SupplierAdmin , EventChannel…..
这里我用PUSH模型简单说一下通讯建立的过程:
供应者端:
1. 供应者获得一个SupplierAdmin对象。供应者通过调用Event_Channel的for_suppliers得到一个SupplierAdmin对象。
2. 供应者获得一个ProxyPushConsumer对象。供应者通过调用SupplierAdmin的obtain_push_consumer得到一个ProxyPushConsumer对象。
3. 供应者利用ProxyPushConsumer对象的connect_push_supplier连接远方的ProxyPushSupplier对象。
消费者端:
1. 消费者获得一个ConsumerAdmin对象。消费者通过调用Event_Channel的for_consumers得到一个ConsumerAdmin对象。
2. 消费者获得一个ProxyPushSupplier对象。消费者通过调用ConsumerAdmin的obtain_push_supplier得到一个ProxyPushSupplier对象。
3. 消费者利用ProxyPushSupplier对象的connect_push_consumer连接远方的ProxyPushConsumer对象。在DELPHI6中对事件服务的PUSH模型实现的例子如下:
供应者端:
实现TpushSupplier类 TPushSupplier = class(TInterfacedObject, PushSupplier) public constructor Create; procedure disconnect_push_supplier; end;
主程序:
procedure TForm1.FormCreate(Sender: TObject); var PushSupplier_Skeleton,PushSupplier_Skeletonaaa : PushSupplier; Event_Channel,Event_Channelaaa : EventChannel; Supplier_Admin,Supplier_Adminaaa : SupplierAdmin; Push_Consumer,Push_Consumeraaa : ProxyPushConsumer; myAny:any; begin CorbaInitialize; // Create the skeleton and register it with the boa PushSupplier_Skeleton:=TPushSupplierSkeleton.Create('ygl', TPushSupplier.Create); BOA.SetScope( RegistrationScope(1) ); BOA.ObjIsReady(PushSupplier_Skeleton as _Object);
//bind to the event channel and get a Supplier Admin object 获得事件信道接口,bind的参数是下面命令行中的参数ygl: Event_Channel := TEventChannelHelper.bind('ygl'); //可以连接另一个事件信道bind的参数是另一个实例中的参数yglaaa: // Event_Channelaaa := TEventChannelHelper.bind('yglaaa'); 获得SupplierAdmin接口: Supplier_Admin := Event_Channel.for_suppliers; //get a push consumer and register the supplier object 获得ProxyPushConsumer接口: Push_Consumer := Supplier_Admin.obtain_push_consumer; 连接ProxyPushSupplier接口 Push_Consumer.connect_push_supplier(PushSupplier_Skeleton); 往信道中推数据 randomize; myany:= random(1000); try Push_Consumer.Push(myAny); except on EDisconnected do ShowMessage('Client Disconnected'); end; end;
消费者端:
实现TPushConsumer类 TPushConsumer = class(TInterfacedObject, PushConsumer) public constructor Create; procedure push(const data : Any); procedure disconnect_push_consumer; end; 这里,重要的方法是: procedure TPushConsumer.push(const data : Any); var num :integer begin num := data; Form1.ListBox1.Items.Add(IntToStr(num)); end; 它表示消费者收到数据后的处理。
主程序:
procedure TForm1.FormCreate(Sender: TObject); var PushConsumer_Skeleton : PushConsumer; Event_Channel : EventChannel; Consumer_Admin : ConsumerAdmin; Push_Supplier : ProxyPushSupplier; begin CorbaInitialize;
// Create the skeleton and register it with the boa PushConsumer_Skeleton:=TPushConsumerSkeleton.Create('ygl', TPushConsumer.Create); BOA.SetScope( RegistrationScope(1) ); BOA.ObjIsReady(PushConsumer_Skeleton as _Object);
获得事件信道接口:
//bind to the event channel and get a Supplier Admin object Event_Channel := TEventChannelHelper.bind('ygl'); 获得ConsumerAdmin接口: Consumer_Admin := Event_Channel.for_consumers;
//get a push consumer and register the supplier object 获得ProxyPushSupplier;接口: Push_Supplier := Consumer_Admin.obtain_push_supplier; 连接ProxyPushConsumer接口 Push_Supplier.connect_push_consumer(PushConsumer_Skeleton); end;
程序运行:
首先运行SmartAgent,再在…/VisiBroker/bin下以命令行方式运行channel ygl 然后分别运行供应者端,消费者端程序。
在这里要注意两个方面的问题:
1.由于大多数O R B使用T C P作为它们底层的传输,而T C P是一可靠的协议,所以即使用户认为是非阻塞的调用,实际上在T C P层还是阻塞的调用。用户的客户机应用程序可能不等待服务器应用程序接收并开始处理请求,但是客户机的T C P / I P栈却要等待,直到请求缓冲区被服务器的T C P / I P栈成功接收(和确认)。
2. 在Visibroker中并没有提供事件粒度的控制-过滤功能。