zoukankan      html  css  js  c++  java
  • 每日扫盲(一):java的rmi

    JAVA RMI 原理和使用浅析

    本地对象调用

    我们先看看本地对象方法的调用:

    ObjectClass objectA = new ObjectClass(); 
    String retn = objectA.Method();

    但是想想,如果objectA对象在JVM a上;而我们的程序在JVM b上,而且想访问JVM a上的objectA对象方法,如何做呢?对于JVM b上的应用程序来说,是不知道JVM a上创建的ObjectClass实例对象名称是什么,因为这次我创建的实例对象可能是objectA,下次我程序一改,我创建的实例对象又叫objectB了,另外,我创没创建ObjectClass实例对象,JVM b上应用程序又怎么知道呢?


    RMI就解决了这个问题。

    工作原理


    方法调用从客户对象经占位程序(Stub)、远程引用层(Remote Reference Layer)和传输层(Transport Layer)向下,传递给主机,然后再次经传 输层,向上穿过远程调用层和骨干网(Skeleton),到达服务器对象。 占位程序扮演着远程服务器对象的代理的角色,使该对象可被客户激活。 远程引用层处理语义、管理单一或多重对象的通信,决定调用是应发往一个服务器还是多个。传输层管理实际的连接,并且追踪可以接受方法调用的远程对象。服务器端的骨干网完成对服务器对象实际的方法调用,并获取返回值。返回值向下经远程引用层、服务器端的传输层传递回客户端,再向上经传输层和远程调用层返回。最后,占位程序获得返回值。 
    要完成以上步骤需要有以下几个步骤: 
    1、 生成一个远程接口 
    2、 实现远程对象(服务器端程序)
    3、 生成占位程序和骨干网(服务器端程序)
    4、 编写服务器程序 
    5、 编写客户程序 
    6、 注册远程对象 
    7、 启动远程对象  

    由于有RMI系统的支持,我们写RMI应用程序时只需要继承相关类,实现相关接口就可以了。也就是说,我们只需要定义接口、接口实现、客户端程序和服务端程序就可以了。

     


    上图中的stub和skeleton代理都是在服务端程序中由RMI系统动态生成,服务端程序只需要继承java.rmi.server.UnicastRemoteObject类。

    那么上图中的RMI Service(RMI registry)是怎么回事呢?

    先卖个关子:

    可以说,RMI由3个部分构成,第一个是RMIService即JDK提供的一个可以独立运行的程序(bin目录下的rmiregistry),第二个是RMIServer即我们自己编写的一个java项目,这个项目对外提供服务。第三个是RMIClient即我们自己编写的另外一个java项目,这个项目远程使用RMIServer提供的服务。
    首先,RMIService必须先启动并开始监听对应的端口。
    其次,RMIServer将自己提供的服务的实现类注册到RMIService上,并指定一个访问的路径(或者说名称)供RMIClient使用。
    最后,RMIClient使用事先知道(或和RMIServer约定好)的路径(或名称)到RMIService上去寻找这个服务,并使用这个服务在本地的接口调用服务的具体方法。
    通俗的讲完了再稍微技术的讲下:
    首先,在一个JVM中启动rmiregistry服务,启动时可以指定服务监听的端口,也可以使用默认的端口。
    其次,RMIServer在本地先实例化一个提供服务的实现类,然后通过RMI提供的Naming,Context,Registry等类的bind或rebind方法将刚才实例化好的实现类注册到RMIService上并对外暴露一个名称。
    最后,RMIClient通过本地的接口和一个已知的名称(即RMIServer暴露出的名称)再使用RMI提供的Naming,Context,Registry等类的lookup方法从RMIService那拿到实现类。这样虽然本地没有这个类的实现类,但所有的方法都在接口里了,想怎么调就怎么调吧。
    值得注意的是理论上讲RMIService,RMIServer,RMIClient可以部署到3个不同的JVM中,这个时候的执行的顺序是RMIService---RMIServer—RMIClient。另外也可以由RMIServer来启动RMIService这时候执行的顺序是RMIServer—RMIService—RMIClient。

    实际应用中很少有单独提供一个RMIService服务器,开发的时候可以使用Registry类在RMIServer中启动RMIService。

      

    RMI远程调用步骤
    RMI的交互图:

    RMI由3个部分构成,第一个是rmiregistry(JDK提供的一个可以独立运行的程序,在bin目录下),第二个是server端的程序,对外提供远程对象,第三个是client端的程序,想要调用远程对象的方法。
    首先,先启动rmiregistry服务,启动时可以指定服务监听的端口,也可以使用默认的端口(1099)。
    其次,server端在本地先实例化一个提供服务的实现类,然后通过RMI提供的Naming/Context/Registry(下面实例用的Registry)等类的bind或rebind方法将刚才实例化好的实现类注册到rmiregistry上并对外暴露一个名称。
    最后,client端通过本地的接口和一个已知的名称(即rmiregistry暴露出的名称)再使用RMI提供的Naming/Context/Registry等类的lookup方法从RMIService那拿到实现类。这样虽然本地没有这个类的实现类,但所有的方法都在接口里了,便可以实现远程调用对象的方法了。

    存根和骨干网的具体通信过程:

    方法调用从客户对象经存根(stub)、远程引用层(Remote Reference Layer)和传输层(Transport Layer)向下,传递给主机,然后再次经传输层,向上穿过远程调用层和骨干网(Skeleton),到达服务器对象。
    存根扮演着远程服务器对象的代理的角色,使该对象可被客户激活。
    远程引用层处理语义、管理单一或多重对象的通信,决定调用是应发往一个服务器还是多个。
    传输层管理实际的连接,并且追踪可以接受方法调用的远程对象。
    骨干网完成对服务器对象实际的方法调用,并获取返回值。
    返回值向下经远程引用层、服务器端的传输层传递回客户端,再向上经传输层和远程调用层返回。最后,存根获得返回值。

    JAVA RMI简单示例

    本示例是client端调用server端远程对象的加减法方法,具体步骤为:

    1. 定义一个远程接口(这个是存在于server端的类)

    import java.rmi.Remote;
    import java.rmi.RemoteException;
    
    /**
     * 必须继承Remote接口。
     * 所有参数和返回类型必须序列化(因为要网络传输)。
     * 任意远程对象都必须实现此接口。
     * 只有远程接口中指定的方法可以被调用。
     */
    public interface IRemoteMath extends Remote {
    
          // 所有方法必须抛出RemoteException
        public double add(double a, double b) throws RemoteException;
        public double subtract(double a, double b) throws RemoteException;
        
    }

    2、远程接口实现类(存在于服务器端计算机的类)

    import java.rmi.RemoteException;
    import java.rmi.server.UnicastRemoteObject;
    import remote.IRemoteMath;
    
    /**
     * 服务器端实现远程接口。
     * 必须继承UnicastRemoteObject,以允许JVM创建远程的存根/代理。
     */
    public class RemoteMath extends UnicastRemoteObject implements IRemoteMath {
    
        private int numberOfComputations;
        
        protected RemoteMath() throws RemoteException {
            numberOfComputations = 0;
        }
        
        @Override
        public double add(double a, double b) throws RemoteException {
            numberOfComputations++;
            System.out.println("Number of computations performed so far = " 
                    + numberOfComputations);
            return (a+b);
        }
    
        @Override
        public double subtract(double a, double b) throws RemoteException {
            numberOfComputations++;
            System.out.println("Number of computations performed so far = " 
                    + numberOfComputations);
            return (a-b);
        }
    
    }

    3、 服务器端)(存在于服务器端计算机上)

    import java.rmi.registry.LocateRegistry;
    import java.rmi.registry.Registry;
    import remote.IRemoteMath;
    
    /**
     * 创建RemoteMath类的实例并在rmiregistry中注册。
     */
    public class RMIServer {
    
        public static void main(String[] args)  {
            
            try {
                // 注册远程对象,向客户端提供远程对象服务。
                // 远程对象是在远程服务上创建的,你无法确切地知道远程服务器上的对象的名称,
                // 但是,将远程对象注册到RMI Registry之后,
                // 客户端就可以通过RMI Registry请求到该远程服务对象的stub,
                // 利用stub代理就可以访问远程服务对象了。
                IRemoteMath remoteMath = new RemoteMath();  
                LocateRegistry.createRegistry(1099);    
                Registry registry = LocateRegistry.getRegistry();
                registry.bind("Compute", remoteMath);
                System.out.println("Math server ready");
                // 如果不想再让该对象被继续调用,使用下面一行
                // UnicastRemoteObject.unexportObject(remoteMath, false);
            } catch (Exception e) {
                e.printStackTrace();
            }        
            
        }
        
    }

    4. 客户端(存在于客户端计算机上)

    import java.rmi.registry.LocateRegistry;
    import java.rmi.registry.Registry;
    import remote.IRemoteMath;
    
    public class MathClient {
    
        public static void main(String[] args) {
            
            try { 
                // 如果RMI Registry就在本地机器上,URL就是:rmi://localhost:1099/hello
                // 否则,URL就是:rmi://RMIService_IP:1099/hello
                Registry registry = LocateRegistry.getRegistry("localhost");        
                // 从Registry中检索远程对象的存根/代理
                IRemoteMath remoteMath = (IRemoteMath)registry.lookup("Compute");
                // 调用远程对象的方法
                double addResult = remoteMath.add(5.0, 3.0);
                System.out.println("5.0 + 3.0 = " + addResult);
                double subResult = remoteMath.subtract(5.0, 3.0);
                System.out.println("5.0 - 3.0 = " + subResult);            
            }catch(Exception e) {
                e.printStackTrace();
            }
                    
        }
        
    }

    结果如下:

    server端

     

    client端

    href:

    https://blog.csdn.net/qq_28081453/article/details/83279066#JAVA_RMI_21

    https://blog.csdn.net/xinghun_4/article/details/45787549

  • 相关阅读:
    TLPI读书笔记第15章-文件属性2
    TLPI读书笔记第15章-文件属性1
    Java异常及错误
    10055
    4月。
    JavaScript三种方法获取地址栏参数的方法
    页面预加载loading动画,再载入内容
    什么是可串行化MVCC
    简化版扫雷详细解
    论unity中UI工具与GUI函数
  • 原文地址:https://www.cnblogs.com/isme-zjh/p/11596429.html
Copyright © 2011-2022 走看看