【尊重
原创,转载请注明出处
】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 的基础上向前又迈进了一步,即提供分布式对象间的通讯。
(1)RMI框架
【参考资料】
《Java网络编程精解》孙卫琴,这本书适合入门学习RMI框架基础
http://wenku.baidu.com/view/90171bd03186bceb19e8bbdc.html?from=search
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()方法查找远程对象,进行远程方法调用
下面具体分析每个步骤:
(1)创建远程接口:继承java.rmi.Remote接口。
远程接口中声明了可以被客户程序访问的远程方法。RMI规范要求远程对象所属的类实现一个远程接口,并且远程接口符合以下条件:
(a)直接或间接继承java.rmi.Remote接口。
(b)接口中的所有方法声明抛出java.rmi.RemoteException。
(2)创建远程类:实现远程接口。
远程类就是远程对象所属的类。RMI规范要求远程类必须实现一个远程接口。此外,为了使远程类的实例变成能为远程客户提供服务的远程对象,可通过以下两种途径之一把它导出(export)为远程对象:
(a)导出为远程对象的第一种方式:使远程类实现远程接口时,同时继承java.rmi.server.UnicastRemoteObject类,并且远程类的构造方法必须声明抛出RemoteException。这是最常用的方式,下面的本例子就采取这种方式。
public class RemoteImpl extends UnicastRemoteObject implements RemoteInterface
(b)导出为远程对象的第二种方式:如果一个远程类已经继承了其他类,无法再继承UnicastRemoteObject类,那么可以在构造方法中调用UnicastRemoteObject类的静态exportObject()方法,同样,远程类的构造方法也必须声明抛出RemoteException。
public class RemoteImpl extends OtherClass implements RemoteInterface{
private String name;
public RemoteImpl (String name)throws RemoteException{
this.name=name;
UnicastRemoteObject.exportObject(this,0);
}
在构造方法RemoteImpl 中调用了UnicastRemoteObject.exportObject(this,0)方法,将自身导出为远程对象。
exportObject()是UnicastRemoteObject的静态方法,源码是:
protected UnicastRemoteObject(int port) throws RemoteException
{
this.port = port;
exportObject((Remote) this, port);
}
public static Remote exportObject(Remote obj, int port)
throws RemoteException
{
return exportObject(obj, new UnicastServerRef(port));
}
exportObject(Remote obj, int port),该方法负责把参数obj指定的对象导出为远程对象,使它具有相应的存根(Stub),并监听远程客户的方法调用请求;参数port指导监听的端口,如果值为0,表示监听任意一个匿名端口。
(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):注销对象,取消对象与名字的绑定。
注册一个远程对象remoteObj2 关键代码如下:
RemoteInterface remoteObj2 = new RemoteImpl();// 创建远程对象
Context namingContext = new InitialContext();// 初始化命名内容
LocateRegistry.createRegistry(8892);// 在本地主机上创建和导出注册表实例,并在指定的端口上接受请求
namingContext.rebind("rmi://localhost:8892/RemoteObj2", remoteObj2);// 注册对象,即把对象与一个名字绑定。
但在JDK1.3版本或更低的版本,需要使用java.rmi.Naming来注册远程对象,注册代码代替如下:
RemoteInterface remoteObj = new RemoteImpl();
LocateRegistry.createRegistry(8891);
Naming.rebind("rmi://localhost:8891/RemoteObj", remoteObj);
(4)创建客户程序:通过 lookup()方法查找远程对象,进行远程方法调用
关键代码如下:
Context namingContext = new InitialContext();// 初始化命名内容
RemoteInterface RmObj2 = (RemoteInterface) namingContext.lookup("rmi://localhost:8892/RemoteObj2");//获得远程对象的存根对象
System.out.println(RmObj2.doSomething());//通过远程对象,调用doSomething方法
在JDK1.3版本或更低的版本,如下方式调用;
RemoteInterface RmObj = (RemoteInterface) Naming.lookup("rmi://localhost:8891/RemoteObj");
System.out.println(RmObj.doSomething());
例子
具体
代码:
1. 创建远程接口:继承java.rmi.Remote接口。
package rmi;
import java.rmi.Remote;
import java.rmi.RemoteException;
//声明一个远程接口RemoteInterface,该接口必须继承Remote接口
//接口中需要被远程调用的方法,必须抛出RemoteException异常
public interface RemoteInterface extends Remote {
// 声明一个doSomething方法
public String doSomething() throws RemoteException;
// 声明一个计算方法Calculate
public int Calculate(int num1, int num2) throws RemoteException;
}
在Java中,只要一个类extends了java.rmi.Remote接口,即可成为存在于服务器端的远程对象, 供客户端访问并提供一定的服务。JavaDoc描述:Remote 接口用于标识其方法可以从非本地虚拟机上调用的接口。任何远程对象都必须直接或间接实现此接口。只有在“远程接口” (扩展 java.rmi.Remote 的接口)中指定的这些方法才可被远程调用。注意:接口中需要被远程调用的方法,必须抛出RemoteException异常。
2. 创建远程类:实现远程接口
package rmi;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
//实现远程接口RemoteInterface,并继承UnicastRemoteObject
//注意RemoteObject这个类,实现了Serializable, Remote这两个接口
public class RemoteImpl extends UnicastRemoteObject implements RemoteInterface {
// 这个实现必须有一个显式的构造函数,并且要抛出一个RemoteException异常
public RemoteImpl() throws RemoteException {
}
// 实现doSomething方法
public String doSomething() throws RemoteException {
return "OK ,You can do......";
}
// 实现Calculate方法,返回计算结果
public int Calculate(int num1, int num2) throws RemoteException {
return (num1 + num2);
}
}
远程对象必须实现java.rmi.server.UniCastRemoteObject类,该类的构造函数中将生成stub和skeleton, 这样才能保证客户端访问获得远程对象时,该远程对象将会把自身的一个拷贝以Socket的形式传输给客户端,此时客户端所获得的这个拷贝称为Stub(
存根), 而服务器端本身已存在的远程对象则称之为Skeleton(骨架)
。
其实此时的存根是客户端的一个代理,用于与服务器端的通信, 而骨架也可认为是服务器端的一个代理,用于接收客户端的请求之后调用远程方法来响应客户端的请求。
3.创建服务器程序:在rmiregistry注册表中注册远程对象,向客户端提供远程对象服务
package rmi2;
import javax.naming.Context;
import javax.naming.InitialContext;
import rmi.RemoteInterface;
public class ClientTest {
public static void main(String args[]) {
try {
Context namingContext = new InitialContext();// 初始化命名内容
RemoteInterface RmObj2 = (RemoteInterface) namingContext
.lookup("rmi://localhost:8892/RemoteObj2");//获得远程对象的存根对象
System.out.println(RmObj2.doSomething());//通过远程对象,调用doSomething方法
System.out.println("远程服务器计算结果为:" + RmObj2.Calculate(90, 2));
} catch (Exception e) {
}
}
}
在JDK1.3版本或更低的版本,如下方式注册;
package rmi;
import java.net.MalformedURLException;
import java.rmi.AlreadyBoundException;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
//在JDK1.3版本或更低的版本,需要使用java.rmi.Naming来注册远程对象
//创建RMI注册表,启动RMI服务,并将远程对象注册到RMI注册表中。
public class RMIServer {
public static void main(String args[])
throws java.rmi.AlreadyBoundException {
try {
// 创建一个远程对象RemoteObj,实质上隐含了是生成stub和skeleton,并返回stub代理引用
RemoteInterface remoteObj = new RemoteImpl();
// 本地创建并启动RMI Service,被创建的Registry服务将在指定的端口,侦听请求
// Java默认端口是1099,缺少注册表创建,则无法绑定对象到远程注册表上
LocateRegistry.createRegistry(8891);
// 把远程对象注册到RMI注册服务器上,并命名为RemoteObj(名字可自定义,客户端要对应)
// 绑定的URL标准格式为:rmi://host:port/name(其中协议名可以省略,下面两种写法都是正确的)
Naming.rebind("rmi://localhost:8891/RemoteObj", remoteObj);// 将stub代理绑定到Registry服务的URL上
// Naming.bind("//localhost:8880/RemoteObj",remoteObj);
System.out.println(">>>>>INFO:远程IHello对象绑定成功!");
} catch (RemoteException e) {
System.out.println("创建远程对象发生异常!");
e.printStackTrace();
} catch (MalformedURLException e) {
System.out.println("发生URL畸形异常!");
e.printStackTrace();
}
}
}
RMIServer类主要实现注册远程对象,并向客户端提供远程对象服务。远程对象是在远程服务上创建的,你无法确切地知道远程服务器上的对象的名称 。但是,将远程对象注册到RMI Service之后,客户端就可以通过RMI Service请求到该远程服务对象的stub了,利用stub代理就可以访问远程服务对象了 。
4. 客户端代码
Server端的代码已经全部写完,这时把服务器的接口RemoteInterface打包成jar,以便在Client端的项目
中
使用。
项目-->右键-->导出-->jar->选择RemoteInterface.java-->finish;关于客户端如何导入jar包,请看这里:http://jingyan.baidu.com/article/ca41422fc76c4a1eae99ed9f.html
package rmi2;
import javax.naming.Context;
import javax.naming.InitialContext;
import rmi.RemoteInterface;
public class ClientTest {
public static void main(String args[]) {
try {
Context namingContext = new InitialContext();// 初始化命名内容
RemoteInterface RmObj2 = (RemoteInterface) namingContext
.lookup("rmi://localhost:8892/RemoteObj2");//获得远程对象的存根对象
System.out.println(RmObj2.doSomething());//通过远程对象,调用doSomething方法
System.out.println("远程服务器计算结果为:" + RmObj2.Calculate(90, 2));
} catch (Exception e) {
}
}
}
在JDK1.3版本或更低的版本,如下方式调用
import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
public class ClientTest {
public static void main(String args[]) {
try {
// 在RMI服务注册表中查找名称为RemoteObj的对象,并调用其上的方法
// 客户端通过命名服务Naming获得指向远程对象的远程引用
RemoteInterface RmObj = (RemoteInterface) Naming
.lookup("rmi://localhost:8881/RemoteObj");
System.out.println(RmObj.doSomething());
System.out.println("远程服务器计算结果为:" + RmObj.Calculate(1, 2));
} catch (NotBoundException e) {
e.printStackTrace();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
OK ,You can do......
远程服务器计算结果为:92
例子代码下载:http://download.csdn.net/detail/guyuealian/9583633
如果你觉得该帖子帮到你,还望贵人多多支持,鄙人会再接再厉,继续努力的~