CORBA与RMI的比较 及 实例

Java远程方法调用(RMI)机制和公用对象请求代理体系(CORBA)是最重要和使用最广泛的两种分布式对象系统。每个系统都有其特点和短处。它们在行业中被用于从电子交易到保健医疗的各个领域。一个项目如果要从这两种分布式机制中选用一个,往往难以抉择。本文概括地介绍了RMICORBA,更重要的是,它将介绍如何开发一个有用的应用程序,用于从远程主机下载文件。然后它将:

  • 简要介绍分布式对象系统
  • 简要介绍RMICORBA
  • 让你对在RMICORBA中开发应用程序所涉及的工作有个初步印象
  • 演示如何使用RMICORBA,从远程主机传送文件
  • RMICORBA进行简单比较

客户机/服务器模型

客户机/服务器模型是分布式计算的一种形式,在这种形式中,一个程序(客户机)与另一个程序(服务器)通讯以便交换信息。在这种模型中,客户机和服务器通常都说同样的语言--也就是说客户机和服务器能理解同一个协议--这样它们才能通讯。

虽然客户机/服务器模型的实现方式多种多样,但典型做法是使用底层套接字。使用套接字开发客户机/服务器系统意味着,我们必须设计一个协议,也就是客户机和服务器都认识的一组命令集,通过这些命令它们就能通讯了。举例来说, HTTP协议中提供了一个名为GET的方法,所有Web服务器都必须实现这个方法,所Web客户机(浏览器)都必须使用这个方法,才能获取文档。

分布式对象模型

基于分布式对象的系统是一组对象的集合,这些对象以一种明确定义封装的接口把服务的请求者(客户机)和服务的提供者(服务器)分隔开。换言之,客户机从服务的实现中分离出来,变成数据的呈现和可执行代码。这就是基于分布式对象的模型与纯粹的客户机/服务器模型的主要区别之一。

在基于分布式对象的模型中,客户机向对象发送消息,然后对象解释该消息以便决定要执行什么服务。这项服务,也就是方法,可以选择是让对象还是让代理来执行。Java远程方法调用(RMI)和公用对象请求代理体系(CORBA)就是这种模型的例子。

RMI

RMI是一个分布式对象系统,它使你能够轻松地开发出分布式Java应用程序。RMI中开发分布式应用程序比用套接字开发要简单,因为不需要做设计协议这种很容易出错的工作。在RMI中,开发者会有一种错觉,似乎是从本地类文件调用的本地方法,其实参数传送给了远程目标,目标解释参数后再把结果发回给调用方。

RMI应用程序初步

使用RMI开发分布式应用程序包括以下步骤:

  1. 定义一个远程接口
  2. 实现这个远程接口
  3. 开发服务器
  4. 开发客户机
  5. 生成存根和基干,启动RMI注册表、服务器和客户机

下面我们将通过开发一个文件传输程序来实践这些步骤。

范例:文件传输程序

这个应用程序允许客户机从远程主机上传送(即下载)任何类型的文件(纯文本或二进制文件)。第一步是定义一个远程接口,这个接口规定了服务器所提供方法的信号,客户机将调用这些方法。

定义一个远程接口

用于文件下载应用程序的远程接口如代码范例1所示。接口 FileInterface提供了一个方法downloadFile,这个方法接受String参数(文件名),将文件的数据以字节数组的形式返回。

代码范例1 1: FileInterface.java

import java.rmi.Remote;

import java.rmi.RemoteException;

public interface FileInterface extends Remote {

public byte[] downloadFile(String fileName) throws

RemoteException;

}

请注意FileInterface的以下特征:

  • 它必须声明为public,这样客户机才能加载实现远程接口的远程对象。
  • 它必须扩展为Remote接口,以满足使该对象成为远程对象的要求。
  • 这个接口中的每种方法都必须投出一个java.rmi.RemoteException

实现远程接口

下一步是实现接口FileInterface。实现的范例见代码范例2请注意,除了实现FileInterface之外,还把FileImpl 类扩展为UnicastRemoteObject。这表示FileImpl将用于创建一个单独的、不可复制的远程对象,它使用RMI缺省的基于TCP的传送通道进行通讯。

代码范例2: FileImpl.java

import java.io.*;

import java.rmi.*;

import java.rmi.server.UnicastRemoteObject;

