这是《java核心技术》 第11章 分布式对象的笔记。

RMI基本原理

我们使用远程方法调用是希望达到这样的目的: 可以像调用本地方法一样去调用一个远程方法。 实现远程调用的方式是 为客户端和服务器端各自生成一个代理对象,客户端调用远程方法其实就是调用客户端代理对象的本地方法,在代理对象的本地方法中,会将参数编组以在网络传输,然后远程访问到 服务器端的代理对象,服务器端代理将根据客户端代理的具体请求信息来定位真实处理这个请求的服务器本地方法,并把最终的执行结果或者异常信息返回给客户端代理,最终返回给客户端本地方法,这一过程在RMI中对于程序员是透明的。下图中的存根(stub)为客户端代理,接受者为服务器端代理。

RMI注册表

现在有一个问题是客户端必须首先能够获得一个存根对象才可以发起远程调用。为了解决这个问题服务器端必须把至少一个服务器对象注册到RMI注册表中,以供客户端获取。假如我们有多个远程对象,我们当然可以在注册表中注册多个,但是更好的方式是通过已注册的远程对象来获取其他远程对象,后面后有例子。

1) By default, the RMI Registry uses port 1099.

2) Client and server (stubs, remote objects) communicate over random ports. The communcation is started via a socket factory which uses 0 as starting port, which means "use any port that's available" between 0 and 65535.

关于上图,默认情况下RMI 注册表使用端口1099监听,当客户端从注册表中获得了一个stub后,将使用在0到65535之间的任意端口来与服务器端代理进行通信。

基本范例:

server:server端有三个类:

1 import java.rmi.Remote;
2 import java.rmi.RemoteException;
3
4 /*
5 * 远程对象接口必须extends Remote,并且其中定义的远程方法必须抛出RemoteException
6 */
7 public interface Warehouse extends Remote{
8 double getPrice(String description) throws RemoteException;
9 }
 1 import java.rmi.RemoteException;
2 import java.rmi.server.UnicastRemoteObject;
3 import java.util.HashMap;
4 import java.util.Map;
5
6 /*
7 * 有两种方式来实现远程对象,一种是 extends UnicastRemoteObject,
8 * 另一种是 调用 UnicastRemoteObject.exportObject(this, 0);就像构造器中注释掉的那样
9 *
10 */
11 public class WarehouseImpl extends UnicastRemoteObject implements Warehouse {
12 private static final long serialVersionUID = -4941915511359795398L;
13 private Map<String, Double> map = new HashMap<>();
14
15 public WarehouseImpl() throws RemoteException{
16 //UnicastRemoteObject.exportObject(this, 0);
17 map.put("Apple", 11.1);
18 map.put("Orange", 22.2);
19 }
20
21 @Override
22 public double getPrice(String description) throws RemoteException {
23 Double price = map.get(description);
24 if(price == null){
25 throw new RuntimeException("can not find this stuff in the warehouse !!!");
26 }else{
27 return price;
28 }
29 }
30 }
 1 import java.rmi.RemoteException;
2
3 import javax.naming.Context;
4 import javax.naming.InitialContext;
5 import javax.naming.NamingException;
6
7 /*
8 * 在这里将远程对象绑定到注册表中,然后等待客户端连接
9 */
10 public class WarehouseServer {
11 public static void main(String[] args) throws RemoteException, NamingException {
12 Warehouse warehouse = new WarehouseImpl();
13
14 Context namingContext = new InitialContext();
15
16 //RMI的URL以rmi:开头,后接服务器名称和端口,然后是一个远程对象的唯一名字。rmi://xx.xx.xx.xx:99/central_warehouse
17 //默认主机名是localhost,端口是1099,可以省略,也就成了 rmi:central_warehouse
18 namingContext.bind("rmi://localhost:1088/central_warehouse", warehouse);
19
20 System.out.println("rmi server is ready, waiting for clients .....");
21 }
22 }

client端有两个文件,一个是和server中相同的Warehouse(这里没有包,如果有包的话,包名也要一样)

 1 import java.rmi.RemoteException;
