用RMI和CORBA进行分布式Java编程

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

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

简要介绍分布式对象系统
简要介绍RMI和CORBA
让你对在RMI和CORBA中开发应用程序所涉及的工作有个初步印象
演示如何使用RMI和CORBA,从远程主机传送文件
对RMI和CORBA进行简单比较
客户机/服务器模型

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

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

分布式对象模型

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

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

RMI

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

RMI应用程序初步

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

定义一个远程接口
实现这个远程接口
开发服务器
开发客户机
生成存根和基干,启动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);
}
}
}
开发服务器
第三个步骤是开发服务器。服务器需要做三件事:

创建RMISecurityManager的一个实例并安装它
创建远程对象(在本例中是FileImpl)的一个实例
在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的实现, 比如VisiBroker、ORBIX,等等。JavaIDL是另一个实现,它是JDK1.3或更高版本 的核心软件包之一。

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

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

CORBA应用程序初步

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

在IDL中定义一个接口
把IDL接口映射到Java中(自动完成)
实现这个接口
开发服务器
开发客户机
运行命名服务、服务器和客户机。
我们现在要开发一个基于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服务器,它做了这么一些事情:

初始化ORB
创建一个FileServant对象
在CORBA命名服务(COS命名)中登记该对象
输出一条状态消息
等待客户机的请求到来
代码范例 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();
}
}
}
运行应用程序
最后一步是运行应用程序。这其中包括几个子步骤:

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

prompt> tnameserv -ORBinitialPort 2500

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

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

prompt> java FileServer -ORBInitialPort 2500

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

prompt> idlj -fclient FileInterface.idl

 
运行客户机假定命名服务在端口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所示。


CORBA与RMI的比较

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

CORBA接口用IDL定义,RMI接口用Java定义。RMI-IIOP允许你用Java定义 所有接口(请参见 RMI-IIOP)。
CORBA支持in和out参数,而RMI不支持,因为 本地对象是通过复制传送的,远程对象是通过引用传送的。
CORBA被特意设计成与语言不相关。举例来说,这意味着可以用Java编写一 些对象,用C++编写其他对象,而它们仍然可以协同工作。所以,作为不同编程 语言间的桥梁,CORBA是一种理想机制。与此不同,RMI为一种单独的语言设计, 所有对象都可用Java编写。但是请注意,用RMI-IIOP有可能达到协同工作的能 力。
CORBA对象不能进行碎片收集。正如我们前面说的,CORBA与语言无关,某 些语言(比如C++)不支持碎片收集。可以认为这是项缺点,因为CORBA对象一 旦创建,就一直存在,直至被除去为止,而决定何时除去一个对象的工作量可 不小。与此相反,RMI对象会自动收集碎片。
结论

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

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

更多信息

- RMI
- CORBA规范(OMG)
- JavaIDL
- 《用Java进行分布式编程》一书(第11章: CORBA概述)
- CORBA服务器和客户机服务小程序
- RMI-IIOP

你可能感兴趣的:(java,编程,应用服务器,浏览器,网络应用)