【中间件技术】第二部分 CORBA规范与中间件(3) 基于CORBA的开发过程

本文详细介绍了基于CORBA的分布式系统开发过程,包括设计阶段考虑的运行平台、调用方式和资源优化,以及CORBA应用程序的开发步骤。通过一个银行账户管理的实例,阐述了从认定分布式对象、编写接口到编译IDL文件、编写对象实现和服务程序、创建并部署应用程序的全过程。此外,还探讨了CORBA的跨语言和跨平台互操作性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这里通过一个基于 Borland VisiBroker (4.5.1) 的简单例子,说明基于 CORBA 的基本开发过程,可从网上免费获得软件试用版。


3.1 设计相关的若干问题

首先,简单看一下在系统设计阶段应注意的问题。由于篇幅的限制,此外并不想去考察「软件系统分析设计阶段的普遍问题」,而是重点讨论几个与「基于 CORBA 的分布式系统」相关的问题。

1. 运行平台

设计者必须在设计初期决定,待开发的分布式系统要运行在哪类硬件和软件平台之上。由于不同平台(计算机硬件和操作系统)之间的差异,为一个平台开发的软件系统,通常不能直接运行在另一个平台之上(应注意:尽管 CORBA 提供了异类环境中良好的可互操作性,但这与系统的可移植性是截然不同的两个问题)。一般来说,设计者总是在「软件系统的性能与通用性的矛盾」之间,作一个折衷的选择:要使所开发的软件系统具备良好的通用性,能够方便的在不同平台上迁移,就需要尽可能的避免使用特定平台(比如某个厂商的平台或某个操作系统)相关的机制,而较好的性能在很多情况下,都要借助于特定平台的特性才能获得。

所以,设计者要根据自己特定项目或系统的特点与需求,作出一个折衷的选择。

2. 调用方式

CORBA 中,分布式对象提供的服务的调用方式可有三种

  • 同步方式:调用时,调用者会阻塞,直到被调用的服务完成并返回。
  • 异步方式调用者发起调用后不会阻塞,等待服务完成期间,可以执行其它操作。调用者通过轮询方式、或服务者发送的事件检测调用完成,服务完成后调用者检查并处理结果。异步方式通常依靠异步消息来实现。
  • 单向方式:调用者只是发出调用请求,并不关心调用什么时候完成(以及完成的结果)。

不同的调用方式适合不同的应用场合

  • 客户程序的请求所引起的服务程序操作,只需要很短时间即可完成,例如:查询某一帐户的当前余额,这时应选用同步通信方式。
  • 如果客户程序请求服务程序格式化并打印几个大型文档,服务程序需要较长时间才可完成该请求,这时应选用异步通信方式,从而在服务程序格式化并打印文档期间,客户程序可以做一些其他事情。
  • 如果客户程序无需获知请求已完成的确认信息,例如:向系统日志模块登记系统执行了某一操作,则应选用单向通信方式。

3. 资源优化

在分布式环境下,跨网络的通信开销是相当可观的,通常是影响系统整体效率的瓶颈。而在 Stub/Skeleton 机制的支撑下,开发者已经不需要自己编程处理底层通信,分布式对象在开发时,并没有表现出很强的分布特性,这更容易使设计者忽略跨网络的通信对系统的影响。

在一个集中式软件系统中,程序员可在同一进程中随意地连续多次调用一个例程,因为这些调用的系统资源开销微不足道。但在分布式环境下,同样的调用如果发生在跨网络的进程之间,这些调用占用的系统资源是相当可观的。因此在设计阶段,特别是在接口详细设计阶段,应考虑尽量提高网络通信资源的利用率,避免频繁的跨网络(尤其是广域网)通信,而不应只从功能实现的方面去考虑

4. 其他决策问题

分布式系统通常要比集中式软件考虑更多的安全性、可靠性、事务处理、并发控制等问题。另外,还需要考虑更多的错误处理,例如客户程序发出请求、但服务程序未就绪,甚至找不到服务程序、或无权限访问服务程序时,应如何处理这种情况。


3.2 CORBA 应用程序开发过程

