IDL 接口
全部 CORBA 结构是从一个接口开始的,理解接口的最佳方法就是想像我的汽车,对,我的汽车。虽然您不熟悉它,但如果我对您说:“开上我的车,带些三明治回来当午餐”,恐怕您就不会怀疑自己能不能驾驶我的汽车。您可能想知道它停在哪里,以及开它是否安全,但是您会确信开我的车与开您的车差别不大。这是因为,在各种汽车当中,人与汽车之间的接口已高度标准化了。我的轿车和您的跑车之间可能会有一些差异,但汽车的油门踏板、刹车和方向盘的安装都是标准的,您一定能轻松快速上路。
因为 CORBA 与语言无关,所以它依靠一种接口定义语言 (IDL),来表达客户机如何向实现接口的服务发出请求。我们的接口就是一个方法:add()。这个方法将取两个数(两个 IDL 的 long 型数)并返回这两个数之和。下面是我们的接口计算程序:
清单 1. calcsimpl.idl
module corbasem { module gen { module calcsimpl { interface calculator { long add(in long x, in long y); }; }; }; };
这个接口中的 IDL 关键字有:module、interface、long 和 in。IDL 使用关键字 module 来创建名称空间,并且此关键字准确地映射为 Java 关键字 package。运行 IDL-to-Java 编译器时,生成的 Java 文件将会存到名为 calcsimpl 的子目录中。IDL 关键字 interface 完美地映射为 Java 接口,并代表一种抽象类型,因为两者都只定义您与对象通讯的方式,而不涉及对象的实现。IDL 关键字 long 是一种基本的整数类型,它至少映射为一个 4 字节的类型,这种类型在 Java 代码中就是 int。
想一想执行远程方法调用的机制,您就会发现定义参数传递的方向(客户机到服务器、服务器到客户机或者双向传递)是多么的有意义。在 IDL 操作中,这些方向用 in、out 和 inout 关键字来声明,每个参数都必须声明方向,以便使对象请求代理程序 (ORB) 知道该参数的去向。这会影响到为发送而进行的参数打包、参数解包以及内存管理。ORB 对参数了解得越多,它的效率就越高。关键字 in 表明 long x 和 long y 是从客户机传递到服务器。
图 1. 参与 CORBA 请求的各个部分
IDL 编译器
需要 IDL 编译器吗?
您可能已经有了 ORB 供应商和 IDL-to-Java 编译器。但如果还没有,您从哪里获取呢?这里有好多,而且有些还可以免费下载。我推荐 Object Oriented Concepts, Inc. 的 Orbacus ORB。如果不将其用于商业目的,它还可以免费下载,而且完全符合 CORBA 2.3 规范。另外一个可试用 60 天的编译器是 Inprise 的 Visibroker,也完全符合 CORBA 2.3 规范并且可下载。如想获得这两种产品,请参阅参考资料。
输入下面的命令,从 Orbacus 执行 IDL-to-Java 编译器,把所有生成的文件都放在 CLASSPATH 的输出目录下。
清单 2. 调用 IDL-to-Java 编译器
jidl --output-dir c:\_work\corbasem calculator.idl
生成了什么呢?这个命令生成了构建实现所需要的全部 Java 源文件。IDL-to-Java 编译器可确保所定义的接口遵守 CORBA 规范的规则。
图 2. IDL-to-Java 编译器文件生成
下面是这些文件: calculator.java - 这个文件叫标记接口文件。CORBA 规范指出这个文件必须扩展 IDLEntity,并且与 IDL 接口同名。这个文件提供类型标记,从而使这个接口能用于其它接口的方法声明。 calculatorOperations.java - 这个文件内含 Java 公共接口 -- calculatorOperations。规范指出,这个文件应该与具有 Operations 后缀的 IDL 接口同名,并且这个文件内含此接口映射的操作标记。上面定义的标记接口 (calculator.java) 可扩展这个接口。 calculatorHelper.java - 设计 helper 类的目的是,让所需要的许多内务处理功能脱离我们的接口,但又随时可用到实现过程中。帮助程序文件含有重要的静态 narrow 方法,这种方法使 org.omg.CORBA.Object 收缩为一种更具体的类型的对象引用;在这种情况下,将是一个计算程序类型。 calculatorHolder.java - holder 类是一个专门化类,是为了需要通过引用来传递参数的任意数据类型而生成的。这个示例中将不使用 holder 类,但我们将会在以后的栏目中经常见到它。 calculatorPOA.java - skeleton 类为 CORBA 功能提供了请求-响应探测的一大部分。生成 calculatorPOA.java,是因为缺省方式下的实现是基于继承的,如果我们选择基于委托的实现方式,输出就会不一样。这些主题将在以后的栏目中详细介绍。 _calculatorStub.java - 顾名思义,这是一个 stub 类。您的客户机将需要这个类来进行工作。
服务器
现在生成的文件必须在服务器上开始工作,用这个服务器实现我们的接口。所幸的是,大部分探测是适合我们的要求的,但别高兴得太早 -- 还有许多工作要做;就是说,所有这些文件都必须用在正确的地方。
让我们从 add() 方法的实现开始。(您可以下载完整的 SimpleCalcSvr.java 文件。)
清单 3. SimpleCalcSvr.java -- add() 方法
SimpleCalcServant extends calculatorPOA { public int add(int x, int y) { return x + y; } }
请注意,我们的实现类扩展了已生成的类 calculatorPOA。从客户机发来一个请求时,该请求通过 ORB 进入 skeleton,skeleton 最终将调用 SimpleCalcServant,来完成请求并启动响应。我们的接口很简单,所以我们的实现也很简单。
服务器其余部分的实现,涉及如何围绕这个接口实现来设置 CORBA 体系结构,由于可移植性和灵活性方面的原因,许多这些调用要按 CORBA 规范执行。
我们需要完成的第一项任务是,详细说明要使用哪一个 ORB,然后予以初始化。下面的代码(文件 SimpleCalcSvr.java 的第 18 行到第 29 行)处理此任务:
清单 4. SimpleCalcSvr.java -- 初始化 ORB
java.util.Properties props = System.getProperties(); props.put("org.omg.CORBA.ORBClass", "com.ooc.CORBA.ORG"); props.put("org.omg.CORBA.ORBSingletonClass", "com.ooc.CORBA.ORBSingleton"); org.omg.CORBA.ORB orb = null; // 初始化 ORB orb = org.omg.CORBA.ORB.init(args, props);
初始化 ORB 时,需要准确地告诉它哪一个类将用作 ORBClass,哪一个类将用作 ORBSingleton 类。我们的实现将不考虑这些,但所有相关的探测则都将考虑这些。正如我前面所说的,这种情况下,我使用的是 Object Oriented Concepts, Inc. 的 Orbacus ORB,而 OOC 类在那两个 props.put() 调用中已给出。一旦填入了属性,props 就只作为一个参数传递给 ORB.init() 方法。实际情况可能不是这样;如果我们要把这个服务器移到另一个 ORB,不希望为服务器重新编码。所以,在理想情况下,我们宁愿改变一个配置文件,使之指向另一个 ORB 类,然后直接重新启动。
现在,ORB 已经到位并已初始化,并且实现也已经到位,只是尚未创建,此时,需要为实现创建一个完善的生存地点,而这可不像听起来那么容易,在一个分布式环境中,各个实现要求的环境可能略有不同。可以赋予实现许多特征。实现既可以是单线程的,也可以是多线程的;既可以是具有高度可伸缩性的对象池,也可以是单元素。这许多不同的服务器特征已产生了可移植对象适配器 (POA)。POA 使我们可以创建完善的环境,供我们的实现在其中驻留。所有符合 2.3 规范的 ORB 都会有一个根 POA,所有其它 POA 都是从根 POA 创建的。在这个简单示例中,我已将实现专用的代码分解为它自己的方法 runcalc()。
为实现创建一个环境将是我们的第一项任务,所以必须设置一个 POA。本来,CORBA 服务器使用基本对象适配器 (BOA),但是每个供应商的 BOA 都不一样,在最新版本的 CORBA 规范中,POA 已完全取代了 BOA。
清单 5. SimpleCalcSvr.java -- 设置 POA
// 从始终存在的 rootPOA // 设置可移植对象适配器 org.omg.PortableServer.POA rootPOA = org.omg.PortableServer.POAHelper.narrow( orb.resolve_initial_references("RootPOA")); org.omg.PortableServer.POAManager manager = rootPOA.the_POAManager();
从标题和定义可以看出,这是一个简单的示例。使用根 POA 而不创建新的 POA,将使事情变得简单。POA 管理器是一种封装了 POA 处理状态的对象,所以,我们使用 POA 管理器,将发给 servant 的请求排队。
还需要实例化实现:
清单 6. SimpleCalcSvr.java -- 实例化实现
// 创建计算程序接口的 servant SimpleCalcServant calcSvt = new SimpleCalcServant(); calculator calc = calcSvt._this(orb);
按照 CORBA 2.3 规范,所有 skeleton 均提供一个 _this() 方法,该方法使 servant 能得到目标 CORBA 对象的对象引用,servant 正是用目标 CORBA 对象来与这些请求相关联的。
完成实现的实例化以后,就必须把机制放到适当的位置,以便客户机能够找到它们。有许多不同的方法和服务可用来找到满足接口请求的对象。CORBA Service 定义 Naming Service 和 Trader Services,来专门帮助客户机查找对象,以处理请求。也可以通过方法调用来传递对象。
在这个示例中,我们将使用所有方法中最直截了当的一种 — 将对象引用写入一个文件,该文件将由客户机选取。对于所有的 ORB 来说,创建一个对象引用的字符串表示,或者反过来,创建由字符串到对象的引用,都是必备的功能。
清单 7. SimpleCalcSvr.java -- 编写对象引用
// 将对象引用写入一个文件 PrintWriter refstr = new PrintWriter( new FileWriter("calcref.ior")); refstr.println(orb.object_to_string(calc)); refstr.close();
最后要做的一件事,就是激活 POA,使客户机请求开始排队,并强制服务器输入其事件循环,以接收这些传入的请求。
清单 8. SimpleCalcSvr.java -- 激活 POA
// 使实现成为可用 manager.activate(); System.out.println("SimpleCalcSvr is running!"); orb.run();
客户机
如果您考虑一下正在发生的事件的机制,就会明白客户机和服务器实际上正是互为映像的。客户机将所有的参数打包以创建一个请求,然后以它自己的方式来发送这个请求。服务器只是将请求中的参数解包,执行运算,将返回值和输出参数打包,然后向客户机发回响应。客户机则将返回值和输出参数解包,然后继续处理。这样,客户机打包什么,服务器就解包什么,反之亦然。
这意味着您将会看到客户机和服务器具有相似的结构。客户机还必须创建并初始化一个 ORB。它可以是我们正在使用的 ORB,也可以是另一个供应商提供的 ORB;但是,不能是任意的 ORB,而应该是支持 IIOP 的 ORB,IIOP 是由对象管理集团 (OMG) 定义的、基于 TCP/IP 的互操作性协议。如果您的 ORB 比较旧,那么请小心,它可能无法与其它 ORB 通话。
首先,我们以相同的方式创建 ORB,就像创建服务器一样。(您可以下载完整的 SimpleCalcClient.java 文件。)
清单 9. SimpleCalcClient.java -- 初始化 ORB
java.util.Properties props = System.getProperties(); props.put("org.omg.CORBA.ORBClass", "com.ooc.CORBA.ORG"); props.put("org.omg.CORBA.ORBSingletonClass", "com.ooc.CORBA.ORBSingleton"); org.omg.CORBA.ORB orb = null; // 初始化 ORB orb = ORB.init(args, props);
看起来眼熟?应该是这样,它看起来与服务器完全一样。现在,客户机已经连接到了一个 ORB 之上,但我们的目标是调用一个服务,而这个服务是在系统中别的地方提供的,需要找到能响应请求的对象。在这个示例中,这意味着要从创建于服务器上的文件中获取一个对象引用。为了找到计算程序服务器,需要取得存储在这个文件中的对象引用的字符串版本,然后把它转换成对象引用,通过这个对象引用就可以进行调用了。
清单 10. SimpleCalcClient.java -- 获取对象引用
System.out.println("Getting reference from string..."); BufferedReader in = new BufferedReader( new FileReader("calcref.ior") ); String ior = in.readLine(); in.close(); calculator calc = calculatorHelper.narrow( orb.string_to_object(ior));
请注意,这里使用了由 IDL-to-Java 编译器生成的 calculatorHelper 类。calcref.ior 文件含有一个对象引用,而不是含有计算程序引用。calculatorHelper 类有一个 narrow 方法,可用来将抽象类型集中到特定的计算程序类型。
仔细看一看计算程序 calc,它表示计算机空间中另外某个地方的一个服务器。最后必须做的一件事,就是调用 calc 上的方法 add()。
清单 11. SimpleCalcClient.java -- 调用 add()
System.out.println( calc.add(2,3) );
结论
已经讨论了很多内容,不过请想一想,都学到了什么。我们的客户机与服务器是完全隔离的,客户机不知道服务器在什么样的硬件上运行,使用的是什么操作系统,它是用什么语言编写的,它是不是多线程的,还有,它位于何处 — 是在隔壁,还是距离半个地球之遥。它只知道一点,即如果它调用 calc 中的 add(),就会得到可以指望的响应。
提供服务的情形全都是这样,电话或电力公司也是如此。当您拿起电话的时候,您所期望的是听到拔号音,然后您的呼叫能畅通连接,您并不在乎电话是通过光缆传输的还是通过卫星转发的,同样的情况在信息产业中也正在成为现实。多亏有了 OMG 和这个基本结构,我们才得以加进这个既简单而又非常有说服力的例子。
下个月,我们将稍微深入地发掘一下,看一看发生在表面现象之下的 IIOP 的神奇力量。
本文地址:http://com.8s8s.com/it/it15469.htm