public class FileImpl extends UnicastRemoteObject

implements FileInterface {

private String name;

public FileImpl(String s) throws RemoteException{

super();

name = s;

}

public byte[] downloadFile(String fileName){

try {

File file = new File(fileName);

byte buffer[] = new byte[(int)file.length()];

BufferedInputStream input = new

BufferedInputStream(new FileInputStream(fileName));

input.read(buffer,0,buffer.length);

input.close();

return(buffer);

} catch(Exception e){

System.out.println("FileImpl: "+e.getMessage());

e.printStackTrace();

return(null);

}

}

}

开发服务器

第三个步骤是开发服务器。服务器需要做三件事:

  1. 创建RMISecurityManager的一个实例并安装它
  2. 创建远程对象(在本例中是FileImpl)的一个实例
  3. RMI注册表中登记这个创建的对象。实现的范例见代码范例3

代码范例 3: FileServer.java

import java.io.*;

import java.rmi.*;

public class FileServer {

public static void main(String argv[]) {

if(System.getSecurityManager() == null) {

System.setSecurityManager(new RMISecurityManager());

}

try {

FileInterface fi = new FileImpl("FileServer");

Naming.rebind("//127.0.0.1/FileServer", fi);

} catch(Exception e) {

System.out.println("FileServer: "+e.getMessage());

e.printStackTrace();

}

}

}

语句Naming.rebind("//127.0.0.1/FileServer", fi)假定RMI 注册表在缺省的端口号1099上运行。

但是,如果RMI注册表在其他端口号上运行, 就必须在这一句中指定端口号。例如,如果RMI注册表在端口4500

上运行,那么 这一句就变成:

Naming.rebind("//127.0.0.1:4500/FileServer", fi)

另外,在这里要着重指出,我们假定rmi注册表和服务器是在同一台电脑上运行。如果不是这样,只需修改rebind方法中的地址即可。

开发客户机

下一步是开发客户机。客户机可以远程调用远程接口FileInterface)中指定的任何方法。但是为了能这么做,客户机首先必须从RMI注册表中获得指向该远程对象的引用。获得引用之后就可以调downloadFile方法了。客户机的实现请见代码范例4。在这个实现中,客户机从命令行接收两个参数:

第一个参数是要下载文件的名称,第二个参数是要下载的文件所在主机的地址,也就是运行文件服务器的那台电脑的地址。

代码范例4: FileClient.java

import java.io.*;

import java.rmi.*;

public class FileClient{

public static void main(String argv[]) {

if(argv.length != 2) {

System.out.println("Usage: java FileClient fileName machineName");

System.exit(0);

}

try {

String name = "//" + argv[1] + "/FileServer";

FileInterface fi = (FileInterface) Naming.lookup(name);

byte[] filedata = fi.downloadFile(argv[0]);

File file = new File(argv[0]);

BufferedOutputStream output = new

BufferedOutputStream(new FileOutputStream(file.getName()));

output.write(filedata,0,filedata.length);

output.flush();

output.close();

} catch(Exception e) {

System.err.println("FileServer exception: "+ e.getMessage());

e.printStackTrace();

}

}

}

运行应用程序

为了运行应用程序,我们需要生成存根和基干,编译服务器和客户机,启动 RMI注册表,最后是启动服务器和客户机。

为了生成存根和基干,请使用rmic编译器:

prompt> rmic FileImpl

这将生成两个文件:FileImpl_Stub.class FileImpl_Skel.class。存根是一个客户机代理,基干是一个服务器基干。

下一步是编译服务器和客户机。用javac编译器来做这件事。但是请注意:如果服务器和客户机是在两台不同的机器上开发的,为了编译客户机,需要把接口FileInterface)复制一份。

最后,启动RMI注册表并运行服务器和客户机。为了在缺省的端口号上启动 RMI注册表,请在Windows中使用命令rmiregistry start rmiregistry。为了在其他端口号上启动RMI注册表,可以提供该端口号作为RMI注册表的一个参数:

prompt> rmiregistry portNumber

运行RMI注册表之后,就可以启动服务器FileServer了。但是,因为在服务器应用程序中正在使用RMI安全管理员,所以需要一个安全方针来与之相配。下面是一个安全方针范例:

grant {

permission java.security.AllPermission "", "";

};

