这是《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. 编程方式实现MySQL批量导入sql文件

    有时候需要在本地导入一些stage环境的数据到本地mysql,面对1000+的sql文件(包含表结构和数据,放在同一个文件夹下),使用navicat一个一个导入sql文件显然有点太慢了,于是考虑使用s ...

  2. Java学习_Java核心类

    字符串和编码 字符串在String内部是通过一个char[]数组表示的,因此,可以按下面的写法: String s2 = new String(new char[] {'H', 'e', 'l', ' ...

  3. GraduateDesign-初试APP编写(去除虚拟按键和禁止状态栏下拉)

    为了毕设的要求,需要在Android系统上运行一个app来控制硬件,今天开始这个app的编写. 首先,我们的系统将只运行这个app,也就是我们不需要状态栏,虚拟按键等. 故这里将app设置为全屏模式. ...

  4. 已加载"C:\Windows\SysWOW64\msvcp120d.dll".无法查找或打开 PDB 文件.

    已加载"C:\Windows\SysWOW64\msvcp120d.dll".无法查找或打开 PDB 文件. 今天使用vs2013遇到了这样的问题. 解决方案: 点调试. 然后选项 ...

  5. hashmap简单实现

    p.p1 { margin: 0; font: 11px Monaco } p.p2 { margin: 0; font: 11px Monaco; min-height: 15px } p.p3 { ...

  6. LockSupport的深入浅出

    public static void main(String[] args)throws Exception { final Object obj = new Object(); Thread A = ...

  7. 转 linux终端 字符界面 显示乱码 .

    方法一:配置SSH工具 SecureCRT中文版配置 [全局选项]→[默认会话]→[编辑默认设置]→[终端]→[外观]→[字体]→[新宋体 10pt CHINESE_GB2312]→[字符编码 UTF ...

  8. Mysql-Incorrect string value

    [问题描述] com.mysql.jdbc.MysqlDataTruncation: Data truncation: Incorrect string value: '\xF0\x9F\x8E\x8 ...

  9. sublime python 去掉单行超出字数的白色框框 (E501)

    方法一 E501错误:行过长 (大于79个字符),在配置文件里设置 忽略E501错误即可 首选项-->Package Settings-->Anaconda-->Settings - ...

  10. 【Java基础】面向对象下

    面向对象下 这一章主要涉及其他关键字,包括 this.super.static.final.abstract.interface.package.import 等. static 在 Java 类中, ...