虽然OMG为 CORBA 制订了统一的规范,但规范中也赋予了软件供应商实现 ORB 产品时、自由选择各自不同的实现途径的权利,例如:ORB 可以是一个独立运行的守护进程,也可以嵌入到客户程序和对象实现中,有些 ORB 产品则选择了几种实现方式的组合。所以,不同供应商提供的 ORB 产品,在具体使用方法上可能存在较大差异,这里的 CORBA 部分以Borland公司的 VisiBroker for Java 4.5.1 为例,介绍一个 CORBA 应用程序的具体开发步骤,使用其他 ORB 产品时可参照类似做法。

尽管使用不同 ORB 产品的具体操作差异较大,但程序员开发一个 CORBA 应用程序通常会遵循一定的框架,即首先通过面向对象分析与设计过程,认定应用程序所需的对象,包括对象的属性、行为与约束等特性,然后遵循本节所述的几个开发步骤,完成应用程序的开发、部署与运行,如图3-1所示,图中的箭头表示了任务之间的先后次序。
图3-1 CORBA 应用的典型开发过程

下面简要解释开发的每一步骤的主要工作。

1. 编写对象接口

对象接口是分布式对象对外提供的服务的规格说明CORBA 中分布式对象的接口定义,要包括以下内容:

  • 提供或使用的服务的名字,即客户端可以调用的方法名;
  • 每个方法的参数列表与返回值
  • 方法可能会引发的异常
  • 可选的上下文环境,即调用相关的上下文环境,上下文环境起和参数类似的作用,包含调用相关的若干信息,只不过它并不写死在参数列表中,有更强的灵活性。

可以看出,CORBA 中对象接口中定义的核心内容,和 RMI 例子中用 Java interface 定义的接口类似,但它们有一点明显区别,那就是 CORBA 中对象接口是由 OMG IDL 定义的,而这种 IDL 是独立于程序设计语言的。正是有了这种中性的接口约定,才使得分布式对象和客户端的跨语言得以实现。第四章详细介绍了 IDL 的语法与语义。

2. 编译 IDL 文件

IDL 是一种独立于具体程序设计语言的说明性语言,IDL 编译器的作用是IDL 映射到具体程序设计语言,产生客户程序使用的桩代码、以及编写对象实现所需的框架代码。由OMG制订的语言映射规范,允许将 IDL 语言映射到Java、C++、Ada、C、COBOL等多种程序设计语言,这通常是由软件供应商提供的不同编译器分别完成的。

一般厂商实现 CORBA 平台时,都会提供专门的 IDL 编译器来完成 IDL 接口的编译工作。厂商实现 IDL 编译器时,应参照OMG制订的语言的规范,编程人员只要选择使用合适的编译器就可以了,IDL 编译器的工作原理如图3-2所示。
图 3-2 IDL 编译器的工作原理
VisiBroker for Java 提供的 idl2java 编译器,将 IDL 映射到Java语言,生成Java语言的客户端桩代码、以及服务端框架代码,桩和框架可分别看做是「服务对象在客户端和服务端的代理」。IDL 文件严格地定义了客户程序与对象实现之间的接口,因而客户端的桩可以与服务端的框架协调工作,即使这两端是用不同的程序设计语言编译,或运行在不同供应商的 ORB 产品之上。

3. 编写客户程序

CORBA 中,客户程序的流程较为简单,如图3-3所示,首先初始化 ORB ,然后绑定到要使用的服务对象,然后调用服务对象提供的服务。和 Java RMI 相比,CORBA 客户端程序多了一步初始化 ORB 的操作。
图3-3 客户程序的工作流程

CORBA 中,无论是客户程序还是服务程序,都必须在利用 ORB 进行通信之前初始化 ORB 。初始化 ORB 的作用有两个,一个ORB 了解有新的成员加入,以便后继为其提供服务;另一个作用就是获取 ORB 伪对象的引用,以备将来调用 ORB 内核提供的操作。

