RMI(Remote Method Invocation)是JAVA早期版本(JDK 1.1)提供的分布式应用解决方案,它作为重要的API被广泛的应用在EJB中。随着互联网应用的发展,分布式处理任务也随之复杂起 来,WebService也得到普遍的推广和应用。
        在某些方面,例如跨语言平台的分布式应用,RMI就显得力不从心了。在实际的应用中,是采用WebService还是传统的RMI来实现?这是一个需要权衡的问题,两者的比较如下所述:
        1. 比起WebService,它只能使用(调用)由JAVA编写的远程服务。而WebService是跨语言平台的,只要客户端和服务端双方都遵守SOAP规范即可;
        2. RMI是在TCP协议基础上传递可序列化的java对象(字节数据),而WebService是在HTTP协议基础上通过XML来传输数据的。因此,在同等业务数据量的前提下,RMI的效率要高于WebService。
        因此,RMI可以用在业务结构比较简单,而要求实时高效的分布式应用中。 
        从设计角度上讲,JAVA采用的是三层结构模式来实现RMI。在整个体系结构中,有如下几个关键角色构成了通信双方:
        1.客户端:
           1)桩(StubObject):远程对象在客户端上的代理;
           2)远程引用层(RemoteReference Layer):解析并执行远程引用协议;
           3)传输层(Transport):发送调用、传递远程方法参数、接收远程方法执行结果。
        2.服务端:
           1)骨架(Skeleton):读取客户端传递的方法参数,调用服务器方的实际对象方法,并接收方法执行后的返回值;
           2)远程引用层(Remote ReferenceLayer):处理远程引用语法之后向骨架发送远程方法调用;
           3)传输层(Transport):监听客户端的入站连接,接收并转发调用到远程引用层。
        3.注册表(Registry):以URL形式注册远程对象,并向客户端回复对远程对象的引用。


图1 RMI三层模型

在实际的应用中,客户端并没有真正的和服务端直接对话来进行远程调用,而是通过本地JVM环境下的桩对象来进行的。
        1.远程调用过程:
        1)客户端从远程服务器的注册表中查询并获取远程对象引用。当进行远程调用时,客户端首先会与桩对象(Stub Object)进行对话,而这个桩对象将远程方法所需的参数进行序列化后,传递给它下层的远程引用层(RRL);
        2)桩对象与远程对象具有相同的接口和方法列表,当客户端调用远程对象时,实际上是由相应的桩对象代理完成的。远程引用层在将桩的本地引用转换为服务器上对象的远程引用后,再将调用传递给传输层(Transport),由传输层通过TCP协议发送调用;      
        3)在服务器端,传输层监听入站连接,它一旦接收到客户端远程调用后,就将这个引用转发给其上层的远程引用层;
        4)服务器端的远程引用层将客户端发送的远程应用转换为本地虚拟机的引用后,再将请求传递给骨架(Skeleton);
        5)骨架读取参数,又将请求传递给服务器,最后由服务器进行实际的方法调用。
        2.结果返回过程:
        1)
如果远程方法调用后有返回值,则服务器将这些结果又沿着“骨架->远程引用层->传输层”向下传递;
        2)
客户端的传输层接收到返回值后,又沿着“传输层->远程引用层->桩”向上传递,然后由桩来反序列化这些返回值,并将最终的结果传递给客户端程序。
        又从技术的角度上讲,有如下几个主要类或接口扮演着上述三层模型中的关键角色:
        1)注册表:java.rmi.Naming和ava.rmi.Registry;
        2)骨架:java.rmi.remote.Skeleton        
        3)桩:java.rmi.server.RemoteStub
        4)远程引用层:java.rmi.server.RemoteRef和sun.rmi.transport.Endpoint;
        5)传输层:sun.rmi.transport.Transport
        作为一般的RMI应用,JAVA为我们隐藏了其中的处理细节,而让开发者有更多的精力和时间花在实际的应用中。开发RMI的步骤如下所述: 
        1.服务端:
        1)定义Remote子接口,在其内部定义要发布的远程方法,并且这些方法都要Throws RemoteException;
        2)定义远程对象的实现类,通常有两种方式:
              a. 继承UnicastRemoteObject或Activatable,并同时实现Remote子接口; 
               b. 只实现Remote子接口和java.io.Serializable接口。 
        3)编译桩(在JAVA 1.5及以后版本中,如果远程对象实现类继承了UnicastRemoteObject或Activatable,则无需此步,由JVM自动完成。否则需手工利用rmic工具编译生成此实现类对应的桩类,并放到和实现类相同的编译目录下); 
        4)启动服务器:依次完成注册表的启动和远程对象绑定。另外,如果远程对象实现类在定义时没有继承UnicastRemoteObject或Activatable,则必须在服务器端显示的调用UnicastRemoteObject类中某个重载的exportObject(Remote remote)静态方法,将此实现类对象导出成为一个真正的远程对象。 
        2.客户端: 
       1)定义用于接收远程对象的Remote子接口,只需实现java.rmi.Remote接口即可。但要求必须与服务器端对等的Remote子接口保持一致,即有相同的接口名称、包路径和方法列表等。
       2)通过符合JRMP规范的URL字符串在注册表中获取并强转成Remote子接口对象;
       3)调用这个Remote子接口对象中的某个方法就是为一次远程方法调用行为。