注意: 这只是一个方针的例子。它允许任何人做任何事情。对于关键性事务应用程序,你需要指定更严格的安全方针。

现在,为了启动服务器,需要把除了客户机类FileClient.class)之外的所有类(包括存根和基干)复制一份。请使用以下命令启动服务器,假定安全方针位于文件policy.txt中:

prompt> java -Djava.security.policy=policy.txt FileServer

为了在另一台机器上启动客户机,需要复制远程接口FileInterface.class)和存根FileImpl_Stub.class)。请使用以下命令启动客户机:

prompt> java FileClient fileName machineName

其中fileNamefileName是要下载的文件,machineName 是该文件所在的机器(运行文件服务器的那台机器)。如果一切顺利,那么客户机就存在了,下载完的文件保存在本地的机器上。


要运行前面介绍的客户机,需要复制接口和存根。更适当的方法是使用RMI动态类加载。这种做法不需要复制接口和存根。取而代之的做法是,可以把接口和存根放在共享的目录里供服务器和客户机使用,在需要存根或者基干的时候,RMI类加载器就会自动下载它。举例来说,用以下命令运行客户机: java -Djava.rmi.server.codebase=http://hostname/locationOfClasses FileClient fileName machineName有关这种方法的更多信息,请参见使用 RMI加载动态代码。


CORBA

公用对象请求代理体系(即CORBA)是由对象管理组织(OMG)开发的一项工业标准,用于帮助分布式对象编程。要注意的重要一点是,CORBA只是一项规范。 CORBA的实现称为ORB(对象请求代理)。从市场上可以找到几个CORBA的实现,比如VisiBrokerORBIX,等等。JavaIDL是另一个实现,它是JDK1.3或更高版本的核心软件包之一。

CORBA被设计成与平台和语言无关。因此,CORBA对象可以运行于任何平台之上,位于网络的任何位置,还可以用任何语言编写,只要该语言具有Interface Definition LanguageIDL,接口定义语言)的映射。

RMI类似,CORBA对象是用接口规定的。但是CORBA中的接口在IDL中指定。虽然IDLC++相似,但请注意,IDL并不是一种编程语言。有关CORBA的详细介绍,请参见用 Java进行分布式编程:11章(CORBA概述)

CORBA应用程序初步

开发CORBA应用程序包括许多步骤。它们是:

1.    IDL中定义一个接口

2.    IDL接口映射到Java中(自动完成)

3.    实现这个接口

4.    开发服务器

5.    开发客户机

6.    运行命名服务、服务器和客户机。

我们现在要开发一个基于CORBA的文件传送程序,以此来解释各个步骤,这个程序类似于本文前面开发的那个RMI应用程序。在这里将使用JavaIDL,它是 JDK1.3+的核心软件包之一。

定义接口

在定义CORBA接口时,请考虑服务器将支持的操作类型。在文件传输应用程序中,客户机将调用一个方法来下载文件。代码范例5显示了FileInterface 的接口。Data是使用typedef关键字引入的新类型。 IDL中的sequence类似于数组,区别在于序列没有固定的大小。 octet是一个8-bit数,等价于Java中的类型byte

请注意,downloadFile方法接收一个类型为string ,声明为in的参数。IDL定义了三种参数传送模式:in (从客户机输入到服务器),out(从服务器输出到客户机), inout(输入输出都可用)。

代码范例 5: FileInterface.idl

interface FileInterface {

typedef sequence<octet> Data;

Data downloadFile(in string fileName);

};

定义好IDL接口之后,就可以编译它了。JDK 1.3+附带了idlj 编译器,用于把IDL定义映射为Java的声明和语句。

idlj编译器可以通过选项来指定是生成客户机存根、服务器基干,还是二者都生成。-f选项用于指定要生成什么。 side可以是client, server或者 all,用于指定客户机存根和服务器基干。在这个例子中,因为应用程序将在两台单独的机器上运行,所以在服务器端使用-fserver 选项,而在客户机端使用-fclient选项。

现在编译FileInterface.idl,生成服务器端基干。请使用命令:

prompt> idlj -fserver FileInterface.idl

这条命令将产生几个文件,比如基干,持有者和辅助器类,等等。其中生成的一个重要文件是_FileInterfaceImplBase,它是实现接口的类的子类。

实现接口

下面我们提供了downloadFile方法的一个实现。这个实现称为仆人,正如你从代码范例6中看到的那样,类FileServant扩展了 _FileInterfaceImplBase类,以便把这个仆人指定为一个CORBA 对象。

代码范例 6: FileServant.java

import java.io.*;

 

public class FileServant extends _FileInterfaceImplBase {

public byte[] downloadFile(String fileName){

File file = new File(fileName);

byte buffer[] = new byte[(int)file.length()];

try {

BufferedInputStream input = new

BufferedInputStream(new FileInputStream(fileName));

input.read(buffer,0,buffer.length);

input.close();

} catch(Exception e) {

System.out.println("FileServant Error: "+e.getMessage());

e.printStackTrace();

}

return(buffer);

}

}

开发服务器

下一步是开发CORBA服务器。代码范例7中的FileServer类实现了一个CORBA服务器,它做了这么一些事情:

1.    初始化ORB

2.    创建一个FileServant对象

3.    CORBA命名服务(COS命名)中登记该对象

4.    输出一条状态消息

5.    等待客户机的请求到来

代码范例 7: FileServer.java

import java.io.*;

import org.omg.CosNaming.*;

import org.omg.CosNaming.NamingContextPackage.*;

import org.omg.CORBA.*;

public class FileServer {

public static void main(String args[]) {

try{

// create and initialize the ORB

ORB orb = ORB.init(args, null);

// create the servant and register it with the ORB

FileServant fileRef = new FileServant();

orb.connect(fileRef);

// get the root naming context

org.omg.CORBA.Object objRef =

orb.resolve_initial_references("NameService");

NamingContext ncRef = NamingContextHelper.narrow(objRef);

// Bind the object reference in naming

NameComponent nc = new NameComponent("FileTransfer", " ");

NameComponent path[] = {nc};

ncRef.rebind(path, fileRef);

System.out.println("Server started....");

// Wait for invocations from clients

java.lang.Object sync = new java.lang.Object();

synchronized(sync){

sync.wait();

}

} catch(Exception e) {

System.err.println("ERROR: " + e.getMessage());

e.printStackTrace(System.out);

}

}

}

FileServer有了一个ORB之后,就可以注册CORBA服务。它使用 COS命名服务进行注册,该服务由OMG制订,

Java IDL实现。从获取指向命名服务根的引用开始。这将返回一个普通CORBA对象。为了把这个对象用作一个

NamingContext对象,必须把它缩短(也就是强制转换)为适当类型,用下列语句实现:

NamingContext ncRef = NamingContextHelper.narrow(objRef);

ncRef对象现在变成了 org.omg.CosNaming.NamingContext你可以使用rebind 方法,用这个对象在命名服务中注册一项CORBA服务。

开发客户机

