JAVA RMI分布式原理和应用

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)

Java代码  

  1. package com.daniele.appdemo.rmi;
  2. import java.rmi.Remote;
  3. import java.rmi.RemoteException;
  4. import com.daniele.appdemo.test.domain.User;
  5. /**
  6. * <p>系统管理远程对象接口</p>
  7. * @author  <a href="mailto:[email protected]">Daniele</a>
  8. * @version 1.0.0, 2013-5-21
  9. * @see
  10. * @since   AppDemo1.0.0
  11. */
  12. public interface SystemManager extends Remote {
  13. /**
  14. * <p>发布的远程对象方法一:获取所有系统环境信息</p>
  15. * @author <a href="mailto:[email protected]">Daniele</a>
  16. * @return
  17. * @throws RemoteException
  18. * @since AppDemo1.0.0
  19. */
  20. public String getAllSystemMessage() throws RemoteException;
  21. /**
  22. * <p>发布的远程对象方法二:获取指定的系统环境信息</p>
  23. * @author <a href="mailto:[email protected]">Daniele</a>
  24. * @param properties:环境信息对应的属性名,多个名之间用逗号隔开
  25. * @return
  26. * @throws RemoteException
  27. * @since AppDemo1.0.0
  28. */
  29. public String getSystemMessage(String properties) throws RemoteException;
  30. }

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

Java代码  

  1. package com.daniele.appdemo.rmi.server;
  2. import java.io.Serializable;
  3. import java.rmi.RemoteException;
  4. import java.rmi.server.RemoteServer;
  5. import java.rmi.server.ServerNotActiveException;
  6. import org.apache.log4j.Logger;
  7. import com.daniele.appdemo.rmi.SystemManager;
  8. import com.daniele.appdemo.test.domain.User;
  9. import com.daniele.appdemo.util.SystemUtils;
  10. /**
  11. * <p>
  12. *         系统管理远程对象实现类。有两种实现方式:
  13. *      1.继承UnicastRemoteObject或Activatable,并同时实现Remote子接口
  14. *      2.只实现Remote子接口,这种方式灵活但比较复杂:
  15. *        1)要求此实现类必须实现java.io.Serializable接口;
  16. *        2)通过这种方式定义的实现类此时还不能叫做远程对象实现类,
  17. *          因为在服务器端绑定远程对象之前,还需要利用JDK提供的rmic工具
  18. *          将此实现类手工编译生成对应的桩实现类,并放到和它相同的编译目录下。
  19. * </p>
  20. * @author  <a href="mailto:[email protected]">Daniele</a>
  21. * @version 1.0.0, 2013-5-21
  22. * @see
  23. * @since   AppDemo1.0.0
  24. */
  25. public class SystemManagerImpl implements SystemManager, Serializable {
  26. //public class SystemManagerImpl extends UnicastRemoteObject implements SystemManager {
  27. private static final long serialVersionUID = 9128780104194876777L;
  28. private static final Logger logger = Logger.getLogger(SystemManagerImpl.class);
  29. private static SystemManagerImpl systemManager = null;
  30. /**
  31. * <p>在服务端本地的匿名端口上创建一个用于监听目的的UnicastRemoteObject对象</p>
  32. * @author <a href="mailto:[email protected]">Daniele</a>
  33. * @throws RemoteException
  34. * @since AppDemo1.0.0
  35. */
  36. private SystemManagerImpl() throws RemoteException {
  37. super();
  38. // 在控制台中显示远程对象被调用,以及返回结果时产生的日志
  39. RemoteServer.setLog(System.out);
  40. }
  41. public static SystemManagerImpl getInstance() throws RemoteException {
  42. if (systemManager == null) {
  43. synchronized (SystemManagerImpl.class) {
  44. if (systemManager == null)
  45. systemManager = new SystemManagerImpl();
  46. }
  47. }
  48. return systemManager;
  49. }
  50. public String getAllSystemMessage() throws RemoteException {
  51. try {
  52. /*
  53. * getClientHost()方法可以获取触发当前远程方法被调用时的客户端的主机名。
  54. * 在远程服务端的环境中,如果当前线程实际上没有运行客户端希望调用的远程方法时,
  55. * 则会抛出ServerNotActiveException。
  56. * 因此,为了尽量避免这个异常的发生,它通常用于远程方法的内部实现逻辑中,
  57. * 以便当此方法真正的被调用时,可以记录下哪个客户端在什么时间调用了这个方法。
  58. */
  59. logger.info("Client {" + RemoteServer.getClientHost() + "} invoke method [getAllSystemMessage()]" );
  60. } catch (ServerNotActiveException e) {
  61. e.printStackTrace();
  62. }
  63. return SystemUtils.formatSystemProperties();
  64. }
  65. public String getSystemMessage(String properties) throws RemoteException {
  66. try {
  67. logger.info("Client {"
  68. + RemoteServer.getClientHost()
  69. + "} invoke method [getAllSystemMessage(String properties)]");
  70. } catch (ServerNotActiveException e) {
  71. e.printStackTrace();
  72. }
  73. return SystemUtils.formatSystemProperties(properties.split(","));
  74. }
  75. }

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)

Java代码  

  1. package com.daniele.appdemo.rmi.server;
  2. import java.io.IOException;
  3. import java.util.Arrays;
  4. import java.rmi.Naming;
  5. import java.rmi.registry.LocateRegistry;
  6. import java.rmi.server.UnicastRemoteObject;
  7. import org.apache.log4j.Logger;
  8. import com.daniele.appdemo.rmi.SystemManager;
  9. /**
  10. * <p>
  11. *         系统管理远程服务器,它主要完成如下任务:
  12. *      1.在绑定之前先启动注册表服务。
  13. *         2.将远程对象SystemManager绑定到注册表中,以便让客户端能远程调用这个对象所发布的方法;
  14. * </p>
  15. * @author  <a href="mailto:[email protected]">Daniele</a>
  16. * @version 1.0.0, 2013-5-21
  17. * @see
  18. * @since   AppDemo1.0.0
  19. */
  20. public class SystemManagerServer {
  21. private static final Logger logger = Logger.getLogger(SystemManagerServer.class);
  22. public static void main(String[] args) {
  23. try {
  24. SystemManager systemManager = SystemManagerImpl.getInstance();
  25. /*
  26. *  如果远程对象实现类不是通过继承UnicastRemoteObject或Activatable来定义的,
  27. *  则必须在服务器端显示的调用UnicastRemoteObject类中某个重载的exportObject(Remote remote)静态方法,
  28. *  将此实现类的某个对象导出成为一个真正的远程对象。否则,此步省略。
  29. */
  30. UnicastRemoteObject.exportObject(systemManager);
  31. int port = 9527;
  32. String url = "rmi://localhost:" + port + "/";
  33. String remoteObjectName = "systemManager";
  34. /*
  35. *  在服务器的指定端口(默认为1099)上启动RMI注册表。
  36. *  必不可缺的一步,缺少注册表创建,则无法绑定对象到远程注册表上。
  37. */
  38. LocateRegistry.createRegistry(port);
  39. /*
  40. *  将指定的远程对象绑定到注册表中:
  41. *  1.如果端口号不为默认的1099,则绑定时远程对象名称中必须包含Schema,
  42. *    即"rmi://<host或ip><:port>/"部分,并且Schema里指定的端口号应与createRegistry()方法参数中的保持一致
  43. *  2.如果端口号为RMI默认的1099,则远程对象名称中不用包含Schema部分,直接定义名称即可
  44. */
  45. if (port == 1099)
  46. Naming.rebind(remoteObjectName, systemManager);
  47. else
  48. Naming.rebind(url + remoteObjectName, systemManager);
  49. logger.info("Success bind remote object " + Arrays.toString(Naming.list(url)));
  50. } catch (IOException e) {
  51. e.printStackTrace();
  52. }
  53. }
  54. }

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

Java代码  

    1. package com.daniele.appdemo.rmi.client;
    2. import java.io.IOException;
    3. import java.rmi.Naming;
    4. import java.rmi.NotBoundException;
    5. import com.daniele.appdemo.rmi.SystemManager;
    6. import com.daniele.appdemo.test.domain.User;
    7. /**
    8. * <p>系统管理进行远程调用的客户端</p>
    9. * @author  <a href="mailto:[email protected]">Daniele</a>
    10. * @version 1.0.0, 2013-5-21
    11. * @see
    12. * @since   AppDemo1.0.0
    13. */
    14. public class SystemManagerClient {
    15. public static void main(String[] args) {
    16. /*
    17. * RMI URL格式:Schame/<远程对象名>
    18. * 1.Schame部分由"rmi://<server host或IP>[:port]"组成,
    19. *   如果远程对象绑定在服务端的1099端口(默认)上,则port部分可以省略,否则必须指定。
    20. * 2.URL最后一个"/"后面的值为远程对象绑定在服务端注册表上时定义的远程对象名,
    21. *   即对应Naming.rebind()方法第一个参数值中最后一个"/"后面的值
    22. */
    23. String rmi = "rmi://192.168.1.101:9527/systemManager";
    24. try {
    25. /*
    26. * 根据URL字符串查询并获取远程服务端注册表中注册的远程对象,
    27. * 这里返回的是本地实现了Remote接口的子接口对象(Stub),
    28. * 它与服务端中的远程对象具有相同的接口和方法列表,因而作为在客户端中远程对象的一个代理。
    29. */
    30. SystemManager systemManager = (SystemManager) Naming.lookup(rmi);
    31. //              System.out.println(systemManager.getAllSystemMessage());
    32. System.out.println(systemManager.getSystemMessage("java.version,os.name"));
    33. } catch (IOException e) {
    34. e.printStackTrace();
    35. } catch (NotBoundException e) {
    36. e.printStackTrace();
    37. }
    38. }
    39. }
时间: 2024-10-17 11:33:48

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

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

一.背景: 这里需要对java反序列化有点了解,在这里得推广下自己的博客嘛,虽然写的不好,广告还是要做的.原谅我: 1.java反序列化漏洞原理研习 2.java反序列化漏洞的检测 二.攻击手法简介 针对这个使用msf攻击需要大家看一篇文章:JMX RMI Exploit 实例 , 鸣谢,确实学到了很多,膜拜大牛 , 简要介绍下攻击手法: (1)下载mjet模块:下载连接mjet,如果是mac电脑安装好metaspolit以后可以直接使用git clone命令下载到metaspolit的父目录下

RMI网络编程: 如何搭建基于JDK1.5的分布式JAVA RMI

1. 在Eclipse里面创建一个server 端的project.然后,创建一个接口,这个接口是你要向client端开放的方法定义.它叫做:UserManagerInterface,而且必须继承Remote接口. 1 package dataserver.rmi.stub; 2 3 import java.rmi.Remote; 4 import java.rmi.RemoteException; 5 6 import dataserver.rmi.bean.Account; 7 8 publ

java RMI入门指南

感觉这篇文章不错,直接转了 RMI全称是Remote Method Invocation-远程方法调用,Java RMI在JDK1.1中实现的,其威力就体如今它强大的开发分布式网络应用的能力上,是纯Java的网络分布式应用系统的核心解决方式之中的一个.事实上它能够被看作是RPC的Java版本号.可是传统RPC并不能非常好地应用于分布式对象系统.而Java RMI 则支持存储于不同地址空间的程序级对象之间彼此进行通信.实现远程对象之间的无缝远程调用. RMI眼下使用Java远程消息交换协议JRMP

对JAVA RMI的认识

RMI的定义 RPC (Remote Procedure Call):远程方法调用,用于一个进程调用另一个进程中的过程,从而提供了过程的分布能力. RMI(Remote Method Invocation):远程方法调用,即在RPC的基础上有向前迈进了一步,提供分布式对象间的通讯.允许运行在一个java 虚拟机的对象调用运行在另一个java虚拟机上对象的方法.这两个虚拟机可以是运行在相同计算机上的不同进程中,也可以是运行在网络上的不同计算机中. RMI的全称宗旨就是尽量简化远程接口对象的调用.

Java RMI(远程方法调用) 实例与分析 (转)

目的: 通过本文,可以加深对Java RMI的理解,知道它的工作原理,怎么使用等. 也为了加深我自己的理解,故整理成文.不足之处,还望指出. 概念解释: RMI(RemoteMethodInvocation):远程方法调用,顾名思义,通过远程的方式调用非本地对象的方法并返回结果.使用远程调用通常解决本地计算瓶颈问题,例如分布式记算,最近很火的阿尔法狗人机大战,据说运算使用上千个CPU. JRMP(java remote method protocol):java远程方法协议,这是完成java到j

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

参考 https://docs.oracle.com/javase/7/docs/platform/rmi/spec/rmi-arch2.html http://www.cnblogs.com/wxisme/p/5296441.html http://blog.csdn.net/qb2049_xg/article/details/3278672 http://classfoo.com/ccby/article/p1wgbVn http://www.cnblogs.com/yin-jingyu/a

rmi 工作原理

rmi 工作原理 (转) 工作SocketJava应用服务器网络应用 RMI(Remote Method Invocation,远程方法调用)是Java的一组拥护开发分布式应用程序的API.RMI使用Java语言接口定义了远程对象,它集合了Java序 列化和Java远程方法协议(Java Remote Method Protocol).它大大增强了Java开发分布式应用的能力.简单地说,这样使原先的程序在同一操作系统的方法调用,变成了不同操作系统之间程序的方 法调用.这也就说明,RMI可以实现在

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

RMI 相关知识 RMI全称是Remote Method Invocation-远程方法调用,Java RMI在JDK1.1中实现的,其威力就体现在它强大的开发分布式网络应用的能力上,是纯Java的网络分布式应用系统的核心解决方案之一.其实它可以被看作是RPC的Java版本.但是传统RPC并不能很好地应用于分布式对象系统.而Java RMI 则支持存储于不同地址空间的程序级对象之间彼此进行通信,实现远程对象之间的无缝远程调用. RMI目前使用Java远程消息交换协议JRMP(Java Remot

Java RMI之HelloWorld篇

Java RMI之HelloWorld篇 Java RMI 指的是远程方法调用 (Remote Method Invocation).它是一种机制,能够让在某个 Java 虚拟机上的对象调用另一个 Java 虚拟机中的对象上的方法.可以用此方法调用的任何对象必须实现该远程接口. Java RMI不是什么新技术(在Java1.1的时代都有了),但却是是非常重要的底层技术. 大名鼎鼎的EJB都是建立在rmi基础之上的,现在还有一些开源的远程调用组件,其底层技术也是rmi. 在大力鼓吹Web Serv