RMI原理解析

之前看过一个RMI的简单示例。这篇文章简单的分析一下RMI的原理。

RMI应用程序的体系结构

上一个例子中,我们编写了两个程序,一个服务端(Server)和一个客户端(Client)。

  • 在Server内创建一个远程对象(CalcServiceImpl),并生成存根,注册到Registry中。
  • 客户端请求远程对象,并调用对象的方法。

RMI架构图

  • Transport Layer:RMI传输层实现网络连接与通信的数据传递。
  • Stub:存根是远程对象在客户端的代理,它驻留在客户端系统中,充当客户端程序的网关。
  • Skeleton:骨架驻留在服务端系统中,Stub通过Skeleton传递参数给远程服务对象。
  • RRL(Remote Reference Layer):RRL是负责管理客户端到远程对象的引用。

RMI的工作原理

  • 当客户端调用远程对象时,它会被Stub接收,并把请求传递给RRL
  • 当客户端RRL接收到请求后,它会调用RemoteRef的invoke()方法。最终这个请求会被传递到服务端的RRL中。
  • 服务端RRL将请求发给Skeleton(服务端的一个代理类),最终调用服务端中的服务对象方法。
  • 服务方法返回的结果按原路返回给客户端。

Marshalling与Unmarshalling

当客户端调用远程对象的方法时,可能需要传递一些参数。这些参数将会被包装在消息中通过网络发送给服务端。这些参数会被序列化成二进制数据流传输到服务端,这个过程也被叫做Marshalling;相反服务端需要把二进制数据流反序列化还原,这个过程也被叫做Unmarshalling

事实上这两个过程使用的使我们非常熟悉的ObjectOutputStream和ObjectInputStream。只不过,RMI进一步包装了这两个类:

Output

Input

这两个过程之间涉及到的消息通信协议,也被称为JRMP协议,Java Remote Message Protocol

RmiRegistry的作用

RmiRegistry用于管理所有的远程服务。

服务器端创建对象时,都会使用Registry.bindRegistry.rebind方法向RmiRegistry注册对象。每个服务对象都对应着一个唯一的服务名。

客户端调用远程对象时,客户端需要远程对象的引用。客户端可以根据服务名调用Registry.lookup方法从RmiRegistry中获取对象。

RmiRegistry

分析Stub中的源代码

在上面的体系架构中,提到了Stub与Skeleton,但是在程序中我们并没有看到这两个类,那是因为从Java 5.0之后,Java使用java.lang.reflect.Proxy为我们动态生成了这两个类。

service.getClass()打印得到的是:class com.sun.proxy.$Proxy0

在Java 5.0之前需要我们使用rmic命令根据服务实现类编译生成存根。

1
2
# -keep选项:保留生成的源代码
rmic -keep cn.hff.service.CalcServiceImpl

RMIC

生成源码的Stub对象如下:

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
// Stub class generated by rmic, do not edit.
// Contents subject to change without notice.

package cn.hff.service;

public final class CalcServiceImpl_Stub extends java.rmi.server.RemoteStub implements cn.hff.service.ICalcService {
private static final long serialVersionUID = 2;

private static java.lang.reflect.Method $method_add_0;
private static java.lang.reflect.Method $method_minus_1;

static {
try {
// 获取add方法
$method_add_0 = cn.hff.service.ICalcService.class.getMethod("add",
new java.lang.Class[] { int.class, int.class });
// 获取minus方法
$method_minus_1 = cn.hff.service.ICalcService.class.getMethod("minus",
new java.lang.Class[] { int.class, int.class });
} catch (java.lang.NoSuchMethodException e) {
throw new java.lang.NoSuchMethodError("stub class initialization failed");
}
}

// constructors
// 创建CalcServiceImpl_Stub时需要指定RemoteRef
// RemoteRef就是远程对象的引用
public CalcServiceImpl_Stub(java.rmi.server.RemoteRef ref) {
super(ref);
}

// methods from remote interfaces

// implementation of add(int, int)
public int add(int $param_int_1, int $param_int_2) throws java.rmi.RemoteException {
try {
/**
* @param obj:包含RemoteRef的存根对象
* @param method:被调用的方法
* @param params:方法的参数
* @param opnum:被调方法的哈希值
* @return result:返回结果
*/
Object $result = ref.invoke(this, $method_add_0,
new java.lang.Object[] { new java.lang.Integer($param_int_1), new java.lang.Integer($param_int_2) },
-7734458262622125146L);
return ((java.lang.Integer) $result).intValue();
} catch (java.lang.RuntimeException e) {
throw e;
} catch (java.rmi.RemoteException e) {
throw e;
} catch (java.lang.Exception e) {
throw new java.rmi.UnexpectedException("undeclared checked exception", e);
}
}

// implementation of minus(int, int)
public int minus(int $param_int_1, int $param_int_2) throws java.rmi.RemoteException {
try {
Object $result = ref.invoke(this, $method_minus_1,
new java.lang.Object[] { new java.lang.Integer($param_int_1), new java.lang.Integer($param_int_2) },
6403391039451205508L);
return ((java.lang.Integer) $result).intValue();
} catch (java.lang.RuntimeException e) {
throw e;
} catch (java.rmi.RemoteException e) {
throw e;
} catch (java.lang.Exception e) {
throw new java.rmi.UnexpectedException("undeclared checked exception", e);
}
}
}

RemoteRef的实现类UnicaseRef的源码可以点击这里查看。