下面结合一个例子来说明RMI分布式应用的开发步骤。
        背景:远程系统管理接口SystemManager允许客户端远程调用其内部的某个方法,来获取服务器环境下某个属性的值。因此,第一步需要在服务端和与之通信的客户端环境下,定义一个完全一样的SystemManager接口,将此接口标记为远程对象。

1.在服务端和客户端定义对等Remote子接口(SystemManager)

2.在服务端定义Remote子接口的实现类(SystemManagerImpl),即远程对象的实际行为逻辑。

  package com.daniele.appdemo.rmi.server;  

     import java.io.Serializable;
import java.rmi.RemoteException;
import java.rmi.server.RemoteServer;
import java.rmi.server.ServerNotActiveException; import org.apache.log4j.Logger; import com.daniele.appdemo.rmi.SystemManager;
import com.daniele.appdemo.test.domain.User;
import com.daniele.appdemo.util.SystemUtils; /**
* <p>
* 系统管理远程对象实现类。有两种实现方式:
* 1.继承UnicastRemoteObject或Activatable,并同时实现Remote子接口
* 2.只实现Remote子接口,这种方式灵活但比较复杂:
* 1)要求此实现类必须实现java.io.Serializable接口;
* 2)通过这种方式定义的实现类此时还不能叫做远程对象实现类,
* 因为在服务器端绑定远程对象之前,还需要利用JDK提供的rmic工具
* 将此实现类手工编译生成对应的桩实现类,并放到和它相同的编译目录下。
* </p>
* @author <a href="mailto:code727@gmail.com">Daniele</a>
* @version 1.0.0, 2013-5-21
* @see
* @since AppDemo1.0.0
*/
public class SystemManagerImpl implements SystemManager, Serializable { //public class SystemManagerImpl extends UnicastRemoteObject implements SystemManager { private static final long serialVersionUID = 9128780104194876777L; private static final Logger logger = Logger.getLogger(SystemManagerImpl.class); private static SystemManagerImpl systemManager = null; /**
* <p>在服务端本地的匿名端口上创建一个用于监听目的的UnicastRemoteObject对象</p>
* @author <a href="mailto:code727@gmail.com">Daniele</a>
* @throws RemoteException
* @since AppDemo1.0.0
*/
private SystemManagerImpl() throws RemoteException {
super();
// 在控制台中显示远程对象被调用,以及返回结果时产生的日志
RemoteServer.setLog(System.out);
} public static SystemManagerImpl getInstance() throws RemoteException {
if (systemManager == null) {
synchronized (SystemManagerImpl.class) {
if (systemManager == null)
systemManager = new SystemManagerImpl();
}
}
return systemManager;
} public String getAllSystemMessage() throws RemoteException {
try {
/*
* getClientHost()方法可以获取触发当前远程方法被调用时的客户端的主机名。
* 在远程服务端的环境中,如果当前线程实际上没有运行客户端希望调用的远程方法时,
* 则会抛出ServerNotActiveException。
* 因此,为了尽量避免这个异常的发生,它通常用于远程方法的内部实现逻辑中,
* 以便当此方法真正的被调用时,可以记录下哪个客户端在什么时间调用了这个方法。
*/
logger.info("Client {" + RemoteServer.getClientHost() + "} invoke method [getAllSystemMessage()]" );
} catch (ServerNotActiveException e) {
e.printStackTrace();
}
return SystemUtils.formatSystemProperties();
} public String getSystemMessage(String properties) throws RemoteException {
try {
logger.info("Client {"
+ RemoteServer.getClientHost()
+ "} invoke method [getAllSystemMessage(String properties)]");
} catch (ServerNotActiveException e) {
e.printStackTrace();
}
return SystemUtils.formatSystemProperties(properties.split(","));
} }

3.由于SystemManagerImpl 不是通过继承UnicastRemoteObject 或 Activatable来实现的,因此在服务器端需要利用JDK提供的rmic工具编译生成对应的桩类。否则,此步略过。例如,在Windows环境下打开命令控制台后  
1)进入工程根目录 :   
         cd d:\ Development\AppDemo
2)桩编译: 
         cmic -classpath WebContent\WEB-INF\classes com.daniele.appdemo.rmi.server.SystemManagerImpl
   语法格式为:
    cmic -classpath <远程对象实现类bin目录的相对路径> <远程对象实现类所在的包路径>.<远程对象实现类的名称>
       完成后,rmic将在相对于根目录的com\daniele\appdemo\rmi\server子目录中自动生成SystemManagerImpl_Stub桩对象类 (即“远程对象实现类名称_Stub”) 的编译文件,此时需要再将此编译文件拷贝到与远程对象实现类SystemManagerImpl相同的编译目录(\WebContent\WEB-INF\classes\com\daniele\appdemo\rmi\server)中,否则在服务器端发布远程对象时将会抛出java.rmi.StubNotFoundException。如下图所示。

图2 实现类与桩

需要特别注意的是:如果服务端中的远程对象实现类存在有对应的桩对象类编译文件,则要求在RMI客户端的环境中,也必须有这个对等的桩对象类编译文件,即意味着这个文件在两端有着相同的包路径、文件名和内部实现细节。因此,最简单的做法就是连同整个包(com\daniele\appdemo\rmi\server)在内,将图2中的SystemManagerImpl_Stub.class文件拷贝到RMI客户端工程的bin目录下即可。如下图。否则,当RMI客户端调用远程服务时将会抛出java.rmi.StubNotFoundException。

图3 RMI客户端工程下的对等桩文件

4.创建用于发布远程对象目的用的服务器(SystemManagerServer)

     package com.daniele.appdemo.rmi.server;  

     import java.io.IOException;
import java.util.Arrays;
import java.rmi.Naming;
import java.rmi.registry.LocateRegistry;
import java.rmi.server.UnicastRemoteObject; import org.apache.log4j.Logger; import com.daniele.appdemo.rmi.SystemManager; /**
* <p>
* 系统管理远程服务器,它主要完成如下任务:
* 1.在绑定之前先启动注册表服务。
* 2.将远程对象SystemManager绑定到注册表中,以便让客户端能远程调用这个对象所发布的方法;
* </p>
* @author <a href="mailto:code727@gmail.com">Daniele</a>
* @version 1.0.0, 2013-5-21
* @see
* @since AppDemo1.0.0
*/
public class SystemManagerServer { private static final Logger logger = Logger.getLogger(SystemManagerServer.class); public static void main(String[] args) {
try { SystemManager systemManager = SystemManagerImpl.getInstance(); /*
* 如果远程对象实现类不是通过继承UnicastRemoteObject或Activatable来定义的,
* 则必须在服务器端显示的调用UnicastRemoteObject类中某个重载的exportObject(Remote remote)静态方法,
* 将此实现类的某个对象导出成为一个真正的远程对象。否则,此步省略。
*/
UnicastRemoteObject.exportObject(systemManager); int port = 9527;
String url = "rmi://localhost:" + port + "/";
String remoteObjectName = "systemManager"; /*
* 在服务器的指定端口(默认为1099)上启动RMI注册表。
* 必不可缺的一步,缺少注册表创建,则无法绑定对象到远程注册表上。
*/
LocateRegistry.createRegistry(port); /*
* 将指定的远程对象绑定到注册表中:
* 1.如果端口号不为默认的1099,则绑定时远程对象名称中必须包含Schema,
* 即"rmi://<host或ip><:port>/"部分,并且Schema里指定的端口号应与createRegistry()方法参数中的保持一致
* 2.如果端口号为RMI默认的1099,则远程对象名称中不用包含Schema部分,直接定义名称即可
*/
if (port == 1099)
Naming.rebind(remoteObjectName, systemManager);
else
Naming.rebind(url + remoteObjectName, systemManager);
logger.info("Success bind remote object " + Arrays.toString(Naming.list(url)));
} catch (IOException e) {
e.printStackTrace();
}
} }

 5.创建发出远程调用请求的客户端(SystemManagerClient)

完成后,再依次到服务器和客户端上启动SystemManagerServer和SystemManagerClient即可。

转载:http://code727.iteye.com/blog/1874271

