RMI原理揭秘之远程对象
讨论开始之前,我们先看看网上的一个例子,这个例子我腾抄了一分,没有用链接的方式,只是为了让大家看得方便,如有侵权,我立马***。
定义远程接口:
- 123456
packagecom.guojje;importjava.rmi.Remote;importjava.rmi.RemoteException;publicinterfaceIHelloextendsRemote {publicinthelloWorld()throwsRemoteException;}
3. 定义实现类:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
package com.guojje;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
public class Hello extends UnicastRemoteObject implements IHello {
private static final long serialVersionUID = 1L;
private int index = 0;
protected Hello() throws RemoteException {
}
@Override
public int helloWorld(){
System.out.println("Hello!");
return ++index;
}
} |
4.服务端:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
package com.guojje;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class HelloServer {
public static void main(String args[]) {
try {
IHello rhello = new Hello();
Registry registry = LocateRegistry.createRegistry(8888);
registry.bind("test", rhello);
System.out.println("Remote Hello Object is bound sucessfully!");
} catch (Exception e) {
e.printStackTrace();
}
}
} |
5.客户端:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
package com.guojje;
import java.rmi.Naming;
public class HelloClient {
public static void main(String args[]) {
try {
for (int i = 0; i < 5; i++) {
IHello rhello = (IHello) Naming
.lookup("rmi://localhost:8888/test");
System.out.println(rhello.helloWorld());
}
} catch (Exception e) {
e.printStackTrace();
}
}
} |
6.输出结果:
1)服务端输出:
Remote Hello Object is bound sucessfully!
Hello!
Hello!
Hello!
Hello!
Hello!
2)客户端输出:
0
1
2
3
4
7.把实现类更改为不继承UnicastRemoteObject基类
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
package com.guojje;
import java.io.Serializable;
import java.rmi.RemoteException;
public class Hello implements IHello,Serializable {
private static final long serialVersionUID = 1L;
private int index = 0;
protected Hello() throws RemoteException {
}
@Override
public int helloWorld(){
System.out.println("Hello!");
return ++index;
}
} |
8.输出结果:
1)服务端输出:
Remote Hello Object is bound sucessfully!
2)客户端输出:
Hello!
1
Hello!
1
Hello!
1
Hello!
1
Hello!
1
这两个用例的输出结果来看,前一个用例index计数器一直在增加,且Hello!输出在服务端。这说明
helloWorld方法执行是在服务端,客户端的每一次对象方法调用,都是对服务端对象的调用。
而后一个用例就不同了。helloWorld方法执行是在客户端,且每次lookup出来的都是新的对象。
我们看一下lookup出来的对象类型:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
package com.guojje;
import java.rmi.Naming;
public class HelloClient {
public static void main(String args[]) {
try {
for (int i = 0; i < 5; i++) {
IHello rhello = (IHello) Naming
.lookup("rmi://localhost:8888/test");
System.out.println(rhello.getClass());
System.out.println(rhello.helloWorld());
}
} catch (Exception e) {
e.printStackTrace();
}
}
} |
实现类继承UnicastRemoteObject时,lookup出来的对象类型是$Proxy0,而不继承UnicastRemoteObject的类,对象类型是com.guojje.Hello。
我们把继承UnicastRemoteObject类的对象叫做远程对象,我们lookup出来的对象,只是该远程对象的存根(Stub)对象,而远程对象永远在远方。客户端每一次的方法调用,最后都是仅有的那一个远程对象的方法调用。
没有继承UnicastRemoteObject类的对象,同样可以bind到Registry,但lookup出来了对象却是远程对象
经过序列化,然后到客户端反序列化出来的新的对象,后续的方法调用与远程对象再无关系。
那UnicastRemoteObject类的继承是如何影响这些的呢? 我们来探索一下。
|
1
2
3
4
5
6
7
8
9
10
|
package com.guojje;
public class HelloServer {
public static void main(String args[]) {
try {
new Hello();
} catch (Exception e) {
e.printStackTrace();
}
}
} |
仅仅new一个远程对象,运行这个程序,我们就发现进程不会退出。它hang在哪儿呢?
jstack查看线程栈,发现有SocketAccept在监听:

的确启动了一个监听端口。ServerSocket类上添加debug.得到调用栈如下:

UnicastRemoteObject基类的构造方法将远程对象发布到一个随机端口上,当然端口也可以指定。
|
1
2
3
4
5
|
protected UnicastRemoteObject(int port) throws RemoteException
{
this.port = port;
exportObject((Remote) this, port);
}
|
|
1
2
3
4
5
|
public static Remote exportObject(Remote obj, int port)
throws RemoteException
{
return exportObject(obj, new UnicastServerRef(port));
}
|
|
1
2
3
4
5
6
7
8
9
10
11
12
|
/**
* Exports the specified object using the specified server ref.
*/
private static Remote exportObject(Remote obj, UnicastServerRef sref)
throws RemoteException
{
// if obj extends UnicastRemoteObject, set its ref.
if (obj instanceof UnicastRemoteObject) {
((UnicastRemoteObject) obj).ref = sref;
}
return sref.exportObject(obj, null, false);
}
|
迎来一个重要的方法(UnicastServerRef.java):
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
/**
* Export this object, create the skeleton and stubs for this
* dispatcher. Create a stub based on the type of the impl,
* initialize it with the appropriate remote reference. Create the
* target defined by the impl, dispatcher (this) and stub.
* Export that target via the Ref.
*/
public Remote exportObject(Remote impl, Object data,
boolean permanent)
throws RemoteException
{
Class implClass = impl.getClass();
Remote stub;
try {
stub = Util.createProxy(implClass, getClientRef(), forceStubUse);
} catch (IllegalArgumentException e) {
throw new ExportException(
"remote object implements illegal remote interface", e);
}
if (stub instanceof RemoteStub) {
setSkeleton(impl);
}
Target target =
new Target(impl, this, stub, ref.getObjID(), permanent);
ref.exportObject(target);
hashToMethod_Map = hashToMethod_Maps.get(implClass);
return stub;
}
|
这里的stub变量就是我们lookup出来的远程对象的存根对象。而target保留了远程对象的信息集合:
对象,存根,objId等。
接着看TCPTransport.java的exportObject方法:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
/**
* Export the object so that it can accept incoming calls.
*/
public void exportObject(Target target) throws RemoteException {
/*
* Ensure that a server socket is listening, and count this
* export while synchronized to prevent the server socket from
* being closed due to concurrent unexports.
*/
synchronized (this) {
listen();
exportCount++;
}
/*
* Try to add the Target to the exported object table; keep
* counting this export (to keep server socket open) only if
* that succeeds.
*/
boolean ok = false;
try {
super.exportObject(target);
ok = true;
} finally {
if (!ok) {
synchronized (this) {
decrementExportCount();
}
}
}
}
|
listen()启动监听。这里对TcpTransport加了同步,防止多个线程同时执行,同时也防止同一端口启动多次。
一次TcpTransport会有一个监听端口,而一个端口上可能会发部多个远程对象。exportCount计录该TcpTransport发布了多少个对象。
|
1
|
super.exportObject(target);
|
|
1
2
3
4
5
6
7
|
/** * Export the object so that it can accept incoming calls.
*/
public void exportObject(Target target) throws RemoteException {
target.setExportedTransport(this);
ObjectTable.putTarget(target);
}
|
ObjectTable为静态方法调用,那么所有的远程对象信息(target)都会放到这个对象表中。我们这个target包含了远程对象几乎所有的信息,找到他,就找到远程对象的存根对象。
远程对象的发布,先说这么多。我们再看一下lookup是如何找到远程对象的存根的.
当然先从远程对象的bind说起:

RegistryImpl.java:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
/**
* Binds the name to the specified remote object.
* @exception RemoteException If remote operation failed.
* @exception AlreadyBoundException If name is already bound.
*/
public void bind(String name, Remote obj)
throws RemoteException, AlreadyBoundException, AccessException
{
checkAccess("Registry.bind");
synchronized (bindings) {
Remote curr = bindings.get(name);
if (curr != null)
throw new AlreadyBoundException(name);
bindings.put(name, obj);
}
}
|
bindings里放得确实是远程对象本身,而不是他的存根。这是怎么回事?继续追究lookup方法。

RegistryImpl_Skel类是rmic生成所有没有源代码,我们只能从反编译的代码中查看一点信息:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
case 2: // lookup(String)
{
java.lang.String $param_String_1;
try {
java.io.ObjectInput in = call.getInputStream();
$param_String_1 = (java.lang.String) in.readObject();
} catch (java.io.IOException e) {
throw new java.rmi.UnmarshalException("error unmarshalling arguments", e);
} catch (java.lang.ClassNotFoundException e) {
throw new java.rmi.UnmarshalException("error unmarshalling arguments", e);
} finally {
call.releaseInputStream();
}
java.rmi.Remote $result = server.lookup($param_String_1);
try {
java.io.ObjectOutput out = call.getResultStream(true);
out.writeObject($result);
} catch (java.io.IOException e) {
throw new java.rmi.MarshalException("error marshalling return", e);
}
break;
}
|
从网络流中读取第一个参数必须是lookup的字符串参数。然后调用服务端RegistryImpl对象lookup
方法,该方法返回的的确是远程对象,而非远程对象的存根呀?
问题的原因在于下面的序列化过程,继续看调用栈:
看一个重要方法MarshalOutputStream.java
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
/** * Checks for objects that are instances of java.rmi.Remote
* that need to be serialized as proxy objects.
*/
protected final Object replaceObject(Object obj) throws IOException {
if ((obj instanceof Remote) && !(obj instanceof RemoteStub)) {
Target target = ObjectTable.getTarget((Remote) obj);
if (target != null) {
return target.getStub();
}
}
return obj;
}
|
如果对象是java.rmi.Remote实例,则向对象表中找到该对象的Target,如果存在,则返回其存根对象。
所以返回服务端的变成了远程对象的存根。先到此,后续再探索其方法调用,安全等相关问题。
补充:
其实Registry的实现类RegistryImpl也是个远程对象,这里registry.bind却没有进行远程调用。这是因为我是用LocateRegistry.createRegistry(8888)创建的远程对象,而不是通过LocateRegistry.getRegistry(8888)获取的远程对象,而getRegistry返回的是RegistryImpl的Stub.仅此而已。
另外一次RMI调用,要创建两个连接,一个连接面向注册端口,即上面的8888端口。另一个面向服务端口。在这里这个服务端口是随机的。最后绑定在UnicastServerRef对象中,序列化到客户端。
本文转自 anranran 51CTO博客,原文链接:http://blog.51cto.com/guojuanjun/1423392
RMI原理揭秘之远程对象的更多相关文章
- RMI原理及简单示例
分布式对象 在学习 RMI 之前,先来分布式对象(Distributed Object):分布式对象是指一个对象可以被远程系统所调用.对于 Java 而言,即对象不仅可以被同一虚拟机中的其他客户程序( ...
- java RMI原理详解
java本身提供了一种RPC框架——RMI(即Remote Method Invoke 远程方法调用),在编写一个接口需要作为远程调用时,都需要继承了Remote,Remote 接口用于标识其方法可以 ...
- RMI原理
一.分布式对象 在学习 RMI 之前,先来分布式对象(Distributed Object):分布式对象是指一个对象可以被远程系统所调用.对于 Java 而言,即对象不仅可以被同一虚拟机中的其他客户程 ...
- RMI原理及简单demo
1 简介 RMI是远程方法调用的简称,它能够帮助我们查找并执行远程对象的方法.通俗地说,远程调用就象将一个class放在A机器上,然后在B机器中调用这个class的方法. 2 概念 其他机器需要调用的 ...
- 使用 RMI 实现方法的远程调用
RMI 介绍 RMI 指的是远程方法调用 (Remote Method Invocation).它是一种机制,能够让在某个 Java虚拟机上的对象调用另一个 Java 虚拟机中的对象上的方法.可以用此 ...
- .Net Remoting 调用远程对象
根据需求,我们的系统必须以C/S方式构建,而且是三层架构,这样一来,就出现了服务器端和客户端通信的问题. 为了解决双方的通信问题,还要考虑效率.性能等方面,经过分析.试验,我们根据效率.移植.开发难易 ...
- (译) Angular运行原理揭秘 Part 1
当你用AngularJS写的应用越多, 你会越发的觉得它相当神奇. 之前我用AngularJS实现了相当多酷炫的效果, 所以我决定去看看它的源码, 我想这样也许我能知道它的原理. 下面是我从源码中找到 ...
- 【转】Angular运行原理揭秘 Part 1
当你用AngularJS写的应用越多, 你会越发的觉得它相当神奇. 之前我用AngularJS实现了相当多酷炫的效果, 所以我决定去看看它的源码, 我想这样也许我能知道它的原理. 下面是我从源码中找到 ...
- VisualVM 使用 service:jmx:rmi:///...无法连接linux远程服务器
VisualVM 无法使用 service:jmx:rmi:///jndi/rmi:///jmxrmi 连接到 关闭远程机器的防火墙即可:service iptables stop 不关闭防火墙的解决 ...
随机推荐
- 《综合》MMM集群
<综合>MMM集群 部署集群基础环境 MySQL-MMM架构部署 MySQL-MMM架构使用 1 部署集群基础环境 1.1 问题 本案例要求为MySQL集群准备基础环境,完成以下任务操作: ...
- 以太坊代币,USDT归集流程图
1.用户充值 600 代币 (网站小助手会及时监听到用户的充值信息,并回调给用户填写的URL地址) 2.会员转账600代币,属于大额转账,开始触发系统的自动汇集程序 注:这里系统检测到会员的地址并没有 ...
- 数据科学 R语言速成
文章更新于:2020-03-07 按照惯例,需要的文件附上链接放在文首: 文件名:R-3.6.2-win.exe 文件大小:82.4M 下载链接:https://www.lanzous.com/i9c ...
- VXLAN 基础教程:VXLAN 协议原理介绍
VXLAN(Virtual eXtensible Local Area Network,虚拟可扩展局域网),是一种虚拟化隧道通信技术.它是一种 Overlay(覆盖网络)技术,通过三层的网络来搭建虚拟 ...
- go 反射包
一.什么是反射? 反射是用程序检查其所拥有的结构,尤其是类型的一种能力: 二.Printf Printf 的函数声明为: func Printf(format string, args ... int ...
- Jmeter常用元件
1.测试计划:测试元件的容器,相当于一个项目名称 线程组: 2.监听器:负责收集测试结果,同时也被告知了结果显示的方式 (1)查看结果树:看具体某个请求——请求响应,结果明细 (2)聚合报告:汇总报 ...
- 使用StopWatch类来计时 (perf4j-0.9.16.jar 包里的类)
public class StopWatch { static public int AN_HOUR = 60 * 60 * 1000; static public int A_MINUTE = 60 ...
- C++_编程前奏
计算机硬件系统 计算机操作系统的五大组成部分 计算机操作系统的组成部分 构成 控制器 指令寄存器(IR)/程序计数器(PC)/操作控制器(OC) 运算器 算数逻辑单元/累加器/状态寄存器/通用寄存器 ...
- 【论文笔记】张航和李沐等提出:ResNeSt: Split-Attention Networks(ResNet改进版本)
github地址:https://github.com/zhanghang1989/ResNeSt 论文地址:https://hangzhang.org/files/resnest.pdf 核心就是: ...
- 落谷 P1734 最大约数和
题目描述 选取和不超过S的若干个不同的正整数,使得所有数的约数(不含它本身)之和最大. 输入格式 输入一个正整数S. 输出格式 输出最大的约数之和. 输入输出样例 输入 #1复制 11 输出 #1复制 ...