转自:http://blog.csdn.net/guyuealian/article/details/51992182
一、Java RMI机制:
远程方法调用RMI(Remote Method Invocation),是允许运行在一个Java虚拟机的对象调用运行在另一个Java虚拟机上的对象的方法。 这两个虚拟机可以是运行在相同计算机上的不同进程中,也可以是运行在网络上的不同计算机中。
Java RMI:Java远程方法调用,即Java RMI(Java Remote Method Invocation)是Java编程语言里,一种用于实现远程过程调用的应用程序编程接口。它使客户机上运行的程序可以调用远程服务器上的对象。远程方法调用特性使Java编程人员能够在网络环境中分布操作。RMI全部的宗旨就是尽可能简化远程接口对象的使用。
而RPC是远程过程调用(Remote Procedure Call)可以用于一个进程调用另一个进程(很可能在另一个远程主机上)中的过程,从而提供了过程的分布能力。Java 的 RMI 则在 RPC 的基础上向前又迈进了一步,即提供分布式对象间的通讯。
而RPC是远程过程调用(Remote Procedure Call)可以用于一个进程调用另一个进程(很可能在另一个远程主机上)中的过程,从而提供了过程的分布能力。Java 的 RMI 则在 RPC 的基础上向前又迈进了一步,即提供分布式对象间的通讯。
(1)RMI框架
【参考资料】
《Java网络编程精解》孙卫琴,这本书适合入门学习RMI框架基础
RMI框架封装了所有底层通信细节,并且解决了编组、分布式垃圾收集、安全检查和并发性等通用问题。有了现成的框架,开发人员就只需专注于开发与特定问题领域相关的各种本地对象和远程对象。
要了解RMI原理,先了解一下Stub和Skeleton两个概念。
(2)Stub和Skeleton
RMI框架采用代理来负责客户与远程对象之间通过Socket进行通信的细节。RMI框架为远程对象分别生成了客户端代理和服务器端代理。位于客户端的代理类称为存根(Stub),位于服务器端的代理类称为骨架(Skeleton)。
【相关资料】
《RMI(Remote Method Invocation)初窥门径》 http://blog.csdn.net/smcwwh/article/details/7080997
stub(存根)和skeleton(骨架)在RMI中充当代理角色,在现实开发中主要是用来隐藏系统和网络的的差异, 这一部分的功能在RMI开发中对程序员是透明的。Stub为客户端编码远程命令并把他们发送到服务器。而Skeleton则是把远程命令解码,调用服务端的远程对象的方法,把结果在编码发给stub,然后stub再解码返回调用结果给客户端。
RMI远程过程调用的实现过程如下图所示:
RMI 框架的基本原理大概如下图,应用了代理模式来封装了本地存根与真实的远程对象进行通信的细节
【参考资料】
《Java RMI 框架(远程方法调用)》http://haolloyin.blog.51cto.com/1177454/332426
《 java RMI原理详解》 http://blog.csdn.net/xinghun_4/article/details/45787549
二、Java RMI 简单示例
别急,慢慢分析~具体代码在下面,附例子代码下载:http://download.csdn.net/detail/guyuealian/9583633
大致说来,创建一个RMI应用包括以下步骤:
(1)创建远程接口:继承java.rmi.Remote接口。
(2)创建远程类:实现远程接口。
(3)创建服务器程序:创建远程对象,通过createRegistry()方法注册远程对象。并通过bind或者rebind方法,把远程对象绑定到指定名称空间(URL)中。
(4)创建客户程序:通过 lookup()方法查找远程对象,进行远程方法调用
(2)创建远程类:实现远程接口。
(3)创建服务器程序:创建远程对象,通过createRegistry()方法注册远程对象。并通过bind或者rebind方法,把远程对象绑定到指定名称空间(URL)中。
(4)创建客户程序:通过 lookup()方法查找远程对象,进行远程方法调用
下面具体分析每个步骤:
(1)创建远程接口:继承java.rmi.Remote接口。
(1)创建远程接口:继承java.rmi.Remote接口。
远程接口中声明了可以被客户程序访问的远程方法。RMI规范要求远程对象所属的类实现一个远程接口,并且远程接口符合以下条件:
(a)直接或间接继承java.rmi.Remote接口。
(a)直接或间接继承java.rmi.Remote接口。
(b)接口中的所有方法声明抛出java.rmi.RemoteException。
(2)创建远程类:实现远程接口。
(2)创建远程类:实现远程接口。
远程类就是远程对象所属的类。RMI规范要求远程类必须实现一个远程接口。此外,为了使远程类的实例变成能为远程客户提供服务的远程对象,可通过以下两种途径之一把它导出(export)为远程对象:
(a)导出为远程对象的第一种方式:使远程类实现远程接口时,同时继承java.rmi.server.UnicastRemoteObject类,并且远程类的构造方法必须声明抛出RemoteException。这是最常用的方式,下面的本例子就采取这种方式。
(a)导出为远程对象的第一种方式:使远程类实现远程接口时,同时继承java.rmi.server.UnicastRemoteObject类,并且远程类的构造方法必须声明抛出RemoteException。这是最常用的方式,下面的本例子就采取这种方式。
(b)导出为远程对象的第二种方式:如果一个远程类已经继承了其他类,无法再继承UnicastRemoteObject类,那么可以在构造方法中调用UnicastRemoteObject类的静态exportObject()方法,同样,远程类的构造方法也必须声明抛出RemoteException。
exportObject()是UnicastRemoteObject的静态方法,源码是:
1 protected UnicastRemoteObject(int port) throws RemoteException 2 { 3 this.port = port; 4 exportObject((Remote) this, port); 5 }
1 public static Remote exportObject(Remote obj, int port) 2 throws RemoteException 3 { 4 return exportObject(obj, new UnicastServerRef(port)); 5 }
(3)创建服务器程序:创建远程对象,在rmiregistry注册表中注册远程对象,并绑定到指定的URL中。
RMI采用一种命名服务机制来使得客户程序可以找到服务器上的一个远程对象。在JDK的安装目录的bin子目录下有一个rmiregistry.exe程序,它是提供命名服务的注册表程序。
服务器程序的一大任务就是向rmiregistry注册表注册远程对象。从JDK1.3以上版本开始,RMI的命名服务API被整合到JNDI(Java Naming and Directory Interface,Java名字与目录接口)中。在JNDI中,javax.naming.Context接口声明了注册、查找,以及注销对象的方法:
【1】 bind(String name,Object obj):注册对象,把对象与一个名字name绑定,这里的name其实就是URL格式。如果该名字已经与其它对象绑定,就会抛出NameAlreadyBoundException。
【2】rebind(String name,Object obj):注册对象,把对象与一个名字绑定。如果该名字已经与其它对象绑定,不会抛出NameAlreadyBoundException,而是把当前参数obj指定的对象覆盖原先的对象。
【3】 lookup(String name):查找对象,返回与参数name指定的名字所绑定的对象。
【4】unbind(String name):注销对象,取消对象与名字的绑定。
【1】 bind(String name,Object obj):注册对象,把对象与一个名字name绑定,这里的name其实就是URL格式。如果该名字已经与其它对象绑定,就会抛出NameAlreadyBoundException。
【2】rebind(String name,Object obj):注册对象,把对象与一个名字绑定。如果该名字已经与其它对象绑定,不会抛出NameAlreadyBoundException,而是把当前参数obj指定的对象覆盖原先的对象。
【3】 lookup(String name):查找对象,返回与参数name指定的名字所绑定的对象。
【4】unbind(String name):注销对象,取消对象与名字的绑定。
注册一个远程对象remoteObj2 关键代码如下:
但在JDK1.3版本或更低的版本,需要使用java.rmi.Naming来注册远程对象,注册代码代替如下:
关键代码如下:
例子具体代码:
1. 创建远程接口:继承java.rmi.Remote接口。
在Java中,只要一个类extends了java.rmi.Remote接口,即可成为存在于服务器端的远程对象, 供客户端访问并提供一定的服务。JavaDoc描述:Remote 接口用于标识其方法可以从非本地虚拟机上调用的接口。任何远程对象都必须直接或间接实现此接口。只有在“远程接口” (扩展 java.rmi.Remote 的接口)中指定的这些方法才可被远程调用。注意:接口中需要被远程调用的方法,必须抛出RemoteException异常。
2. 创建远程类:实现远程接口 远程对象必须实现java.rmi.server.UniCastRemoteObject类,该类的构造函数中将生成stub和skeleton, 这样才能保证客户端访问获得远程对象时,该远程对象将会把自身的一个拷贝以Socket的形式传输给客户端,此时客户端所获得的这个拷贝称为Stub(存根), 而服务器端本身已存在的远程对象则称之为Skeleton(骨架)。其实此时的存根是客户端的一个代理,用于与服务器端的通信, 而骨架也可认为是服务器端的一个代理,用于接收客户端的请求之后调用远程方法来响应客户端的请求。
3.创建服务器程序:在rmiregistry注册表中注册远程对象,向客户端提供远程对象服务
RMIServer类主要实现注册远程对象,并向客户端提供远程对象服务。远程对象是在远程服务上创建的,你无法确切地知道远程服务器上的对象的名称 。但是,将远程对象注册到RMI Service之后,客户端就可以通过RMI Service请求到该远程服务对象的stub了,利用stub代理就可以访问远程服务对象了 。
4. 客户端代码 Server端的代码已经全部写完,这时把服务器的接口RemoteInterface打包成jar,以便在Client端的项目中使用。
项目-->右键-->导出-->jar->选择RemoteInterface.java-->finish;关于客户端如何导入jar包,请看这里:http://jingyan.baidu.com/article/ca41422fc76c4a1eae99ed9f.html
1 package rmi2; 2 import javax.naming.Context; 3 import javax.naming.InitialContext; 4 5 import rmi.RemoteInterface; 6 public class ClientTest { 7 public static void main(String args[]) { 8 try { 9 Context namingContext = new InitialContext();// 初始化命名内容 10 RemoteInterface RmObj2 = (RemoteInterface) namingContext 11 .lookup("rmi://localhost:8892/RemoteObj2");//获得远程对象的存根对象 12 System.out.println(RmObj2.doSomething());//通过远程对象,调用doSomething方法 13 System.out.println("远程服务器计算结果为:" + RmObj2.Calculate(90, 2)); 14 } catch (Exception e) { 15 } 16 } 17 }
输出结果: