大发体育娱乐在线-大发体育娱乐官方网站-大发体育娱乐登录网址
做最好的网站

RMI底层实现原理,Java学习随笔

来源:http://www.dfwstonefabricators.com 作者:编程应用 人气:84 发布时间:2019-10-08
摘要:PPT RMI(Remote MethodInvocation)远程方法注入,用来实现远程方法调用,是实现分布式技术的一种方法。RMI提供了客户辅助对象和服务辅助对象,为客户辅助对象创建了和服务对象相同的方

PPT

  RMI(Remote Method Invocation)远程方法注入,用来实现远程方法调用,是实现分布式技术的一种方法。RMI提供了客户辅助对象和服务辅助对象,为客户辅助对象创建了和服务对象相同的方法。其好处在于我们不必亲自写任何网络或I/O代码。客户程序在调用远程方法时就和调用本地方法一样(感觉上)。

RMI底层实现原理_腾讯视频

  RMI分为客户端和服务端。

1.背景介绍

  服务端的构建步骤如下:

远程方法调用顾名思义是一台机器上的程序调用另一台机器上的方法,其主要目的就是要使运行在不同的计算机中的对象之间的调用表现得像本地调用一样。从而可以实现分布式计算。

  1. 扩展远程接口Remote(),使其符合项目需求
  2. 实现被扩展的接口
  3. 为服务对象绑定唯一标识
  4. 利用rmic产生stub(客户辅助对象)和skeleton(服务辅助对象,高版本的Java不需要显示的skeleton对象,但服务端仍然有一些东西负责skeleton的行为)
  5. 启动rmireistry(rmireistry类似一个注册表,客户可以从中找到代理的位置)
  6. 启动远程服务

2、知识剖析

  客户端的构建步骤如下:

RMI应用程序通常包含两个单独的程序,一个服务器和一个客户机。典型的服务器程序创建一些远程对象,使对这些对象的引用可访问,并等待客户端调用这些对象上的方法。典型的客户端程序获取对服务器上的一个或多个远程对象的远程引用,然后调用它们上的方法。RMI提供了服务器和客户端来回传递信息的机制。这种应用有时被称为分布式对象应用。

  1. 根据注册的服务名查找服务得到实例的引用
  2. 调用服务的相关方法

3、常见问题

  通过一个具体的例子来说明:

stubs和skeletons起到了什么作用?

  假设现在有一个自动售货机公司(销售的主要产品为糖果),为了能尽可能的扩大受益,公司需要知道每个自动售货机的运行状态,从而在售货机中的糖果卖完时,可以及时的添加;如果哪台售货机坏掉了,可以及时的维修。但是售货机分布在一个城市的各个角落,如果用人工去一个个检查,效率十分低下。所以公司希望能有一个监控系统,这样就可以一个人坐在办公室,点点鼠标,就一切尽掌握啦~

RMIRegistry的作用是什么?

  这样一个系统的最大的特殊点在于我们都是远程监控这些自动售货机,所以我们需要用到RMI。

4、解决方案

  服务端程序:

存根 和骨架( Skeleton) 合在一起形成了 RMI 构架协议,负责网络通信相关的功能

  第一步、我们先定义一个对象,这个对象用来存储自动售货机的状态信息:自动售货机的地理位置、运行状态和剩余糖果数。远程方法的变量必须被打包并通过网络传输,这就需要通过序列化来完成。一般的原语类型、字符串和许多API中的内定类型(包括数据和集合),都不会存在问题,但是如果是自己定义的类,就需要实现Serializable接口。具体代码如下:

stub担当远程对象的客户本地代表或代理人角色,负责把要调用的远程对象方法的方法名及其参数编组打包,并将该包转发给远程对象所在的服务器

  等等,还要再这里插入一小段程序:枚举。用于定义机器的运行状态,具体代码如下:

图片 1图片 2

 1 package pattern.proxy;
 2 
 3 /**
 4  * 机器的运行状态
 5  * RUNNING:正常运行
 6  * BREAKDOWN:坏掉了
 7  * REPAIR:维修中
 8  * @author CS_Xiaochao
 9  *
10  */
11 public enum MachineState {
12     RUNNING, BREAKDOWN, REPAIR
13 }

调用stub的方法时将执行下列操作:

  

与服务端的skeleton建立socket连接 对参数进行打包

 1 package pattern.proxy;
 2 
 3 import java.io.Serializable;
 4 
 5 
 6 /**
 7  * 糖果机对象
 8  * 实现Serializable接口,因为需要在网络中进行传输
 9  * @author CS_Xiaochao
10  *
11  */
12 public class GumballMachineBean implements Serializable{
13 
14     /**
15      * 
16      */
17     private static final long serialVersionUID = -6395522198769463162L;
18     private String localtion; // 糖果机的位置
19     private int count; // 糖果的剩余数量
20     private MachineState state; // 机器的运行状态
21 
22     public GumballMachineBean() {
23         super();
24         // TODO Auto-generated constructor stub
25     }
26 
27     public GumballMachineBean(String localtion, int count, MachineState state) {
28         super();
29         this.localtion = localtion;
30         this.count = count;
31         this.state = state;
32     }
33 
34     public String getLocaltion() {
35         return localtion;
36     }
37 
38     public void setLocaltion(String localtion) {
39         this.localtion = localtion;
40     }
41 
42     public int getCount() {
43         return count;
44     }
45 
46     public void setCount(int count) {
47         this.count = count;
48     }
49 
50     public MachineState getState() {
51         return state;
52     }
53 
54     public void setState(MachineState state) {
55         this.state = state;
56     }
57 
58 }

等待方法调用结果

  第二步、扩展Remote接口,Remote接口中未声明任何方法,只是一个“标记”。所以我们需要扩展该接口来符合自己的项目需求。客户使用远程接口调用服务,也就是说客户会调用实现远程接口的Stub上的方法,而Stub底层用到了网络和I/O,所以存在一定的风险性,必须通过处理或声明远程异常来解决。具体源码如下:

返回值或返回的异常

 1 package pattern.proxy;
 2 
 3 import java.rmi.Remote;
 4 import java.rmi.RemoteException;
 5 
 6 /**
 7  * 扩展Remote接口,以适应糖果机
 8  * @author CS_Xiaochao
 9  *
10  */
11 public interface GumballMachineMonitor extends Remote {
12     /**
     * 获取糖果机状态信息
13      * 网络中存在许多不可测的因素,所以必须对其进行异常控制
14      * @return
15      * @throws RemoteException
16      */
17     GumballMachineBean getGumballMachineBean() throws RemoteException;  
18 
19 }

结果返回给客户端

   第三步、实现远程扩展的接口。在这里我另开了一个线程用于糖果机的售卖糖果的动作,在实际中糖果机在运行时,销售糖果和上传状态信息是两个独立的动作,所以用两个线程来分别负责比较合理。糖果贩卖源码如下:

Skeleton运行服务端上,接受来自stub对象的调用。当skeleton接收到来自stub对象的调用请求后,skeleton会作如下工作:

 1 package pattern.proxy;
 2 
 3 /**
 4  * 糖果销售动作对象
 5  * @author CS_Xiaochao
 6  *
 7  */
 8 public class SellCandy implements Runnable {
 9     
10     private GumballMachineBean gumballMachine;
11     
12     public SellCandy() {
13         // TODO Auto-generated constructor stub
14     }
15     
16     public SellCandy(GumballMachineBean gumballMachine) {
17         this.gumballMachine = gumballMachine;
18     }
19 
20     @Override
21     public void run() {
22         while(true){
23             if(gumballMachine.getState() == MachineState.RUNNING){
24                 int countTemp = gumballMachine.getCount();
25                 if(countTemp > 0){
26                     --countTemp;  //每次卖出一颗糖
27                 }
28                 gumballMachine.setCount(countTemp);
29             }else{
30                 break;  //说明糖果机坏掉,停止运作
31             }
32             //每隔两秒采集一次数据
33             try {
34                 Thread.sleep(2000);
35             } catch (InterruptedException e) {
36                 // TODO Auto-generated catch block
37                 e.printStackTrace();
38             } 
39         }
40 
41     }
42 
43 }

stub传来的参数

扩展接口实现类代码如下:

调用匹配实现上的方法

 1 package pattern.proxy;
 2 
 3 import java.net.MalformedURLException;
 4 import java.rmi.Naming;
 5 import java.rmi.RemoteException;
 6 import java.rmi.server.UnicastRemoteObject;
 7 
 8 public class GumballMachineMonitorImpl extends UnicastRemoteObject implements
 9         GumballMachineMonitor {
10 
11     /**
12      * 
13      */
14     private static final long serialVersionUID = -6922251119304810189L;
15     
16     private GumballMachineBean gumballMachine;
17 
18     /**
19      * 因为父类UnicastRemoteObject的构造方法抛出了RemoteException异常
20      * 所以子类必须手动定义构造方法并抛出该异常,以防止异常链断掉
21      * @throws RemoteException
22      */
23     protected GumballMachineMonitorImpl(GumballMachineBean gumballMachine) throws RemoteException {
24         super();    
25         this.gumballMachine = gumballMachine;
26     }
27 
28     @Override
29     public GumballMachineBean getGumballMachineBean() throws RemoteException {    
30         return gumballMachine;   //获取糖果机状态对象
31     }
32 
33     /**
34      * 驱动函数
35      * @param args
36      */
37     public static void main(String[] args) {
38         
39         try {
40             GumballMachineBean gmb = 
41                     new GumballMachineBean("湖北省武汉市武昌区八一路武汉大学文理学部自强超市旁", 1000, MachineState.RUNNING);    
42             new Thread(new SellCandy(gmb)).start();  //开始销售
43             GumballMachineMonitorImpl gmm1 = new GumballMachineMonitorImpl(gmb);
44             Naming.rebind("GumballMachineMonitor_1", gmm1); 
45             
46             GumballMachineBean gmb2 = 
47                     new GumballMachineBean("湖北省武汉市武昌区八一路武汉大学文理学部计算机学院软件工程国家重点实验室旁", 1000, MachineState.BREAKDOWN);
48             new Thread(new SellCandy(gmb2)).start();  //开始销售
49             GumballMachineMonitorImpl gmm2 = new GumballMachineMonitorImpl(gmb2);
50             Naming.rebind("GumballMachineMonitor_2", gmm2); 
51             
52             GumballMachineBean gmb3 = 
53                     new GumballMachineBean("湖北省武汉市武昌区八一路武汉大学文理学部樱花大道旁", 1000, MachineState.REPAIR);
54             new Thread(new SellCandy(gmb3)).start();  //开始销售
55             GumballMachineMonitorImpl gmm3 = new GumballMachineMonitorImpl(gmb3);
56             Naming.rebind("GumballMachineMonitor_3", gmm3);
57             
58         } catch (RemoteException e) {
59             // TODO Auto-generated catch block
60             e.printStackTrace();
61         } catch (MalformedURLException e) {
62             // TODO Auto-generated catch block
63             e.printStackTrace();
64         }
65 
66     }
67 
68 }

打包返回值或错误发送给stub对象

  为了让自己的对象成为远程服务对象,需要让对象具备一些远程的功能,所以要继承UnicastRemoteObject类,由于UnicastRemoteObject类的构造方法抛出了RemoteException异常,为了不让异常链断裂,导出类的构造方法也必须抛出该异常,而这需要手动的去实现。getGumballMachineBean方法用于获取糖果机的状态。在驱动类中,我们创建了三个糖果机,分别将其状态设为:RUNNING、BREAKDOWN和REPAIR。然后也给每个糖果机添加了一些糖果,并“摆放”在三个不同的地方,然后启动线程开始销售。

RMIRegistry的作用是什么?

  Naming.rebind("GumballMachineMonitor_1", gmm1); 

java.rmi.Naming类提供存储和获得“远程对象注册表”上远程对象的引用的方法。Naming类的每个方法都使用URL格式的参数,一个//ip:port/name可以唯一定位一个RMI服务器上的发布了的对象

  用于将当前服务对象注册到RMI registry的注册表中,因为客户端需要在注册表中查找来获取该远程对象,从而可以达到像操作本地对象一样操作远程对象的目的。

运行public static Remote lookup(String name)方法的时候,会生成一个stub对象,stub对象的构造参数需要一个RemoteRef类型的对象,

  第四步、通过rmic工具来产生Stub和Skeleton。rmic是JDK内的一个小工具,使用方法:通过命令行进入实现类class文件所在路径,执行命令“rmic 类全名(包名+类名,不需要‘.class’)”。执行完成之后,在类所在路径下会生成两个文件(类名+‘_Stub.class’ 和 类名+‘_Skeleton.class’),高版本的jdk一般只有"类名+‘_Stub.class’ "文件。

服务端运行public static void bind(String name, Remote obj)这个方法会传入一个实现类的引用作为第二参数,此时Naming会使用getClass来获取类的名字,并构建成stub

  第五步、执行rmiregistry。运用命令行,进入和上一步相同的路径下,执行命令"rmiregistry"。

从实体类中获取RemoteRef对象,这个对象中就封装了服务端的细节,包括服务端的hostname、port。RMIRegistry会储存stub对象,并提供给客户端使用

  第六步、新开一个命令行窗口,执行驱动程序来启动服务。

5、扩展思考

 

RMIRegistry 是必须的吗?

  客服端程序:

No,RMIRegistry起到的作用只是为了方便client获取到stub对象,如果还有其他的方法让client拿到stub就不需要RMIRegistry了,因为client一旦拿到了stub就不需要RMIRegistry了

  客户端程序(在本例中,也即是客户需要使用的监控窗口程序)需要查找RMI的注册表来获取远程服务对象所在地址,并获取到该服务对象,然后通过该对象来调用相关方法,对于客户而言,这一系列的操作就如同在操作本地服务对象一样,是透明的。唯一能感受到的就是网络的不可预测性导致的异常。

直接在client new一个stub对象不就可以了?

  第一步、根据注册的服务名查找服务得到实例的引用  

No,stub构造函数中需要一个RemoteRef对象,这个对象只能在server端获取。

  第二步、通过获取到的远程服务对象来调用相关的方法


  相关代码如下:

今天的分享就到这里啦,欢迎大家点赞、转发、留言、拍砖~

 1 package pattern.proxy;
 2 
 3 import java.net.MalformedURLException;
 4 import java.rmi.Naming;
 5 import java.rmi.NotBoundException;
 6 import java.rmi.RemoteException;
 7 
 8 public class ClientManageSystem {
 9 
10     /**
11      * @param args
12      */
13     public static void main(String[] args) {
14         try {
15             GumballMachineMonitor gmm1 = (GumballMachineMonitor) Naming.lookup("rmi://127.0.0.1/GumballMachineMonitor_1");
16             GumballMachineMonitor gmm2 = (GumballMachineMonitor) Naming.lookup("rmi://127.0.0.1/GumballMachineMonitor_2");
17             GumballMachineMonitor gmm3 = (GumballMachineMonitor) Naming.lookup("rmi://127.0.0.1/GumballMachineMonitor_3");
18             
19             while(true){
20                 System.out.println("GM_1_Location:" + gmm1.getGumballMachineBean().getLocaltion() + "n" +
21                                    "GM_1_State:" + gmm1.getGumballMachineBean().getState() + "n" +
22                                    "GM_1_Count:" + gmm1.getGumballMachineBean().getCount());
23                 
24                 System.out.println("GM_2_Location:" + gmm2.getGumballMachineBean().getLocaltion() + "n" +
25                            "GM_2_State:" + gmm2.getGumballMachineBean().getState() + "n" +
26                            "GM_2_Count:" + gmm2.getGumballMachineBean().getCount());
27                 
28                 System.out.println("GM_3_Location:" + gmm3.getGumballMachineBean().getLocaltion() + "n" +
29                            "GM_3_State:" + gmm3.getGumballMachineBean().getState() + "n" +
30                            "GM_3_Count:" + gmm3.getGumballMachineBean().getCount());
31                 
32                 //每隔五秒监控一次
33                 Thread.sleep(5000);
34             }
35             
36         } catch (MalformedURLException e) {
37             // TODO Auto-generated catch block
38             e.printStackTrace();
39         } catch (RemoteException e) {
40             // TODO Auto-generated catch block
41             e.printStackTrace();
42         } catch (NotBoundException e) {
43             // TODO Auto-generated catch block
44             e.printStackTrace();
45         } catch (InterruptedException e) {
46             // TODO Auto-generated catch block
47             e.printStackTrace();
48         }
49         
50         
51 
52     }
53 
54 }

技能树.IT修真院

  GumballMachineMonitor gmm1 = (GumballMachineMonitor) Naming.lookup("rmi://127.0.0.1/GumballMachineMonitor_1");
  客户端通过lookup方法到RMI registry中查找远程服务对象,RMI registry返回的是Stub对象,然后对stub进行反序列化。

“我们相信人人都可以成为一个工程师,现在开始,找个师兄,带你入门,掌控自己学习的节奏,学习的路上不再迷茫”。

  在使用RMI的过程中,最易犯的三个错误:(摘自Head First 设计模式)

这里是技能树.IT修真院,成千上万的师兄在这里找到了自己的学习路线,学习透明化,成长可见化,师兄1对1免费指导。快来与我一起学习吧~

  1. 忘了在启动远程服务之前先启动rmiregistry(要用Naming.rebind()注册服务,rmiregistry必须是运行的)。
  2. 忘了让变量和返回值的类型成为可序列化的类型(这种错误无法在编译期发现,只会在运行时发现)
  3. 忘了给客户提供stub类

我的邀请码:14229748,或者你可以直接点击此链接:

本文由大发体育娱乐在线发布于编程应用,转载请注明出处:RMI底层实现原理,Java学习随笔

关键词:

上一篇:没有了

下一篇:没有了

最火资讯