RMI学习以及深入理解

转自:http://blog.163.com/eye_ziye/blog/static/21447105120131127105623452/

Java RMI (Remote Method Invocation 远程方法调用)是用Java在JDK1.1中实现的,它大大增强了Java开发分布式应用的能力。Java作为一种风靡一时的网络开发语言,其巨大的威力就体现在它强大的开发分布式网络应用的能力上,而RMI就是开发百分之百纯Java的网络分布式应用系统的核心解决方案之一。其实它可以被看作是RPC的Java版本。但是传统RPC并不能很好地应用于分布式对象系统。而Java RMI 则支持存储于不同地址空间的程序级对象之间彼此进行通信,实现远程对象之间的无缝远程调用。RMI目前使用Java远程消息交换协议JRMP(Java Remote Messaging Protocol)进行通信。JRMP是专为Java的远程对象制定的协议。因此,Java RMI具有Java的"Write Once,Run Anywhere"的优点,是分布式应用系统的百分之百纯Java解决方案。用Java RMI开发的应用系统可以部署在任何支持JRE(Java Run Environment Java,运行环境)的平台上。但由于JRMP是专为Java对象制定的,因此,RMI对于用非Java语言开发的应用系统的支持不足。不能与用非Java语言书写的对象进行通信。本文拟从程序的角度举例介绍怎样利用RMI实现Java分布式应用。 

 
一、RMI系统运行机理 

RMI应用程序通常包括两个独立的程序:服务器程序和客户机程序。典型的服务器应用程序将创建多个远程对象,使这些远程对象能够被引用,然后等待客户机调用这些远程对象的方法。而典型的客户机程序则从服务器中得到一个或多个远程对象的引用,然后调用远程对象的方法。RMI为服务器和客户机进行通信和信息传递提供了一种机制。 

在与远程对象的通信过程中,RMI使用标准机制:stub和skeleton。远程对象的stub担当远程对象的客户本地代表或代理人角色。调用程序将调用本地stub的方法,而本地stub将负责执行对远程对象的方法调用。在RMI中,远程对象的stub与该远程对象所实现的远程接口集相同。调用stub的方法时将执行下列操作:(1) 初始化与包含远程对象的远程虚拟机的连接;(2) 对远程虚拟机的参数进行编组(写入并传输);(3) 等待方法调用结果;(4) 解编(读取)返回值或返回的异常;(5) 将值返回给调用程序。为了向调用程序展示比较简单的调用机制,stub将参数的序列化和网络级通信等细节隐藏了起来。在远程虚拟机中,每个远程对象都可以有相应的skeleton(在JDK1.2环境中无需使用skeleton)。Skeleton负责将调用分配给实际的远程对象实现。它在接收方法调用时执行下列操作:(1) 解编(读取)远程方法的参数;(2) 调用实际远程对象实现上的方法;(3) 将结果(返回值或异常)编组(写入并传输)给调用程序。stub和skeleton由rmic编译器生成。 

利用RMI编写分布式对象应用程序需要完成以下工作:(1) 定位远程对象。应用程序可使用两种机制中的一种得到对远程对象的引用。它既可用RMI的简单命名工具rmiregistry来注册它的远程对象,也可以将远程对象引用作为常规操作的一部分来进行传递和返回。(2)与远程对象通信。远程对象间通信的细节由RMI处理,对于程序员来说,远程通信看起来就像标准的Java方法调用。(3)给作为参数或返回值传递的对象加载类字节码。因为RMI允许调用程序将纯Java对象传给远程对象,所以,RMI将提供必要的机制,既可以加载对象的代码又可以传输对象的数据。在RMI分布式应用程序运行时,服务器调用注册服务程序以使名字与远程对象相关联。客户机在服务器上的注册服务程序中用远程对象的名字查找该远程对象,然后调用它的方法。 

二、对象序列化 

在RMI分布式应用系统中,服务器与客户机之间传递的Java对象必须是可序列化的对象。不可序列化的对象不能在对象流中进行传递。对象序列化扩展了核心Java输入/输出类,同时也支持对象。对象序列化支持把对象编码以及将通过它们可访问到的对象编码变成字节流;同时,它也支持流中对象图形的互补重构造。序列化用于轻型持久性和借助于套接字或远程方法调用(RMI)进行的通信。序列化中现在包括一个 API(Application Programming Interface,应用程序接口),允许独立于类的域指定对象的序列化数据,并允许使用现有协议将序列化数据域写入流中或从流中读取,以确保与缺省读写机制的兼容性。 

