本篇用实例来说明采用Java进行RMI远程方法调用及CORBA服务调用的实现方法,并分析它们的异同,从而为分布应用软件的开发者提供参考和帮助。
Internet/Intranet的飞速发展使得Web应用日益广泛而复杂,Web早已不仅仅是超媒体信息的浏览工具,它正逐步发展成为分布异构环境中企业应用的通用前端和事务处理的展现窗口。在分布式环境异构中,各种机器所采用的操作系统、网络通信协议和应用软件千差万别,要实现信息共享和软件资源的整合十分困难,然而一个健壮的分布式计算框架能为可移植的分布式应用软件开发带来巨大的便利和好处。
分布式对象技术主要是在分布式异构环境下建立应用系统框架和对象构件。在应用系统框架的支撑下,开发者可以将软件功能封装为更易管理和使用的对象,这些对象可以跨越不同的软、硬件平台进行互操作。目前,分布式互操作标准主要有Microsoft的COM/DCOM标准、Sun公司的Java RMI标准和OMG组织的CORBA标准。
Java RMI调用实例
Java RMI简介
远程方法调用(RMI,Remote Method Invocation)是jdk1.1中引入的分布式对象软件包,它的出现大大简化了分布异构环境中Java应用之间的通信。
要使用RMI,必须构建四个主要的类:远程对象的本地接口、远程对象实现、RMI客户机和RMI服务器。RMI服务器生成远程对象实现的一个实例,并用一个专有的URL注册。RMI客户机在远程RMI服务器上查找服务对象,并将它转换成本地接口类型,然后像对待一个本地对象一样使用它。
下面是一个简单的RMI实例,RMI客户机通过RMI服务器提供的方法实现对两个双精度浮点数的加减运算。例子虽然很简单,但掌握了Java RMI调用的基本原理和方法,在实现复杂应用时,我们需要做的也只是完善远程对象的实现类而已。
RMI实例分析
1.远程对象的本地接口声明(RMIOperate.java)
该类仅仅是一个接口声明,RMI客户机可以直接使用它,RMI服务器必须通过一个远程对象来实现它,并用某个专有的URL注册它的一个实例。
具体代码如下:
package wf.rmi; //包名
import java.rmi.*; //导入类包
/*RMI本地接口必须从Remote接口派生*/
public interface RMIOperate extends Remote
{
/*接口中的具体方法声明,注意必须声明抛出RemoteException*/
public double add(double x, double y) throws RemoteException;
//输入两个浮点数,返回其和
public double minus(double x, double y) throws RemoteException;
//输入两个浮点数,返回其差
}
2.远程对象实现类(OperateImpl.java)
这个类应实现RMI客户机调用的远程服务对象的本地接口,它必须从UnicastRemoteObject继承,构造函数应抛出RemoteException异常。
具体代码如下:
package wf.rmi; //包名
//导入需要的类包
import java.rmi.*;
import wf.rmi.RMIOperate;
import java.rmi.server.UnicastRemoteObject;
public class OperateImpl extends UnicastRemoteObject implements RMIOperate
{
/*构造函数*/
public OperateImpl() throws RemoteException
{
}
/*实现本地接口中声明的add方法*/
public double add(double x, double y) throws RemoteException
{
double z = x + y;
return z;
}
/*实现本地接口中声明的minus方法*/
public double minus(double x, double y) throws RemoteException
{
double z = x - y;
return z;
}
}
3.RMI服务器类(RMIServer.java)
该类创建远程对象实现类OperateImpl的一个实例,然后通过一个专有的URL来注册它。所谓注册就是通过Java.rmi.Naming.bind()方法或Java.rmi.Naming.rebind()方法,将OperateImpl实例绑定到指定的URL上。
具体实现代码如下:
package wf.rmi; //包名
//导入需要的类包
import java.rmi.Naming;
import wf.rmi.OperateImpl;
public class RMIServer
{
public static void main(String[] args)
{
try
{
//创建远程对象的实现实例
OperateImpl operObj = new OperateImpl();
//提示信息
System.out.println("RMI Server Starting...");
//将实例注册到专有的URL
Naming.rebind("rmi:///RMIOperate", operObj);
//等待RMI客户机调用的提示信息
System.out.println("Waiting RMI Client Invoke...");
}
catch(Exception e)
{
e.printStackTrace();
}
}
}
4.RMI客户机类(RMIClient.java)
RMI客户使用java.rmi.Naming.lookup()方法,在指定的远程主机上查找RMI服务对象,若找到就把它转换成本地接口RMIOperate类型。它与CORBA不同之处在于RMI客户机必须知道提供RMI服务主机的URL,这个URL可以通过rmi://host/path或rmi://host:port/path来指定,如果省略端口号,就默认使用1099。Java.rmi.Naming.lookup()方法可能产生三个异常:Java.rmi.RemoteException、Java.rmi.NotBoundException、java.net. MalformedURLException,三个异常都需要捕获。
下面是详细代码:
package wf.rmi; //包名
//导入需要的类包
import java.rmi.*;
import java.net.*;
import java.io.*;
public class RMIClient
{
public static void main(String[] args)
{
try
{
BufferedReader readIn = new BufferedReader(new
InputStreamReader(System.in));
String host = "localhost"; //默认为本地主机
//带输入参数时,将host设置为指定主机
if (args.length > 0)
host = args[0];
//根据指定的URL定位远程实现对象
RMIOperate rmiObj = (RMIOperate)Naming.lookup("rmi://" + host +
"/RMIOperate"); //提示输入运算参数1
System.out.println("Please Input Data1: ");
String str1 = readIn.readLine(); //读取输入参数1
double x = 0;
try
{
x = Double.parseDouble(str1); //分析输入参数1,转换为double类型
}
catch(NumberFormatException nfe)
{
x = 0; //如果转换异常,则重置x为0
}
//提示输入运算参数2
System.out.println("Please Input Data2: ");
String str2 = readIn.readLine();//读取输入参数2
double y = 0;
try
{
y = Double.parseDouble(str2); //分析输入参数2,转换为double类型
}
catch(NumberFormatException nfe)
{
y = 0; //如果转换异常,则重置y为0
}
//调用远程对象的本地接口方法,实现输入参数的加、减运算,并输出结果
System.out.println("Data1 Add Data2 Result is: " + rmiObj.add(x, y));
System.out.println("Data1 minus Data2 Result is: " +
rmiObj.minus(x, y));
}
catch(Exception e)
{
e.printStackTrace();
}
}
}
RMI Server/Client的编译与运行
(1).编译所有的源代码(如图1)
图1
(2).生成客户端存根和服务器框架(如图2)
图2
这将构造OperateImpl_Stub.class和OperateImpl_Skel.class。这时可将所有的Class文件打包成jar,并将其分别置于RMI客户机和RMI服务器的ClassPath中(如图3):
图3
当然,也可以只将RMIOperate.class、RMIClient.class和OperateImpl_Stub.class复制到RMI客户机,将RMIOperate.class、OperateImpl.class 、RMIServer.class和OperateImpl_Skel.class复制到RMI服务器。
(3).启动RMI注册(如图4)
图4
(4).运行和调用
● 在服务器上执行RMIServer(如图5)
图5
● 在本地客户机上运行RMIClient(如图6)
图6
● 在远程客户机上运行RMIClient(须指明RMI服务器主机名或IP地址,如图7)
图7
至此,RMI调用完成。
CORBA调用实例
CORBA基本介绍
CORBA(通用对象请求代理体系结构)是OMG(对象管理组织)于1991年提出的基于对象技术的分布计算应用软件体系结构。CORBA标准主要分为三个部分:接口定义语言(IDL)、对象请求代理(ORB),以及ORB之间的互操作协议IIOP,核心是对象请求代理。CORBA可以抽象系统平台、网络通讯及编程语言的差异。通过在CORBA技术规范中定义多种类型的服务,如名字服务(Naming Service)、事务服务(Transaction Service)、对象生命期服务(LifeCycle Service)、并发控制服务(Concurrency Control Service)、时间服务(Time Service)等功能,为应用软件开发者提供一个全面、健壮、可扩展的分布对象计算平台,使面向对象的应用软件在分布异构环境下方便地实现可重用、易移植和互操作。
采用Java创建CORBA应用
CORBA对象服务的实现方式分为两种:对象的命名引用方式和字符串化对象引用方式。不论采用何种高级语言,创建CORBA应用程序的过程大体如下:
● 进行系统分析,确定服务对象需要提供的功能;
● 根据分析结果,编写IDL接口说明文件;
● 将接口说明文件编译为相应高级语言源代码,产生服务器框架与客户端存根;
● 基于服务器框架,编写服务对象实现程序;
● 基于客户端存根,编写客户对象调用程序;
● 分别编译客户对象和服务对象程序;
● 启动服务对象程序;
● 启动客户对象程序。
CORBA实例分析
下面通过一个实例,描述如何通过Java创建CORBA应用程序,并比较其与Java RMI应用程序的异同。
假设要通过CORBA实现与上面Java RMI所描述的相同应用,我们该如何开始呢?
首先,编写IDL接口说明文件(Test.idl):
module test
{
interface operate
{
double add(in double a, in double b);
double minus(in double a, in double b);
};
};
然后,通过Sun提供的将IDL文件编译成Java源代码的工具idlj(jdk1.3.0_01以上版本),为接口定义文件生成客户端存根和服务器框架。具体操作如下(如图8):
图8
-f参数指明生成内容,all包括客户端存根和服务器框架,这样在当前目录下生成一个test目录。其目录下将包括下列文件(如图9):
图9
通过这些文件,就可以编写服务对象实现程序和客户对象调用程序了。
为保证IDL接口说明文件中声明的方法能被调用,首先需要实现该接口。具体代码如下(注意:该类需从_operateImplBase派生):
package wf.corba; //包名
import test.*; //引入类包
public class OperateImpl extends _operateImplBase
{
/*构造函数*/
public OperateImpl()
{
super();
}
/*实现接口声明方法add*/
public double add(double x, double y)
{
double z = x + y;
return z;
}
/*实现接口声明方法minus*/
public double minus(double x, double y)
{
double z = x - y;
return z;
}
}
在此基础上,再编写服务对象程序。为了方便实现服务端的多线程调用,我们实现了Runnable接口。具体代码如下:
package wf.corba; //包名
/*引入类包*/
import org.omg.CORBA.*;
import org.omg.CosNaming.*;
import java.net.*;
/*服务端实现了Runnable接口,以便多线程扩展*/
public class CORBAServer implements Runnable
{
/*构造函数*/
public CORBAServer(String[] args)
{
initORB(args);
}
/*初始化ORB*/
private void initORB(String[] args)
{
try
{
//根据参数初始化ORB
ORB orb = ORB.init(args, null);
//新建一个服务实现对象
OperateImpl operateRef = new OperateImpl();
//连接该服务实现对象
orb.connect(operateRef);
//解析根名字引用
org.omg.CORBA.Object objRef = orb.resolve_initial_references("NameService");
//匹配CORBA对象
NamingContext ncRef = NamingContextHelper.narrow(objRef);
//新建指定接口模块名的名字组件
NameComponent nc = new NameComponent("test", "");
NameComponent path[] = {nc};
//根据名字服务绑定CORBA服务对象
ncRef.rebind(path, operateRef);
}
catch(Exception e)
{
e.printStackTrace();
}
}
/*实现Runnable接口*/
public void run()
{
try
{
//输出系统提示信息
System.out.println("CORBA Server Host: " +
InetAddress.getLocalHost().getHostAddress() + ",
Port: 10050/nCORBA Server Ready, Waiting Client Invoke Now...");
java.lang.Object object = new java.lang.Object();
//同步对象,等待客户调用
synchronized(object)
{
object.wait();
}
}
catch(Exception e)
{
e.printStackTrace();
}
}
/*主入口函数*/
public static void main(String[] args)
{
CORBAServer cs = new CORBAServer(args);
//运行CORBA服务对象
cs.run();
}
}
最后编写客户端调用程序。具体代码如下:
package wf.corba;//包名
/*引入类包*/
import test.*;
import org.omg.CORBA.*;
import org.omg.CosNaming.*;
import java.io.*;
public class CORBAClient
{
/*主入口函数*/
public static void main(String[] args)
{
try
{
BufferedReader readIn = new BufferedReader(new InputStreamReader(System.in));
ORB orb = ORB.init(args, null); //初始化ORB
//解析根名字引用
org.omg.CORBA.Object objRef = orb.resolve_initial_references("NameService");
//匹配CORBA对象
NamingContext ncRef = NamingContextHelper.narrow(objRef);
//根据指定接口创建名字组件
NameComponent nc = new NameComponent("test", "");
NameComponent path[] = {nc};
//根据名字服务定位指定接口对象
operate opRef = operateHelper.narrow(ncRef.resolve(path));
System.out.println("Please Input Data1: ");
String data1 = readIn.readLine(); //读入参数1
double x = 0;
try
{
x = Double.parseDouble(data1); //将输入参数1转换为double类型
}
catch(Exception e)
{
x = 0; //转换异常,则置0
}
System.out.println("Please Input Data2: ");
String data2 = readIn.readLine();//读入参数2
double y = 0;
try
{
y = Double.parseDouble(data2); //将输入参数2转换为double类型
}
catch(Exception e)
{
y = 0; //转换异常,则置0
}
//调用服务接口对象,实现输入参数的加、减运算,并输出运算结果
System.out.println("Data1 Add Data2 Result is: " + opRef.add(x, y));
System.out.println("Data1 Minus Data2 Result is: " + opRef.minus(x, y));
}
catch(Exception e)
{
e.printStackTrace();
}
}
}
到这里,我们已创建了该CORBA应用所需要的所有服务器和客户机对象,接着运行它,看是否可以实现预想的功能。
CORBA Server/Client的编译与运行
1.编译所有的Java源文件(如图10):
图10
2.启动Sun提供名字服务器(可通过ORBInitiaPort设定其端口,默认为900,本例中为10050)(如图11):
图11
3.启动本例中实现的CORBA服务对象test(如图12):
图12
4.CORBA客户机在本地调用CORBA服务(如图13):
图13
5.CORBA客户机在运程调用CORBA服务(如图14):
图14
由上可见,在远程调用时,CORBA客户机需要知道服务对象的主机名(或IP,通过ORBInitialHost参数指定)和端口(可通过ORBInitialPort参数指定);而在本地调用时,只需要指明服务端口即可。
通过Applet调用CORBA服务
在很多情况下,我们不但希望通过客户端程序调用CORBA服务,而且还希望将其嵌入Applet中。由于Java Applet采取“Sand-Box”安全模型,使得Java Applet既不能与宿主机以外的计算机建立网络连接,也不能访问本地文件系统和设备。CORBA系统的特点是服务对象的分布性和远程对象定位的透明性。本地系统可通过CORBA规范提供的IIOP代理(Gatekeeper)来解决安全限制的约束,它负责接收Applet发来的IIOP报文,分析报文内容,将其转发给位于其它主机上的无法直接被Applet调用的CORBA服务对象实现,并将服务对象返回的IIOP报文提交给Applet,从而完成CORBA服务调用。
下面给出通过Applet访问本例中CORBA服务对象的主要实现代码。
public class ClientApplet extends JApplet implements ActionListener
{
......
private void initORB()
{
try
{
//定义参数数组,指明CORBA服务对象主机和端口
String args[] = {"-ORBInitialHost", "10.16.93.39", "-
ORBInitialPort", "10050"};
ORB orb = ORB.init(args, null); //根据指定参数进行ORB初始化
//解析根名字引用
org.omg.CORBA.Object objRef = orb.resolve_initial_references("NameService");
NamingContext ncRef = NamingContextHelper.narrow(objRef);
NameComponent nc = new NameComponent("test", "");
NameComponent path[] = {nc};
opRef = operateHelper.narrow(ncRef.resolve(path));
}
catch(Exception e)
{
e.printStackTrace();
}
}
......
}
通过浏览器可以直接运行,也可以通过Appletviewer查看运行结果(如图15)。
图15
正确选择RMI和CORBA
与CORBA相比,RMI功能较弱且只能用于Java系统。RMI可以直接把分布式对象模型嵌入到Java语言内部,使得Java程序员可以方便、快捷地编写分布式程序,而不必离开Java环境,或者涉及CORBA的IDL,以及Java到CORBA的数据类型映射与转换。但是,RMI不遵守CORBA标准,基本上是Java-to-Java技术,它需要客户端程序和服务器程序都用Java编写,所以难以实现与其它语言编写的对象之间的互操作。CORBA则灵活、强大许多,其客户机与服务器可以根据需要采用不同的高级语言来开发和编写,并且抽象了分布系统中远程方法调用的内在复杂性,提供了分布异构环境中的应用对象集成和互操作的软件总线。因此,在实际分布应用开发中,我们应该根据具体的情况来决定采用RMI还是CORBA。总之,RMI只能是Java对象之间的互操作,功能较为简单,实现也比较容易;CORBA则更适合于大型异构系统之间的复杂应用整合,开发的代价相对要高一些。