摘要:RMI是开发Java网络分布式应用系统的一个重要框架,开发人员通过运用RMI框架将更易于分布式系统的开发。该文详细介绍了RMI的运行机制,并对运用RMI框架进行分布式系统的开发步骤进行了阐述,最后给出了RMI技术的具体应用实例和实现方法。
关键词:RMI;Java;Stub;分布式
中图分类号:TP393 文献标识码:A 文章编号:1009-3044(2014)01-0051-03
1 概述
由于单台计算机的计算能力有限,在实际的应用中,我们常常要将计算任务分成多个子任务,将每个子任务放到网络中不同的计算机上进行计算,实现分布式并行计算,以加快计算速度。在分布式计算模型中不管采取何种方式,主要是将分部在不同计算机上的对象间发送的消息转换为字节序列,然后通过套接字建立连接并传输这些字节序列。在网络连接和传输消息时还要考虑出现的各种故障和安全问题以及对象垃圾收集机制等等问题。
在具体的分布式系统实现过程中,一种是采用基于消息方式实现各个节点间的通信。当系统要通信时就向外发送消息,消息可以是字节流、字节数组,其他系统接收到消息后则进行相应的业务处理。这种系统间通信的方式,通常基于网络协议来实现,常用的实现系统间通信的协议有:TCP/IP和UDP/IP。另一种是采用基于远程调用方式实现系统间的通信。这种方式当系统间要通信时,可通过调用本地的一个Java接口的方法,透明地调用远程的Java实现。具体的细节则由Java或框架来完成,尽可能地使系统间的通信和系统内一样,让使用者感觉调用远程方法同调用本地方法一样。
因为开发一个完善的分布式软件系统相当复杂,如果采用基于消息的方式实现分布式通信,相当麻烦。开发人员不仅仅要关注对数据的业务处理,还要关注很多纯技术细节。而基于远程调用方式实现分布式通信的方法解放了开发人员的一些纯技术细节问题,使开发人员能够更专注于业务数据的处理。Java为我们开发分布式网络应用提供了比较完善的远程方法框架,那就是Java RMI(Remote Method Invocation,远程方法调用)。通过RMI,可以很方便地让Java程序调用网络中其他计算机上的Java方法。
2 RMI系统运行机制
RMI是Java用于实现透明远程调用的重要机制。在远程调用中,客户端仅有服务器端提供的接口。客户端通过此接口实现对远程服务器端的方法调用。
RMI服务器端通过启动RMIRegistry (RMIRegistry是运行在服务器上的一个后台进程,且必须在服务进程启动之前启动)在一个端口上监听对外提供的接口,其实现实例以字符串的方式绑定到RMI注册对象上。RMI客户端程序采用命名服务机制通过注册表获取远程对象的存根stub。当要调用远程方法时,通过此stub将被访问的远程对象的名字、被调用的方法描述和相关的参数封装成一个对象,序列化成流后传输到RMI服务器端。RMI服务器端skeleton接收到客户端的请求对象后,解析其中的对象字符串、方法和参数,通过对象字符串和访问的方法名称来反射获取到方法实例对象,传入参数完成对服务器端对象实例的调用。然后获得方法调用产生的返回值或者异常,并对其进行序列化然后返回给客户端。客户端的stub接收到服务器端skeleton发送过来的返回值或者异常的序列化字节流后,对其进行反序列化,就得到调用远程方法的返回结果。RMI的具体运行机制如图1。
图1 RMI运行机制
3 RMI技术应用实例开发步骤
3.1 创建远程接口
RMI中要求远程对象所属的类实现一个远程接口,远程对象必须通过远程接口声明服务。在远程接口中声明可以被客户程序访问的远程方法。此接口必须要直接或间接继承java.rmi.Remote接口。由于远程方法调用依赖于网络通信,而网络通信是不可靠的,一旦服务器端或客户端有一方断开连接,或者网络出现故障,此次通信就会失败。所以在接口中的所有方法需要声明抛出java.rmi.RemoteException异常。当远程方法调用出现网络通信异常时,RMI框架抛出RemoteException异常,客户端捕获这种异常,并进行相應的处理。
实例代码如下:
import java.rmi.*;
public interface Account extends Remote{
public int add(int i,int j) throws RemoteException;
}
3.2 实现远程接口
远程接口中定义的远程方法的具体实现都在远程接口的实现类中,远程接口的实现类中也可以定义一些本地方法,这些本地方法不需要在远程接口中声明,也无需抛出RemoteException异常,本地方法只能被本地调用,不能被远程调用。远程接口的实现类需要继承java.rmi.server.UnicastRemoteObject类。因为RMI框架中关于远程对象的生命周期、基于TCP的连接传输、客户端和服务器端的远程对象、方法、参数序列化后的流协议交流功能在UnicastRemoteObject类中实现。所以远程接口的实现类必须直接或间接继承UnicastRemoteObject类。
实例代码如下:
import java.rmi.*;
import java.rmi.server.UnicastRemoteObject;
public class AccountImpl extends UnicastRemoteObject implements Account{
public AccountImpl() throws RemoteException {
super();
}
public int add (int i,int j) throws RemoteException{ //远程方法
System.out.println("调用add()方法");
int result = i + j;
System.out.println("Server:3 + 5 = " + result);
return result;
}}
3.3 创建服务器端
服务器端首先要创建远程对象实例,然后向rmiregistry注册表注册此远程对象实例,把远程对象实例与一个名字绑定。rmiregistry注册表的相关功能由JDK的安装目录里的一个提供命名服务的注册表程序rmiregistry.exe完成。
实例代码如下:
import java.net.*;
import java.rmi.*;
import java.rmi.registry.LocateRegistry;
public class RMIServer {
private static final int PORT = 10002;
private static final String HOST_NAME = "localhost";
public RMIServer() throws RemoteException, MalformedURLException,NotBoundException {
//启动RMI注册表线程,并将RMI服务器绑定在PORT端口上
LocateRegistry.createRegistry(PORT);
System.out.println("Registry created on host computer " + HOST_NAME +
" on port " + Integer.toString(PORT));
Account ac = new AccountImpl(); //实例化远程对象
System.out.println("Remote AccountService implementation object created");
String urlString = "//" + HOST_NAME + ":" + PORT + "/" + "AccountService";
Naming.rebind(urlString, ac); //将远程对象的名字绑定到远程对象的引用上
System.out.println("Bindings Finished, waiting for client requests.");
}
public static void main(String[] args) {
System.setSecurityManager(new RMISecurityManager());
try {
RMIServer rmi = new RMIServer();
}catch(Exception e){
e.printStackTrace();
}} }
3.4 创建客户端
客户端程序首先需要获得远程对象的存根对象,然后通过此存根对象来调用方法。这些存根由rmic生成。
实例代码如下:
import java.rmi.*;
import java.rmi.server.*;
import java.rmi.registry.LocateRegistry;
public class RMIClient {
private static final int PORT = 10002;
private static final String HOST_NAME = "localhost";
public RMIClient() {
try {
Account ac = (Account) Naming.lookup("rmi://" + HOST_NAME + ":" + PORT + "/AccountService"); //通过远程对象名字查找远程对象,并返回远程接口的引用
System.out.println("AccountService lookup successful");
int result = ac.add(3,5); //调用远程对象的方法
System.out.println("Client:3 + 5 = " + result);
} catch (Exception e){
e.printStackTrace();
} }
public static void main(String[] args) {
RMIClient rmi = new RMIClient();
} }
4 编译与运行RMI系统步骤
对程序的具体执行步骤需要先编译然后执行注册程序、服务器端程序和客户端程序,在具体的执行过程中需要将JDK的安全策略文件java.policy中的java.net.SocketPermission赋予listen,connect,accept权限。具体编译与运行RMI系統步骤如下:
1) 使用javac编译远程接口类、远程接口实现类、服务端和客户端类文件。
运行cmd命令打开控制台,在控制台窗口执行
c:\rmi>javac *.java
2) 使用rmic编译器生成实现类的Stub和Skeleton,注意在新版的RMI机制下,只生成Stub文件,Skeleton文件不会生成。
c:\rmi>rmic AccountImpl
3) 运行rmiregistry命令,启动RMI注册监听进程。
c:\rmi>start rmiregistry (下转第66页)
(上接第53页)
4) 运行服务器端类,注册RMI对象,服务器端准备就绪,等待远程客户端的调用。
c:\rmi>start java RMIServer
5) 启动客户端
c:\rmi>java RMIClient
5 结束语
通过以上分析可知Java RMI的结构非常清楚,程序员可以利用此框架非常方便、快速的进行分布式程序的编写,而不需要对底层的一些通信细节进行考虑,大大提高了程序员编写代码的效率。而且由于Java自身的纯面向对象特性,其实现非常方便且透明,为使用分布式对象技术提供了一个可靠的平台。
参考文献:
[1] Robert Orfali,Dan Harkey.Java与CORBA客户/服务器编程[M].2版.北京:电子工业出版社,2004.
[2] 林昊.分布式Java应用基础与实践[M].北京:电子工业出版社,2010.
[3] 孙卫琴.Java网络编程精解[M].北京:电子工业出版社,2009.
[4] 孟宪福.分布式对象技术及其应用[M].北京:清华大学出版社,2008.
[5] 刘丹,程晓,侯德林.一种基于RMI的分布式架构设计[J].计算机应用与软件,2007(9):206-208.