为编写应用程序,除多数瞬态应用程序外,都必须具备存储和检索 Java对象的能力。以序列化方式存储和检索对象的关键在于提供重新构造该对象所需的足够对象状态。存储到流的对象可能会支持 Serializable(可序列化)或 Externalizable(可外部化)接口。对于Java对象,序列化形式必须能标识和校验存储其内容的对象所属的 Java类,并且将该内容还原为新的实例。对于可序列化对象,流将提供足够的信息将流的域还原为类的兼容版本。对于可外部化对象,类将全权负责其内容的外部格式。序列化 Java 对象的目的是:提供一种简单但可扩充的机制,以序列化方式维护 Java对象的类型及安全属性;具有支持编组和解编的扩展能力以满足远程对象的需要;具有可扩展性以支持 Java 对象的简单持久性;只有在自定义时,才需对每个类提供序列化自实现;允许对象定义其外部格式。 

三、分布式应用的实现和运行步骤 

编写Java RMI分布式应用程序的步骤主要包括以下几步: 

(1) 将远程类的功能定义为Java接口。在Java中,远程对象是实现远程接口的类的实例。在远程接口中声明每个要远程调用的方法。远程接口具有如下特点:1) 远程接口必须声明为public。如果不这样,则除非客户端与远程接口在同一个包内,否则当试图装入实现该远程接口的远程对象时会得到错误结果。2) 远程对象扩展java.rmi.Remote接口。3) 除了所有应用程序特定的例外之外,每个方法还必须抛出java.rmi.RemoteException例外。4) 任何作为参数或返回值传送的远程对象的数据类型必须声明为远程接口类型,而不是实现类。 

(2) 编写和实现服务器类。该类是实现(1)中定义的远程接口。所以在该类中至少要声明实现一个远程接口,并且必须具有构造方法。在该类中还要实现远程接口中所声明的各个远程方法。 

(3) 编写使用远程服务的客户机程序。在该类中使用java.rmi.Naming中的lookup()方法获得对远程对象的引用,依据需要调用该引用的远程方法,其调用方式和对本地对象方法的调用相同。 

实现了服务器和客户机的程序后,就是编译和运行该RMI系统。其步骤有: 
(1) 使用javac编译远程接口类,远程接口实现类和客户机程序。 
(2) 使用rmic编译器生成实现类的stub和skeleton。 
(3) 启动RMI注册服务程序rmiregistry。 
(4) 启动服务器端程序。 
(5) 启动客户机程序。

 

Java与.NET都提供了远程处理功能,但不完全相同.Java远程处理是通过一个“共享接口”实现的,而.NET可以通过一个“共享命令集”实现。下面就这两种方式来具体说明。
   
   Java 远程处理 
   Java远程方法调用(RMI)提供了Java程序语言的远程通讯功能,这种特性使客户机上运行的程序可以调用远程服务器上的对象,使Java编程人员能够在网络环境中分布操作。 
   创建一个简单的Java分布式远程方法调用程序可以按以下几个步骤操作, 
   
   一、定义远程接口: 
   在 Java 中,远程对象是实现远程接口的类的实例, 远程接口声明每个要远程调用的方法。在需要创建一个远程对象的时候,我们通过传递一个接口来隐藏基层的实施细节,客户通过接口句柄发送消息即可。 
   远程接口具有如下特点: 
   1) 远程接口必须为public属性。如果不这样,除非客户端与远程接口在同一个包内,否则 当试图装入实现该远程接口的远程对象时,调用会得到错误结果。 
   2) 远程接口必须扩展接口java.rmi.Remote。 
   3) 除与应用程序本身特定的例外之外,远程接口中的每个方法都必须在自己的throws从句中 声明java.rmi.RemoteException。(或 RemoteException 的父类)。 
   4) 作为参数或返回值传递的一个远程对象(不管是直接,还是本地对象中嵌入)必须声明为远 程接口,而不应声明为实施类。 
   下面是远程接口的接口RmiSample的定义

java 代码

  1. package com.robin.demo.rmi.interf;   

  2.   

  3. import java.rmi.Remote;   

  4. import java.rmi.RemoteException;   

  5.   

  6. public interface RmiSample extends Remote {   

  7.     public int sum(int a, int b) throws RemoteException;   

  8. }   

 二、实现远程接口: 
   远程对象实现类必须扩展远程对象java.rmi.UnicastRemoteObject类,并实现所定义的远程接口。远程对象的实现类中包含实现每个远程接口所指定的远程方法的代码。这个类也可以含有附加的方法,但客户只能使用远程接口中的方法。因为客户是指向接口的一个句柄,而不是它的哪个类。必须为远程对象定义构造函数,即使只准备定义一个默认构造函数,用它调用基础类构造函数。因为基础类构造函数可能会抛出 java.rmi.RemoteException,所以即使别无它用必须抛出java.rmi.RemoteException例外。 
   以下是远程对象实现类的声明:

java 代码

  1. package com.robin.demo.rmi.impl;   

  2.   

  3. import java.rmi.RemoteException;   

  4. import java.rmi.server.UnicastRemoteObject;   

  5.   

  6. import com.robin.demo.rmi.interf.RmiSample;   

  7.   

  8.   

  9. public class RmiSampleImpl extends UnicastRemoteObject implements RmiSample {   

  10.     /**  

  11.      *   

  12.      */  

  13.     private static final long serialVersionUID = 2742977636753958461L;   

  14.   

  15.     public RmiSampleImpl() throws RemoteException {   

  16.         super();   

  17.     }   

  18.   

  19.     public int sum(int a, int b) throws RemoteException {   

  20.         return a + b;   

  21.     }   

  22.   

  23. }   


   
   三、编写服务器类: 
   包含 main 方法的类可以是实现类自身,也可以完全是另一个类。下面通过RmiSampleServer 来创建一个远程对象的实例,并通过java.rmi.registry.LocateRegistry类的createRegistry 方法从指定端口号启动注册服务程序,也可以通过执行 rmiregistry 命令启动注册服务程序,注册服务程序的缺省运行端口为 1099。必须将远程对象名字绑定到对远程对象的引用上: Naming.rebind("//localhost:8808/SAMPLE-SERVER" , Server); 
   以下是服务器类的声明:

java 代码

  1. package com.robin.demo.rmi.server;   

  2.   

  3. import java.net.MalformedURLException;   

  4. import java.rmi.Naming;   

  5. import java.rmi.RemoteException;   

  6. import java.rmi.registry.LocateRegistry;   

  7.   

  8. import com.robin.demo.rmi.impl.RmiSampleImpl;   

  9.   

  10.   

  11. public class RmiSampleServer {   

  12.   

  13.     /**  

  14.      * @param args  

  15.      */  

  16.     public static void main(String[] args) {   

  17.         try{   

  18.             LocateRegistry.createRegistry(8808);   

  19.             RmiSampleImpl server= new RmiSampleImpl();   

  20.             Naming.rebind("//localhost:8808/SAMPLE-SERVER" , server);   

  21.         }catch (MalformedURLException me){   

  22.             System.out.println("Malformed URL: " + me.toString());   

  23.         }catch(RemoteException re){   

  24.             System.out.println("Remote Exception: "+re.toString());   

  25.         }   

  26.     }   

  27.   

  28. }   


   
   四、编写使用远程服务的客户机类:
   客户机类的主要功能有两个,一是通过Naming.lookup方法来构造注册服务程序 stub 程序实例,二是调用服务器远程对象上的远程方法。 
   以下是服务器类的声明:

java 代码

  1. package com.robin.demo.rmi.client;   

  2.   

  3. import java.rmi.Naming;   

  4. import java.rmi.RemoteException;   

  5.   

  6. import com.robin.demo.rmi.interf.RmiSample;   

  7.   

  8.   

  9. public class RmiSampleClient {   

  10.   

  11.     /**  

  12.      * @param args  

  13.      */  

  14.     public static void main(String[] args) {   

  15.         try {   

  16.             String url = "//localhost:8808/SAMPLE-SERVER";   

  17.             RmiSample RmiObject = (RmiSample) Naming.lookup(url);   

  18.             System.out.println(" 1 + 2 = " + RmiObject.sum(1, 2));   

  19.         } catch (RemoteException rex) {   

  20.             System.out.println("Error in lookup: " + rex.toString());   

  21.         } catch (java.net.MalformedURLException me) {   

  22.             System.out.println("Malformed URL: " + me.toString());   

  23.         } catch (java.rmi.NotBoundException ne) {   

  24.             System.out.println("NotBound: " + ne.toString());   

  25.         }   

  26.   

  27.     }   

  28.   

  29. }   


   五、编译代码:
   要编译 Java 源文件,请运行 javac 命令:
   javac RmiSample.java RmiSampleImpl.java RmiSampleServer.java RmiSampleClient.java 
   
   六、为远程对象实现创建根和干: 
   要创建存根程序和骨架文件,应以包含远程对象实现的已编译类包全名运行 rmic 编译器。 
   存根(Stub)是远程对象在客户端的代理,它将RMI调用传递给服务器端的骨架(Skeleton),后者负责将该调用传递给实际的远程方法输入如下: 
   D:/RMI>rmic -d D:/RMI RmiSampleImpl 执行这个命令, 若rmic成功运行,RMI目录下就会多出两个新类: RmiSampleImpl_Stub.class RmiSampleImpl_Skel.class 它们分别对应的是存根(stub)和骨架(skeleton). 
   
   七、运行代码:
   运行服务端程序:在Windows下,输入下列命令,在后台启动RmiSampleServer程序:
   D:/RMI>java RmiSampleServer 
   运行客户端程序: 
   D:/RMI>java RmiSampleClient 
   客户端输出: 1 + 2 = 3 

 

修改上面内容:(因为上面服务端程序和客户端程序在同一台电脑上)

服务器端程序不变

客户端修改成  String url = "//服务端IP:8808/SAMPLE-SERVER";   

另外特别说明:经测试验证了:客户端的接口(包括包名)要和服务端接口和包名相同才能通过

 

 

猜你喜欢

转载自blog.csdn.net/qq646040754/article/details/81604822