扫一扫
分享文章到微信
扫一扫
关注官方公众号
至顶头条
Java远程方法调用(RMI)机制和公用对象请求代理体系(CORBA)是最重要和使用最广泛的两种分布式对象系统。每个系统都有其特点和短处。它们在行 业中被用于从电子交易到保健医疗的各个领域。一个项目如果要从这两种分布式机制中选用一个,往往难以抉择。本文概括地介绍了RMI和CORBA,更重要的是,它将介绍如何开发一个有用的应用程序,用于从远程主机下载文件。然后它将:
客户机/服务器模型是分布式计算的一种形式,在这种形式中,一个程序(客 户机)与另一个程序(服务器)通讯以便交换信息。在这种模型中,客户机和服 务器通常都说同样的语言--也就是说客户机和服务器能理解同一个协议--这 样它们才能通讯。
虽然客户机/服务器模型的实现方式多种多样,但典型做法是使用底层套接字。使用套接字开发客户机/服务器系统意味着,我们必须设计一个协议,也就是客户 机和服务器都认识的一组命令集,通过这些命令它们就能通讯了。举例来说, HTTP协议中提供了一个名为GET的方法,所有Web服务器都必须实现这个方法,所 有Web客户机(浏览器)都必须使用这个方法,才能获取文档。
基于分布式对象的系统是一组对象的集合,这些对象以一种明确定义封装的接口把服务的请求者(客户机)和服务的提供者(服务器)分隔开。换言之,客户 机从服务的实现中分离出来,变成数据的呈现和可执行代码。这就是基于分布式对象的模型与纯粹的客户机/服务器模型的主要区别之一。
在基于分布式对象的模型中,客户机向对象发送消息,然后对象解释该消息以 便决定要执行什么服务。这项服务,也就是方法,可以选择是让对象还是让代理 来执行。Java远程方法调用(RMI)和公用对象请求代理体系(CORBA)就是这种 模型的例子。
RMI是一个分布式对象系统,它使你能够轻松地开发出分布式Java应用程序。在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);
}
}
}
开发服务器
第三个步骤是开发服务器。服务器需要做三件事:
FileImpl
)的一个实例
代码范例 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
其中fileName
fileName是要下载的文件,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服务器,它做了这么一些事情:
代码范例 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中演示了一个实现。获得指向命名服务的引 用之后,就可以用它来访问命名服务和查找其他服务(例如FileTransfer
FileTransfer服务时,将调用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();
}
}
}
运行应用程序
最后一步是运行应用程序。这其中包括几个子步骤:
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
prompt> java FileClient hello.txt -ORBInitialPort 2500
注意: 如果要在另一台主机上运行命名服务,请使用
-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所示。
从编码的角度看,很明显RMI更易于使用,因为Java开发者不需要熟悉接口定 义语言(IDL)。但是总的说来,CORBA在以下方面与RMI有所不同:
开发基于分布式对象的应用程序可以在Java中用RMI或JavaIDL(CORBA的一个实现)完成。这两种技术的用法类似,第一步都是定义对象的接口。但是与RMI 用Java定义接口不同,CORBA接口是用接口定义语言(IDL)定义的。可是这就多了一层复杂度,开发者需要熟悉IDL,同样重要的是,还要熟悉它到Java的映射。
在这两种分布式机制中如何选择,取决于当前项目及其需求。我希望此文能为 你开发基于分布式对象的应用程序提供足够的信息,为帮助你选择分布式机制提 供足够的指导。
如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。