JAVA RMI分布式原理和应用的更多相关文章

  1. java RMI原理详解

    java本身提供了一种RPC框架——RMI(即Remote Method Invoke 远程方法调用),在编写一个接口需要作为远程调用时,都需要继承了Remote,Remote 接口用于标识其方法可以 ...

  2. Java RMI 的使用及原理

    1.示例 三个角色:RMIService.RMIServer.RMIClient.(RMIServer向RMIService注册Stub.RMIService在RMIClient lookup时向其提 ...

  3. 为什么 jmeter 分布式测试,一定要设置 java.rmi.server.hostname

    之前总结了 jmeter 分布式测试的过程,在部署过程中提到,要在 system.properties 中配置自己的 IP. 至于为什么要这么做,源于这一次 debug 的过程. 运行环境 mint, ...

  4. 关于metaspolit中进行JAVA反序列化渗透RMI的原理分析

    一.背景: 这里需要对java反序列化有点了解,在这里得推广下自己的博客嘛,虽然写的不好,广告还是要做的.原谅我: 1.java反序列化漏洞原理研习 2.java反序列化漏洞的检测 二.攻击手法简介 ...

  5. 转载:把你的精力专注在java,jvm原理,spring原理,mysql锁,事务,多线程,大并发,分布式架构,微服务,以及相关的项目管理等等,这样你的核心竞争力才会越来越高

    https://developer.51cto.com/art/202001/608984.htm 把你的精力专注在java,jvm原理,spring原理,mysql锁,事务,多线程,大并发,分布式架 ...

  6. Java RMI 介绍和例子以及Spring对RMI支持的实际应用实例

    RMI 相关知识 RMI全称是Remote Method Invocation-远程方法调用,Java RMI在JDK1.1中实现的,其威力就体现在它强大的开发分布式网络应用的能力上,是纯Java的网 ...

  7. Java学习笔记(十六)——Java RMI

    [前面的话] 最近过的好舒服,每天过的感觉很充实,一些生活和工作的技巧注意了就会发现,其实生活也是可以过的如此的有滋有味,满足现在的状况,并且感觉很幸福. 学习java RMI的原因是最近在使用dub ...

  8. Java RMI(远程方法调用)开发

    参考 https://docs.oracle.com/javase/7/docs/platform/rmi/spec/rmi-arch2.html http://www.cnblogs.com/wxi ...

  9. java RMI入门指南

    感觉这篇文章不错,直接转了 RMI全称是Remote Method Invocation-远程方法调用,Java RMI在JDK1.1中实现的,其威力就体如今它强大的开发分布式网络应用的能力上,是纯J ...

随机推荐

  1. string::cbegin string::cend

    const_iterator cbegin() const noexcept; const_iterator cend() const noexcept;注:返回常量迭代器,不能修改 #include ...

  2. string::back

    char& back(); const char& back() const; #include <iostream>#include <string> usi ...

  3. 在laravel5.8中集成swoole组件----用协程实现的服务端和客户端(二)---静态文件如何部署

    目前,较为成熟的技术是采用laravelS组件,注意和laravel 区别laravelS多了一个大写的S,由于laravelS默认监听5200端口,所以laravel项目要做一些调整 例如: 静态文 ...

  4. 状压dp做题笔记

    CodeChef Factorial to Square (分块决策) Description 给定一个n,要求在[1,n]中删除一些数,并使剩下的数的乘积是一个完全平方数,同时要求乘积最大,求删除方 ...

  5. Educational Codeforces Round 34 (Rated for Div. 2) B题【打怪模拟】

    B. The Modcrab Vova is again playing some computer game, now an RPG. In the game Vova's character re ...

  6. B/S大文件分片上传

    需求:项目要支持大文件上传功能,经过讨论,初步将文件上传大小控制在500M内,因此自己需要在项目中进行文件上传部分的调整和配置,自己将大小都以501M来进行限制. 第一步: 前端修改 由于项目使用的是 ...

  7. vector 与 array

    vector STL中的模板数组(在堆中分配内存空间,通过new delete管理内存) 使用包涵头文件#include <vector> vector<ElmentTpye> ...

  8. 小米oj 反向位整数(简单位运算)

     反向位整数 序号:#30难度:一般时间限制:1000ms内存限制:10M 描述 输入32位无符号整数,输出它的反向位. 例,输入4626149(以二进制表示为00000000010001101001 ...

  9. HDU3501 Calculation 2 [欧拉函数]

    题目传送门 Calculation 2 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Othe ...

  10. 11.17 模拟赛&&day-2

    /* 后天就要复赛了啊啊啊啊啊. 可能是因为我是一个比较念旧的人吧. 讲真 还真是有点不舍. 转眼间一年的时间就过去了. 2015.12-2016.11. OI的一年. NOIP gryz RP++. ...