关闭

Java RMI远程方法调用详解

标签: Java RMI远程方法调用进程通信Stub和Skeleton远程方法调用RMI
9886人阅读 评论(4) 收藏 举报

Java RMI远程方法调用详解

    【尊重原创,转载请注明出处】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




如果你觉得该帖子帮到你,还望贵人多多支持,鄙人会再接再厉,继续努力的~

5
0
查看评论

远程过程调用(RPC)详解

本文介绍了什么是远程过程调用(RPC),RPC 有哪些常用的方法,RPC 经历了哪些发展阶段,以及比较了各种 RPC 技术的优劣。
  • kkkloveyou
  • kkkloveyou
  • 2016-07-11 00:12
  • 22404

开发中常用的五种远程调用方式

一、综述 本文比较了RMI,Hessian,Burlap,Httpinvoker,web service等5种通讯协议的在不同的数据结构和不同数据量时的传输性能。 RMI是java语言本身提供的远程通讯协议,稳定高效,是EJB的基础。但它只能用于JAVA程序之间的通讯。 Hessian和Bur...
  • xujiangdong1992
  • xujiangdong1992
  • 2017-07-31 14:08
  • 583

远程调用的几种方式

在分布式服务框架中,最基础的问题就是远程服务是怎么通讯的。首先来看看计算机系统网络通信的基本原理,网络通信需要做的就是将流从一台计算机传输到另外一台计算 机,基于传输协议和网络IO来实现,其中传输协议有 tcp、udp等等,tcp、udp都是在基于Socket概念上为某类应用场景而扩展出的传输协议...
  • kanglix1an
  • kanglix1an
  • 2015-06-29 20:28
  • 2848

分布式计算——远程对象和远程方法的调用

分布式计算中有个远程方法的调用,在此基础上有个作业是在调用的远程方法中传递两个参数, 一个参数是本地对象,一个参数是远程对象。 下面就对这次的作业结果进行整理。 作业的要求如下:Java RMI远程方法调用实验:若一个方法是远程调用,其参数一是本地对象、参数二是远程对象,请实现程序, 在三台计算机上...
  • u011215133
  • u011215133
  • 2016-04-04 23:23
  • 1427

Java远程方法调用

主要参考 http://www.kaixinwenda.com/article-yxc135-7690958.html http://bbs.chinaunix.net/thread-1179312-1-1.html Java 远程处理     Java远程方法调用(R...
  • sunmenggmail
  • sunmenggmail
  • 2013-01-26 10:09
  • 7121

远程调用的几种方式

在分布式服务框架中,一个最基础的问题就是远程服务是怎么通讯的,在Java领域中有很多可实现远程通讯的技术,例如:RMI、MINA、ESB、 Burlap、Hessian、SOAP、EJB和JMS 等,这些名词之间到底是些什么关系呢,它们背后到底是基于什么原理实现的呢,了解这些是实现分布式服务框架的基...
  • Magister_Feng
  • Magister_Feng
  • 2013-02-26 10:37
  • 14692

Java RMI服务远程命令执行利用

-------------------------------------------------------------- 很长时间没更新博客了,今天来一发。 -------------------------------------------------------------- 0x00 ...
  • u011721501
  • u011721501
  • 2016-02-23 19:32
  • 9975

JAVA RMI 反序列化远程命令执行漏洞

JAVA RMI 反序列化远程命令执行漏洞 漏洞资料 背景 原理 Payload构造 搭建本地测试环境 开启包含第三方库的RMI服务 测试RMI客户端 攻击测试 升级版攻击 Weblogic Commons-Collections反序列化RCE漏洞CVE-2015-4852JAVA RMI 反序列化...
  • LeeHDsniper
  • LeeHDsniper
  • 2017-05-11 00:42
  • 6935

Java之RMI(远程方法调用)

RMI(Remote Method Invocation)中文名称是远程方法调用,可用于分布式计算。 这里就不去详细介绍RMI了,本Blog主要讲叙RMI实战和有哪些需要注意的地方,如果想要查看详细介绍请查看:百度百科RMIRMI分为服务端和客户端服务端:创建服务端:LocateRegistry....
  • u012643122
  • u012643122
  • 2015-06-14 12:28
  • 1166

java RMI远程方法调用详解

RMI是Java的一组拥护开发分布式应用程序的API。RMI使用Java语言接口定义了远程对象,它集合了Java序列化和Java远程方法协议(Java Remote Method Protocol)。简单地说,这样使原先的程序在同一操作系统的方法调用,变成了不同操作系统之间程序的方法调用,由于J2E...
  • jzhf2012
  • jzhf2012
  • 2013-01-07 14:43
  • 3441
    个人资料
    • 访问:552530次
    • 积分:5880
    • 等级:
    • 排名:第5195名
    • 原创:106篇
    • 转载:51篇
    • 译文:1篇
    • 评论:174条
    博客专栏
    最新评论