所谓伪对象,专指在 CORBA 基础设施中的一个对象,比如 ORB 本身可以看作一个伪对象,伪对象的这个“伪”字主要是针对「在程序中远程访问的 CORBA 分布式对象」而言的,因为伪对象是本地的,我们通过伪对象调用 CORBA 基础设施提供的操作。

ORB 内核提供了一些「不依赖于任何对象适配器的操作」,这些操作可由客户程序或对象实现调用,包括获取初始引用的操作、动态调用相关的操作、生成类型码的操作、线程和策略相关的操作等,初始化 ORB 即是其中的一个操作。程序员在程序中通过 ORB 伪对象来调用这些操作,也就是说,在程序中通过使用 <orb 伪对象名>.<方法名> 的方式来调用ORB 内核提供的操作。

4. 编写对象实现和服务程序代码

CORBA 服务端,开发的主要工作包括编写对象实现与服务程序IDL 文件只定义了服务对象的规格说明,程序员必须另外编写服务对象的具体实现

CORBA 规定,所有对象接口定义必须统一用 IDL 书写,但对象实现则有很多选择的余地,例如可使用Java、C++、C、Smalltalk等程序设计语言,并且选择这些语言与客户程序所选用的语言无关,只要 ORB 产品供应商支持 IDL 到这些语言的映射即可。

选用任何一门程序设计语言的程序员,应该熟悉 IDL 到该语言的映射规则,因为通常情况下,IDL 编译器除了生成 StubSkeleton 之外,还会生成一些开发时需要使用辅助代码。例如 VisiBroker for JavaIDL 编译器,将自动生成一些对象适配器的Java类和各种辅助性的Java类,编写对象实现的代码时,必须继承其中的一些类、或使用某些类提供的方法。

Java RMI 例子中类似,CORBA 服务端,除了编写对象实现代码后,还要编写一个服务程序来将分布式对象准备好服务程序利用可移植对象适配器 POA 激活伺服对象供客户程序使用。服务程序通常是一个循环执行的进程,不断监听客户程序请求并为之服务

5. 创建并部署应用程序

编写完代码以后,就可以编译生成目标应用程序了。创建客户程序时,应将「程序员编写的客户程序代码」与「IDL 编译器自动生成的客户程序桩代码」一起编译;创建服务程序时,应将「程序员编写的对象实现代码」与 「IDL 编译器自动生成的服务程序框架代码」一起编译。一些 ORB 产品提供了专门的编译器以简化这一过程,例如 VisiBroker for Java 提供的编译器 vbjc 会自动调用JDK中的Java编译器 javac ,指示 javac 在编译客户程序的同时、编译相关的客户程序桩文件,在编译服务程序的、同时编译相关的服务程序框架文件。

程序员创建的客户程序和服务应用程序,在通过测试并准备投入运行后,进入应用程序的部署 deployment 阶段。分布式系统的布署工作通常远比集中式软件的安装复杂,在该阶段由系统管理员规划,如何在终端用户的桌面系统安装客户程序,或在服务器一类的机器上安装服务程序。由于其复杂性,布署工作经常由单独的角色来承担。

6. 运行应用程序

运行 CORBA 应用程序时,必须首先启动服务程序,然后才可运行客户程序。其他步骤可能与具体 ORB 产品有关,例如 VisiBroker for JavaORB 内核是一个名为 osagent 的独立运行进程(又称智能代理 smart agent ),可以在启动服务程序之后才启动 osagent ,但必须在运行客户程序之前让 osagent 启动完毕。

应注意,上述过程是一个典型的 CORBA 应用程序开发过程,具体实施时,各个步骤会由于不同项目的各自特点而有所区别。例如,利用 CORBA 集成企业原有系统时,就是一种先有对象实现,而后有对象接口规格说明的过程。


3.3 CORBA 开发实例

本节以一个银行帐户管理的简单例子,演示 CORBA 应用程序的典型开发过程,使对「CORBA 应用程序的开发、部署与运行」有一个初步的感性认识。

这是一个简单的银行帐户管理程序,服务端管理大量银行顾客的账户,向远程客户端提供基本的开户、存款、取款、查询余额的功能。

3.3.1 认定分布式对象