下一步是开发客户机。代码范例8中演示了一个实现。获得指向命名服务的引用之后,就可以用它来访问命名服务和查找其他服务(例如FileTransferFileTransfer服务时,将调用downloadFile 方法。

代码范例 8: FileClient

import java.io.*;

import java.util.*;

import org.omg.CosNaming.*;

import org.omg.CORBA.*;

public class FileClient {

public static void main(String argv[]) {

try {

// create and initialize the ORB

ORB orb = ORB.init(argv, null);

// get the root naming context

org.omg.CORBA.Object objRef =

orb.resolve_initial_references("NameService");

NamingContext ncRef = NamingContextHelper.narrow(objRef);

NameComponent nc = new NameComponent("FileTransfer", " ");     

// Resolve the object reference in naming

NameComponent path[] = {nc};

FileInterfaceOperations fileRef =

FileInterfaceHelper.narrow(ncRef.resolve(path));

if(argv.length < 1) {

System.out.println("Usage: java FileClient filename");

}

// save the file

File file = new File(argv[0]);

byte data[] = fileRef.downloadFile(argv[0]);

BufferedOutputStream output = new

BufferedOutputStream(new FileOutputStream(argv[0]));

output.write(data, 0, data.length);

output.flush();

output.close();

} catch(Exception e) {

System.out.println("FileClient Error: " + e.getMessage());

e.printStackTrace();

}

}

}

运行应用程序

最后一步是运行应用程序。这其中包括几个子步骤:

1.    运行CORBA命名服务。这可以使用命令tnameserv缺省情况下,该服务在端口900上运行。如果不能在这个端口运行命名服务,你可以在其他端口上启动它。例如,要在端口2500上启动命名服务,请使用以下命令:

prompt> tnameserv -ORBinitialPort 2500

 

2.    启动服务器。如下所示,假定命名服务是在缺省的端口号上运行:

prompt> java FileServer

如果命名服务运行于其他端口之上,比如2500,则需要使用 ORBInitialPort选项来指定端口,如下所示:

prompt> java FileServer -ORBInitialPort 2500

3.    生成用于客户机的存根。在可以运行客户机之前,先要生成客户机的存根。为此需要复制FileInterface.idl文件,并使用idlj译器来编译它,在编译前指定希望生成的结果是客户机端存根,如下所示:

prompt> idlj -fclient FileInterface.idl

 

4.    运行客户机假定命名服务在端口2500上运行,那么现在就可以使用以下命令来运行客户机了。 

prompt> java FileClient hello.txt -ORBInitialPort 2500 

其中hello.txt是我们要从服务器下载的文件。


注意: 如果要在另一台主机上运行命名服务,请使用 -ORBInitialHost/CODE>选项,指定它在哪台主机上运行。例如,如果 要在名为gosling的主机的端口号4500上运行命名服务,则使用 以下命令启动客户机:

prompt> java FileClient hello.txt -ORBInitialHost gosling -ORBInitialPort 4500


另一种做法是,在代码级使用属性指定这些选项。所以除了像下面这样初始化 ORB

ORB orb = ORB.init(argv, null);

还可以通过指定CORBA服务器所在机器(名为gosling)和命名服务的端口号( 2500)来进行初始化,如下所示:

 

Properties props = new Properties();

props.put("org.omg.CORBA.ORBInitialHost", "gosling"); 

props.put("orb.omg.CORBA.ORBInitialPort", "2500"); 

ORB orb = ORB.init(args, props); 

练习

在文件传输应用程序中,客户机必须预先知道要下载的文件的名称(RMI CORBA中都是如此)。可是并未提供列出服务器上可用文件的方法。作为练习,你也许希望改进这个应用程序,添加一个列出服务器上可用文件的方法。另外,你可能不想使用命令行客户机,而开发一个基于GUI的客户机。在客户机启动时,它调用服务器上的一个方法,获得文件列表,然后弹出一个菜单,显示可用的文件,用户可以从中选择一个或多个要下载的文件,如图1所示。

 

CORBARMI的比较

从编码的角度看,很明显RMI更易于使用,因为Java开发者不需要熟悉接口定义语言(IDL)。但是总的说来,CORBA在以下方面与RMI有所不同:

·         CORBA接口用IDL定义,RMI接口用Java定义。RMI-IIOP允许你用Java定义所有接口(请参见 RMI-IIOP)。

·         CORBA支持inout参数,而RMI不支持,因为本地对象是通过复制传送的,远程对象是通过引用传送的。

·         CORBA被特意设计成与语言不相关。举例来说,这意味着可以用Java编写一些对象,用C++编写其他对象,而它们仍然可以协同工作。所以,作为不同编程语言间的桥梁,CORBA是一种理想机制。与此不同,RMI为一种单独的语言设计,所有对象都可用Java编写。但是请注意,用RMI-IIOP有可能达到协同工作的能力。

·         CORBA对象不能进行碎片收集。正如我们前面说的,CORBA与语言无关,某些语言(比如C++)不支持碎片收集。可以认为这是项缺点,因为CORBA对象一旦创建,就一直存在,直至被除去为止,而决定何时除去一个对象的工作量可不小。与此相反,RMI对象会自动收集碎片。

结论

开发基于分布式对象的应用程序可以在Java中用RMIJavaIDLCORBA的一个实现)完成。这两种技术的用法类似,第一步都是定义对象的接口。但是与RMI Java定义接口不同,CORBA接口是用接口定义语言(IDL)定义的。可是这就多了一层复杂度,开发者需要熟悉IDL,同样重要的是,还要熟悉它到Java的映射。

在这两种分布式机制中如何选择,取决于当前项目及其需求。我希望此文能为你开发基于分布式对象的应用程序提供足够的信息,为帮助你选择分布式机制提供足够的指导。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值