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的阻塞,反正是各种不服,终于慢慢理顺了思路,自己记录一下,防止遗忘. 环境 ...
随机推荐
- 动态SQL基本语句用法
1.if语句 如果empno不为空,则在WHERE参数后加上AND empno = #{empno},这里有1=1所以即使empno为null,WHERE后面也不会报错. 映射文件 <selec ...
- JavaDailyReports10_13
今天完成了课堂测试二的内容要求,开阔了编程的思路,学到了很多程序设计思想,为以后的学习提供了很多帮助. 明天开始继续完善四则运算的程序,并且开始JavaWeb的学习!
- 关于java方法重写
1.子类的方法与父类中的方法有相同的返回类型,相同的方法名称.相同的参数列表 2.子类方法的访问级别不能低于父类方法的访问级别 3.子类方法抛出的异常范围不能大于父类中方法抛出的异常范围
- 30天自制操作系统-day2
30天自制操作系统(linux)-day2 使用简单的汇编语言 首先Centos环境安装nasm,使用vim工具编辑一个os.asm文件,文件内容如下: DB 0xeb, 0x4e, 0x90, 0x ...
- INNER JOIN、LEFT JOIN、RIGHT JOIN、FULL JOIN 的使用和区别
INNER JOIN:如果表中有至少一个匹配,则返回行 LEFT JOIN:即使右表中没有匹配,也从左表返回所有的行 RIGHT JOIN:即使左表中没有匹配,也从右表返回所有的行 FULL JOIN ...
- TurtleBot3使用课程-第三节b(北京智能佳)
目录 1.使用TurtleBot3机械手运行SLAM 2 1.1 roscore运行 2 1.2 准备行动 3 1.3 运行SLAM节点 3 1.4 运行turtlebot3_teleop_key节点 ...
- cornerstoneTools 作用,用法,api使用心得
一.cornerstoneTools的用途 1.作用可以响应一些事件,例如鼠标按下的事件,鼠标滚轮的事件或按键或触摸事件 2.可以对视口进行缩放平移 3.可以在图像上绘制图形 4.可以在图像上绘制文本 ...
- Linux服务器初始化调优及安全加固
一,开启iptables 仅开放必要的SSH端口和监控端口 示例:SSH tcp 22snmpd udp 161nrpe tcp 5666本人公网IP全端口开放 二,除非特别熟悉selinux配置,否 ...
- 如何将项目推到github上面
1.先查看是否安装git. 2.如果没有安装git ,下载之后别忘了配置环境变量.(右击此电脑 --属性--高级系统设置--环境变量--系统变量中的path) 3.推代码 查看状态(可查可不查) gi ...
- 第8章 控制对象的访问(setter、getter、proxy)
目录 1. 使用getter和setter控制属性访问 1.1 定义getter与setter 通过对象字面量定义,或在ES6的class中定义 通过使用内置的Object.definePropert ...