按照面向对象的设计理念,我们很容易认定出,系统中应包含图3-4所示的两类对象:

  • Account 是一个银行帐户的实体模型,它有一个属性 balance 表示当前的余额,另有三个行为分别为存款 deposit 、取款 withdraw 和查询余额 getBalance ,每一银行帐户在生存期的任何时刻,都满足帐户余额不小于 0 0 0 这一约束。
  • 由于例子程序不是仅仅管理某一位顾客的帐户,而是涉及到大量的帐户需要处理,所以还建立了“帐户管理员”这一实体模型,它负责对每一个帐户的开设、撤销和访问等,在实现世界中该实体对应着银行中的储蓄员。帐户管理员 AccountManager 有一个属性 accountList ,记录当前已开设的所有帐户,并且有一个行为表示根据帐户标识查找某一帐户,如果该标识的帐户不存在,则创建一个新帐户,我们将该行为命名为 open 。这其实只是帐户管理员的最简化模型,在一个实际应用系统中会赋予帐户管理员这一实体更多的职能。
    图3-4 系统对象

应注意,上述两类对象都是分布式对象,因为对象上的 open, deposit, withdrawgetBalance 操作,都是要被客户端远程调用的。

3.3.2 编写分布式对象的接口

按照图3-1所示的开发过程,对于每一个分布式对象,首先要做得就是定义其接口。我们利用OMG的接口定义语言 IDL ,编写对象 AccountAccountManager 的规格说明,规格说明存放在一个文本文件中,称之为 IDL 文件。从程序3-1所示 IDL 文件中的对象接口定义可看出,IDL 与Java中的 interface 具有类似的语法。第四章将详细介绍如何使用 IDL 定义模块、接口、数据结构等。

// 程序 3-1 Bank.idl 文件中定义的对象接口
// 银行帐户管理系统的对象接口定义
module Bank {
   
	// 帐户
	interface Account {
   
		// 存款
		void deposit(in float amount);
		// 取款
		boolean withdraw(in float amount);
		// 查询余额
		float getBalance();
	};
	
	// 帐户管理员
	interface AccountManager {
   
		// 查询指定名字的帐户,查无则新开帐户
		Account open(in string name);
	};
};

3.3.3 编译 IDL 文件生成桩与框架

完成对象接口的规格说明后,下一步工作是利用 VisiBroker for Java 提供的 idl2java 编译器,根据 IDL 文件生成客户程序的桩代码、以及对象实现的框架代码。客户程序用这些 Java 桩代码调用所有的远程方法,框架代码则与程序员编写的代码一起创建对象实现。

上述 Bank.idl 文件无需作特殊处理,它可用以下命令编译:

prompt> idl2java Bank.idl

由于Java语言规定,每一个文件只能定义一个公有的接口或类,因此 IDL 编译器的输出会生成多个 .java 文件。这些文件存储在一个新建的子目录 Bank 中,BankIDL 文件中指定的模块名字,也是根据该模块中所有 IDL 接口、自动生成的所有Java类所属的程序包

IDL 编译器为 IDL 文件中定义的每一个接口自动生成 7 7 7.java 文件,故上述 IDL 文件的编译结果会生成 14 14 14.java 文件,下面以 Account 接口对应的 7 7 7 个文件为例,介绍 IDL 编译器生成的代码,IDL 编译器为接口 Account 生成的文件包括:

AccountOperations.java
Account.java
_AccountStub.java
AccountPOA.java
AccountPOATie.java
AccountHelper.java
AccountHolder.java

在这些文件中,Account.javaAccountOperations.java 定义了 IDL 接口 Account 的完整基调(signature ,包括操作的名字和参数表,可用于唯一地表示一个操作的类型)。AccountOperations.javaIDL 文件中由 Account 接口定义的所有方法和常量的基调声明,如程序3-2所示。IDL 编译器自动生成的对象实现框架代码,将实现该接口(参见程序3-5),该接口还与 AccountPOATie 类一起提供纽带机制(参见程序3-6)。

// 程序 3-2 IDL 编译器生成的 AccountOperations.java 文件内容
package Bank;
public interface AccountOperations {
   