2
3 import javax.naming.Context;
4 import javax.naming.InitialContext;
5 import javax.naming.NamingException;
6
7 public class Client {
8 public static void main(String[] args) throws NamingException, RemoteException {
9 Context namingContext = new InitialContext();
10 //我们这里所获得到的其实就是那个stub存根对象
11 Warehouse warehouse = (Warehouse)namingContext.lookup("rmi://localhost:1088/central_warehouse");
12 System.out.println("stub class : " + warehouse.getClass().getName() );
13 System.out.println("invoke remote method ......");
14 System.out.println(warehouse.getPrice("Apple")); //远程对象可以正常调用
15 System.out.println(warehouse.getPrice("Banana"));//远程对象将抛出异常
16 }
17 }

部署:

编译后的class文件的分布:

首先我们要启动rmi注册表来等待服务器端向其中绑定远程对象,默认监听1099端口,可以通过 rmiregistry port 来手动指定。 这里需要注意的是 rmi注册表必须能够访问到Warehouse.class 这样的远程方法双方都需访问的文件,访问的方法有:

1.最简单的方式就是在拥有这些文件的目录下启动rmiregistry

2.配置CLASSPATH包含这些文件

3.通过-Djava.rmi.server.codebase参数   在启动server的时候通过这个系统参数告诉注册表,参数的值是一个URL,例如 http://192.168.4.1/export/  或者 file:///home/admin/zhangcheng/server/ (值得注意的是url后面的分隔符是必须的), 但是如果我们希望通过这种方式让rmi加载文件的话,必须在启动的rmi注册标的时候加上 -J-Djava.rmi.server.useCodebaseOnly=false, 关于这个参数可以参考:        http://docs.oracle.com/javase/7/docs/technotes/guides/rmi/enhancements-7.html

下面将按照第三种方式启动,在rmi中启动rmiregistr, 在server中启动 服务器端,在client中发起客户端访问。

rmi注册表:

启动server端:

发起client访问:

下面我们对这个例子进行一些扩展,主要是看看RMI如何传递对象的。

1. 所有实现Remote的对象我们称之为 远程对象,在RMI中传递远程对象 其实就是传递了 由网络地址和方法唯一标识符组成的信息, 通过这些信息可以使客户端存根和服务器接受者之间建立起远程访问。(仅仅是我的理解)

2. 其他对象必须实现Serializable的对象通过序列化机制进行传输,例如上面例子中的String参数 就是一个Serializable对象。

对于warehouse例子的扩展用来展示如何传递远程对象(主要改变的代码使用红色标记出来)

server端:

 1 import java.rmi.Remote;
2 import java.rmi.RemoteException;
3
4 /*
5 * 远程对象接口必须extends Remote,并且其中定义的远程方法必须抛出RemoteException
6 */
7 public interface Warehouse extends Remote{
8 double getPrice(String description) throws RemoteException;
9 //增加了一个访问备份仓库的远程方法,通过这个远程方法,我们可以从一个远程对象中获得另一个远程对象。
10 Warehouse getBackup() throws RemoteException;
11 }
 1 import java.rmi.RemoteException;
2 import java.rmi.server.UnicastRemoteObject;
3 import java.util.HashMap;
4 import java.util.Map;
5
6 /*
7 * 有两种方式来实现远程对象,一种是 extends UnicastRemoteObject,
8 * 另一种是 调用 UnicastRemoteObject.exportObject(this, 0);就像构造器中注释掉的那样
9 *
10 */
11 public class WarehouseImpl extends UnicastRemoteObject implements Warehouse {
12 private static final long serialVersionUID = -4941915511359795398L;
13 private Map<String, Double> map = new HashMap<>();
14 private Warehouse backup ;
15
16 public WarehouseImpl(Warehouse backup) throws RemoteException{
17 //UnicastRemoteObject.exportObject(this, 0);
18 this.backup = backup;
19 }
20
21 public void add(String desc, Double price){
22 map.put(desc, price);
23 }
24
25 @Override
26 public double getPrice(String description) throws RemoteException {
27 Double price = map.get(description);
28 if(price == null){
29 throw new RuntimeException("can not find this stuff in the warehouse !!!");
30 }else{
31 return price;
32 }
33 }
34
35 @Override
36 public Warehouse getBackup() throws RemoteException {
37 return backup;
38 }
39 }
 1 import java.rmi.RemoteException;
