RMI笔记
这是《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笔记的更多相关文章
- Java学习笔记(十六)——Java RMI
[前面的话] 最近过的好舒服,每天过的感觉很充实,一些生活和工作的技巧注意了就会发现,其实生活也是可以过的如此的有滋有味,满足现在的状况,并且感觉很幸福. 学习java RMI的原因是最近在使用dub ...
- Neo4j图数据库管理系统开发笔记之三:构建安全的RMI Service(Server)
RMI Server(服务端)主要包括以下功能:远程用户权限验证管理.远程服务接口实现类.Neo4j实体映射转换等.项目目录结构如下图所示: 3.2.1 远程用户权限验证管理 3.2.1.1 用户权限 ...
- RMI 使用笔记
Java 远程方法调用,即 Java RMI( Java Remote Method Invocation ) .顾名思义,可以使客户机上运行的程序能够调用远程服务器上的对象(方法). 下面主要介绍一 ...
- 关于JDK高版本下RMI、LDAP+JNDI bypass的一点笔记
1.关于RMI 只启用RMI服务时,这时候RMI客户端能够去打服务端,有两种情况,第一种就是利用服务端本地的gadget,具体要看服务端pom.xml文件 比如yso中yso工具中已经集合了很多gad ...
- Java RMI 学习笔记
概况 功能:提供了客户辅助对象和服务辅助对象,为客户辅助对象创建和服务辅助对象形同的方法. 优点:客户不必写任何网络或I/O代码,调用远程方法就和运行在客户自己的本地JVM上对对象进行的正常方法一样. ...
- Linux 学习笔记
Linux学习笔记 请切换web视图查看,表格比较大,方法:视图>>web板式视图 博客园不能粘贴图片吗 http://wenku.baidu.com/view/bda1c3067fd53 ...
- Java的RMI远程方法调用实现和应用
最近在学习Dubbo,RMI是很重要的底层机制,RMI(Remote Method Invocation)远程方法调用是一种计算机之间利用远程对象互相调用实现双方通讯的一种通讯机制.使用这种机制,某一 ...
- Docker 学习笔记(CentOS 7.1)
基本概念 Docker 包括三个基本概念 镜像(Image) 容器(Container) 仓库(Repository)理解了这三个概念,就理解了 Docker 的整个生命周期. Docker 镜像 D ...
- kubernetes 内网节点部署笔记(一)
在Centos7上部署kubernetes时,碰到很多坑,特别在摸拟在内网部署时,有来自GFW的障碍,有来自Firewalld的阻塞,反正是各种不服,终于慢慢理顺了思路,自己记录一下,防止遗忘. 环境 ...
随机推荐
- [leetcode]304Range Sum Query 2D - Immutable动态规划计算二维数组中子数组的sum
303一维数组的升级版,方法就是用二维数组res存下从(0,0)到当前位置的sum,存的方法是动态规划,看着二维数组画圈比较好搞清楚其中的加减法 算子数组的sum的时候也是和存差不多的逻辑,就是某一部 ...
- salesforce零基础学习(九十九)Salesforce Data Skew(数据倾斜)
本篇参考: https://developer.salesforce.com/blogs/engineering/2013/04/managing-lookup-skew-to-avoid-recor ...
- 动态REM
什么是rem? rem是相对于根元素html字体大小来计算的,即( 1rem = html字体大小 ) rem和em区别? rem:(root em,根em)根元素的fort-size的大小计算em: ...
- mysql 双主复制 centos7
mysql 安装请看:http://www.cnblogs.com/leohe/p/6839705.html 双主复制设置 1. 两台虚拟机,都是 centos7 主: 10.1.1.115 从: 1 ...
- 浅入kubernetes(2):Kubernetes 的组成
目录 说明 Kubernetes集群的组成 What are containerized applications? What are Kubernetes containers? What are ...
- Alpha冲刺——汇总博客
一.代码规范与计划 代码规范与计划 二.10篇冲刺随笔 冲刺随笔--Day1 冲刺随笔--Day2 冲刺随笔--Day3 冲刺随笔--Day4 冲刺随笔--Day5 冲刺随笔--Day6 冲刺随笔-- ...
- windows7 错误0xc00000ba;无法进入系统;
事件背景:电脑windows7 错误0xc00000ba无法进系统:无法进入安全模式:无法进入最后一次正确配置: 事件处理:提示系统文件丢失,注册表异常:知乎有人提及更换WS2_32.DLL; 使用P ...
- 用python做youtube自动化下载器 思路
目录 0. 思路 1.准备 i.savfrom.net 2. 探索并规划获取方式 i.总览 ii. 获取该网页取到下载url的请求 iii. 在本地获取请求 iv.解析请求结果 v.解析解密后的结果 ...
- Synchronized 精讲
1.简介 1.1 作用 在并发场景中,保证同一时刻只有一个线程对有并发隐患的代码进行操作 1.2 错误案例 需求:两个线程对 count 变量进行200000次循环增加,预期结果是400000次 pu ...
- 【SpringMVC】SpringMVC 入门
SpringMVC 入门 文章源码 SpringMVC 基本概念 在 JavaEE 开发中,几乎全都是基于 B/S 架构的开发.在 B/S 架构中,系统标准的三层架构包括:表现层.业务层.持久层. 表 ...