	public void deposit(float amount);
	public boolean withdraw(float amount);
	public float getBalance();
}

IDL 编译器为每一个 IDL 接口,生成一个最基本的 Java 接口,例如,Account.java 包含了 Account 接口的声明,如程序3-3所示。该接口继承了 AccountOperations 接口。在程序员编写的客户程序中,会使用该接口来调用远程账户对象上的操作。

// 程序 3-3 IDL 编译器生成的 Account.java 文件内容
package Bank;
public interface Account 
	extends com.inprise.vbroker.CORBA.Object,
	Bank.AccountOperations, org.omg.CORBA.portable.IDLEntity {
   }

IDL 编译器还为每一个接口创建一个桩类_AccountStub.javaAccount 对象在客户端的桩代码,它实现了 Account 接口,如程序3-4所示。应注意,此类中 deposit, withdraw, getBanlance 等操作并没有真正实现账户对象的业务逻辑,只是替客户端完成(对)服务端真正业务逻辑实现的调用。该类负责客户端调用账户对象时的底层通信工作,从客户程序的代码上(程序 3-9)看,客户端通过 Account 接口来调用账户对象上的操作,但实际上客户端调用的是该类上的操作,由该类替客户端完成远程调用。

// 程序 3-4 IDL 编译器生成的_AccountStub.java 文件内容
package Bank;
public class _AccountStub 
	extends com.inprise.vbroker.CORBA.portable.ObjectImpl implements Account {
   
	final public static java.lang.Class _opsClass = Bank.AccountOperations.class;
	private static java.lang.String[] __ids = {
   "IDL:Bank/Account:1.0"};
	
	public java.lang.String[] _ids() {
   
		return __ids;
	}	
	
	public void deposit(float amount) {
   
		while (true) {
   
			if (!_is_local()) {
   
				org.omg.CORBA.portable.OutputStream _output = null;
				org.omg.CORBA.portable.InputStream _input = null;
				try {
   
					_output = this._request("deposit", true);
					_output.write_float((float) amount);
					_input = this._invoke(_output);
				} catch(org.omg.CORBA.portable.ApplicationException _exception) {
					final org.omg.CORBA.portable.InputStream in =
					_exception.getInputStream();
					java.lang.String _exception_id = _exception.getId();
					throw new org.omg.CORBA.UNKNOWN("Unexpected User Exception: " + _exception_id);
				} catch(org.omg.CORBA.portable.RemarshalException _exception) {
					continue;
				} finally {
					this._releaseReply(_input);
				}
			} else {
				final org.omg.CORBA.portable.ServantObject _so =
				_servant_preinvoke("deposit", _opsClass);
				if (_so == null) {
					continue;
				}
				final Bank.AccountOperations _self = (Bank.AccountOperations)_so.servant;
				try {
					_self.deposit(amount);
				} finally {
					_servant_postinvoke(_so);
				}
			}
			break;
		}
	}
	
	public boolean withdraw(float amount) {
		while (true) {
			if (!_is_local()) {
   
				org.omg.CORBA.portable.OutputStream _output = null;
				org.omg.CORBA.portable.InputStream _input = null;
				boolean _result;
				try {
   
					_output = this._request("withdraw", true);
					_output.write_float((float)amount);
					_input = this._invoke(_output);
					_result = _input.read_boolean();
					return _result;
				} catch(org.omg.CORBA.portable.ApplicationException _exception) {
					final org.omg.CORBA.portable.InputStream in = _exception.getInputStream();
					java.lang.String _exception_id = _exception.getId();
					throw new org.omg.CORBA.UNKNOWN("Unexpected User Exception: " + _exception_id);
				} catch(org.omg.CORBA.portable.RemarshalException _exception) {
					continue;
				} finally {
					this._releaseReply(_input);
				}
			} else {
				final org.omg.CORBA.portable.ServantObject _so = _servant_preinvoke("withdraw", _opsClass);
				if (_so == null) {
					continue;
				}
				final Bank.AccountOperations _self = (Bank.AccountOperations)_so.servant
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

memcpy0

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值