2
3 import javax.naming.Context;
4 import javax.naming.InitialContext;
5 import javax.naming.NamingException;
6
7 /*
8 * 在这里将远程对象绑定到注册表中,然后等待客户端连接
9 */
10 public class WarehouseServer {
11 public static void main(String[] args) throws RemoteException, NamingException {
12 WarehouseImpl backup = new WarehouseImpl(null);
13 backup.add("Banana", 33.3);
14
15 //为绑定在注册表中的远程对象增加一个可获得的远程对象
16 WarehouseImpl warehouse = new WarehouseImpl(backup);
17 warehouse.add("Apple", 11.1);
18 warehouse.add("Orange", 22.2);
19
20 Context namingContext = new InitialContext();
21
22 //RMI的URL以rmi:开头,后接服务器名称和端口,然后是一个远程对象的唯一名字。rmi://xx.xx.xx.xx:99/central_warehouse
23 //默认主机名是localhost,端口是1099,可以省略,也就成了 rmi:central_warehouse
24 namingContext.bind("rmi://localhost:1088/central_warehouse", warehouse);
25
26 System.out.println("rmi server is ready, waiting for clients .....");
27 }
28 }

client端:

 1 import java.rmi.RemoteException;
2
3 import javax.naming.Context;
4 import javax.naming.InitialContext;
5 import javax.naming.NamingException;
6
7 public class Client {
8 public static void main(String[] args) throws NamingException, RemoteException {
9
10 Context namingContext = new InitialContext();
11 //我们这里所获得到的其实就是那个stub存根对象
12 Warehouse warehouse = (Warehouse)namingContext.lookup("rmi://localhost:1088/central_warehouse");
13 System.out.println("stub class : " + warehouse.getClass().getName() );
14 System.out.println("invoke remote method ......");
15 try {
16 System.out.println(warehouse.getPrice("Banana"));//远程对象将抛出异常
17 } catch (Exception e) {
18 System.out.println("occur exception when access the main warehouse, now access the backup warehouse !!!!");
19 //我们在客户端通过一个远程对象(warehouse)获得了另一个远程对象(backup),这个backup和warehouse一样,其实就是一个stub对象,我们可以通过它像调用本地方法一样调用远程方法。
20 Warehouse backup = warehouse.getBackup();
21 System.out.println(backup.getPrice("Banana"));
22 }
23 }
24 }

client调用结果:

关于RMI注册表还有另外一种方式,从实现上来看稍微简单一些,其实本质上是一样的,还是理解原理最重要,可以参考

http://lavasoft.blog.51cto.com/62575/91679/

其核心就是 通过 LocateRegistry.createRegistry(8888);  这种方式在server中用代码启动rmi注册表。

另外在《java核心技术》 中还讲解了 动态加载类 延迟启动等知识,这些知识牵扯到JVM动态加载类文件等安全机制,我自己照葫芦画瓢的确实现了,但是不是太明白,就不记录了,等搞明白 JVM的安全机制再回头来看看吧!!!

使用spring可以非常容易集成RMI,但是有一点要注意: Spring的RMI server是无法与 原生的RMI client通信的,这在不是使用Spring实现的项目中会是一个问题。

每一个不能起舞的日子,都是对生命的一种辜负, 翻滚吧,少年!!!

RMI笔记的更多相关文章

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

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

  2. Neo4j图数据库管理系统开发笔记之三:构建安全的RMI Service(Server)

    RMI Server(服务端)主要包括以下功能:远程用户权限验证管理.远程服务接口实现类.Neo4j实体映射转换等.项目目录结构如下图所示: 3.2.1 远程用户权限验证管理 3.2.1.1 用户权限 ...

  3. RMI 使用笔记

    Java 远程方法调用,即 Java RMI( Java Remote Method Invocation ) .顾名思义,可以使客户机上运行的程序能够调用远程服务器上的对象(方法). 下面主要介绍一 ...

  4. 关于JDK高版本下RMI、LDAP+JNDI bypass的一点笔记

    1.关于RMI 只启用RMI服务时,这时候RMI客户端能够去打服务端,有两种情况,第一种就是利用服务端本地的gadget,具体要看服务端pom.xml文件 比如yso中yso工具中已经集合了很多gad ...

  5. Java RMI 学习笔记

    概况 功能:提供了客户辅助对象和服务辅助对象,为客户辅助对象创建和服务辅助对象形同的方法. 优点:客户不必写任何网络或I/O代码,调用远程方法就和运行在客户自己的本地JVM上对对象进行的正常方法一样. ...

  6. Linux 学习笔记

    Linux学习笔记 请切换web视图查看,表格比较大,方法:视图>>web板式视图 博客园不能粘贴图片吗 http://wenku.baidu.com/view/bda1c3067fd53 ...

  7. Java的RMI远程方法调用实现和应用

    最近在学习Dubbo,RMI是很重要的底层机制,RMI(Remote Method Invocation)远程方法调用是一种计算机之间利用远程对象互相调用实现双方通讯的一种通讯机制.使用这种机制,某一 ...

  8. Docker 学习笔记(CentOS 7.1)

    基本概念 Docker 包括三个基本概念 镜像(Image) 容器(Container) 仓库(Repository)理解了这三个概念,就理解了 Docker 的整个生命周期. Docker 镜像 D ...

  9. kubernetes 内网节点部署笔记(一)

    在Centos7上部署kubernetes时,碰到很多坑,特别在摸拟在内网部署时,有来自GFW的障碍,有来自Firewalld的阻塞,反正是各种不服,终于慢慢理顺了思路,自己记录一下,防止遗忘. 环境 ...

随机推荐

  1. 运行jar提示“没有主清单属性”的解决方法

    以下记录的是我export jar包后运行遇到问题的解决方法,如有错误,欢迎批评指正. 1.运行导出jar包,提示"没有主清单属性" 2.回想自己导出jar的操作是否有误,重新ex ...

  2. 【代码周边】-GitHub笔记

    ------------恢复内容开始------------ 程序员的宝库github是个好东西,其中开源的项目足够我们的使用,但是如何去精准的获取我们的项目是很多初学者的问题.特别是英语不好的我,一 ...

  3. [leetcode]200. Number of Islands岛屿数量

    dfs的第一题 被边界和0包围的1才是岛屿,问题就是分理出连续的1 思路是遍历数组数岛屿,dfs四个方向,遇到1后把周围连续的1置零,代表一个岛屿. /* 思路是:遍历二维数组,遇到1就把周围连续的1 ...

  4. [Deep Learning] 神经网络编程基础 (Basics of Neural Network Programming) - 逻辑回归-梯度下降-计算图

    在神经网络中,假如有m个训练集,我们想把他们加入训练,第一个想到得就是用一个for循环来遍历训练集,从而开始训练.但是在神经网络中,我们换一个计算方法,这就是 前向传播和反向传播. 对于逻辑回归,就是 ...

  5. 深入理解Redis系列之持久化

    redis持久化配置 redis.conf // RDB配置 save 900 1 save 300 10 save 60 10000 // AOF配置 appendonly yes //AOF三种同 ...

  6. 关于领域驱动架构DDD思考

    一个高大上的概念领域驱动架构就这样展开. 开发了多年的软件,一直以来的习惯是拿到产品的需求 对照UI的图纸然后就干干干 碰到问题大不了找人沟通再次定义问题,最后交付.其实最后也能把一件事情完成 但如果 ...

  7. Redis基础篇(八)数据分片

    现在有一个场景:要用Redis保存5000万个键值对,每个键值对大约是512B,要怎么部署Redis服务呢? 第一个方案,也是最容易想到的,需要保存5000万个键值对,每个键值对约为512B,一共需要 ...

  8. 有序矩阵中第k小元素

    有序矩阵中第k小元素 题目: 给定一个 n x n 矩阵,其中每行和每列元素均按升序排序,找到矩阵中第 k 小的元素. 请注意,它是排序后的第 k 小元素,而不是第 k 个不同的元素. 看到有序就会想 ...

  9. Modbus 仿真测试工具 Mod_Rssim 详细图文教程

    Mod_RSsim是一款轻量级的Modbus从机模拟器,它可以模拟ModBusTCP和ModBusRTU的从机,能够同时模拟254个被控站,软件使用简单方便,可以满足一般的主机调试. 官方网站:www ...

  10. 数组的方法some和includes

    some() 方法用于检测数组中的元素是否满足指定条件(函数提供). some() 方法会依次执行数组的每个元素: 如果有一个元素满足条件,则表达式返回true , 剩余的元素不会再执行检测. 如果没 ...