0%

RMI

一、含义

英文全称为“Java Remote Method Invocation”,即“Java远程方法调用”。RMI是一种用于实现远程方法调用的应用程序编程接口,它使得RMI Client上的Java程序能够远程调用RMI Server上Java对象的方法。

二、详细介绍

RMI应用程序基本架构如图1所示。

图1

RMI应用程序数据流如下:

  1. 运行Web Server服务
  2. 运行rmiregistry服务
  3. 运行RMI Server,生成一个Remote Object实例,使用自定义名称将其注册到rmiregistry服务中(具体实现细节是,在生成一个Remote Object实例之后,再生成一个代理类实例stub,stub内含有该Remote Object实例的引用,以及该RMI Server的主机地址和该Remote Object实例绑定到的端口号,使用自定义名称将stub注册到rmiregistry服务中)
  4. 运行RMI Client,根据名称,从rmiregistry服务中获取Remote Object实例的引用,通过该引用远程调用Remote Object实例上的方法(具体实现细节是,根据名称,从rmiregistry服务中获取代理类实例stub,根据其中的“RMI Server的主机地址”,定位到相应的RMI Server,进而根据“Remote Object实例绑定到的端口号”和“Remote Object实例引用”执行远程方法调用)

在远程方法调用过程中,“RMI Server”和“RMI Client”作为主要角色,“Web Server”和“rmiregistry”作为辅助角色。rmiregistry服务类似于DNS服务,RMI Server使用名称向其注册代理类实例,RMI Client根据名称向其获取代理类实例。rmiregistry,RMI Server和RMI Client可访问Web Server,下载所需要而缺失的类定义。
RMI Server和RMI Client之间,RMI Server与rmiregistry之间,RMI Client与rmiregistry之间的数据交互(方法参数,方法返回值,代理类实例)要求数据“或为基本类型,或为可序列化对象类型”。

三、实验

3.1、主机列表

实验主机列表规划如表1。

表1

内网地址 角色
10.110.20.53 运行Web Server服务
10.110.20.61 运行rmiregistry服务
10.110.20.62 运行RMI Server
10.110.20.63 运行RMI Server
10.110.20.96 运行RMI Client

3.2、源代码

3.2.1、公共接口部分

Compute接口:

1
2
3
4
5
6
7
8
9
10
11
package compute;

import java.rmi.Remote;
import java.rmi.RemoteException;

/**
* Remote Object需要继承实现Remote接口
*/
public interface Compute extends Remote {
<T> T executeTask(Task<T> t) throws RemoteException;
}

Task接口:

1
2
3
4
5
6
7
8
package compute;

/**
* 任务接口
*/
public interface Task<T> {
T execute();
}

3.2.2、RMI Server部分

ComputeEngine类:

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
73
74
75
76
77
package engine;

import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import compute.Compute;
import compute.Task;

public class ComputeEngine implements Compute {

public ComputeEngine() {
super();
}

public <T> T executeTask(Task<T> t) {
return t.execute();
}

public static void main(String[] args) {
if (System.getSecurityManager() == null) {
System.setSecurityManager(new SecurityManager());
}

try {
if (args[0].equals("server1")) {
String name = "Compute1";

// 生成一个Remote Object实例
Compute engine = new ComputeEngine();

/**
* 1、传入Remote Object实例<br/>
* 2、RMI Server主机地址可由程序自动获取(自动获取由于“内网地址和外网地址”之分偶尔会导致一些问题),也可通过“java.rmi.server.hostname”环境变量显式指定<br/>
* 3、传入参数值0,表示随机选择一个端口用于绑定Remote Object实例<br/>
* 4、基于以上3个数据,生成代理类实例stub
*/
Compute stub = (Compute) UnicastRemoteObject.exportObject(engine, 0);

/**
* 1、通过“own.variable.registry.host”环境变量,获取rmiregistry服务地址<br/>
* 2、使用默认端口1099连接rmiregistry服务<br/>
* 3、使用“Compute1”名称将代理类实例stub注册到rmiregistry服务中
*/
Registry registry = LocateRegistry.getRegistry(System.getProperty("own.variable.registry.host"));
registry.rebind(name, stub);

System.out.println("ComputeEngine Server1 bound");
} else {
String name = "Compute2";

// 生成一个Remote Object实例
Compute engine = new ComputeEngine();

/**
* 1、传入Remote Object实例<br/>
* 2、RMI Server主机地址可由程序自动获取(自动获取由于“内网地址和外网地址”之分偶尔会导致一些问题),也可通过“java.rmi.server.hostname”环境变量显式指定<br/>
* 3、传入参数值0,表示随机选择一个端口用于绑定Remote Object实例<br/>
* 4、基于以上3个数据,生成代理类实例stub
*/
Compute stub = (Compute) UnicastRemoteObject.exportObject(engine, 0);

/**
* 1、通过“own.variable.registry.host”环境变量,获取rmiregistry服务地址<br/>
* 2、使用默认端口1099连接rmiregistry服务<br/>
* 3、使用“Compute2”名称将代理类实例stub注册到rmiregistry服务中
*/
Registry registry = LocateRegistry.getRegistry(System.getProperty("own.variable.registry.host"));
registry.rebind(name, stub);

System.out.println("ComputeEngine Server2 bound");
}
} catch (Exception e) {
System.err.println("ComputeEngine exception:");
e.printStackTrace();
}
}
}

3.2.3、RMI Client部分

Pi类:

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
package client;

import compute.Task;
import java.io.Serializable;
import java.math.BigDecimal;

public class Pi implements Task<BigDecimal>, Serializable {

private static final long serialVersionUID = 227L;

private static final BigDecimal FOUR = BigDecimal.valueOf(4);

private static final int roundingMode = BigDecimal.ROUND_HALF_EVEN;

private final int digits;

public Pi(int digits) {
this.digits = digits;
}

public BigDecimal execute() {
return computePi(digits);
}

public static BigDecimal computePi(int digits) {
int scale = digits + 5;
BigDecimal arctan1_5 = arctan(5, scale);
BigDecimal arctan1_239 = arctan(239, scale);
BigDecimal pi = arctan1_5.multiply(FOUR).subtract(arctan1_239).multiply(FOUR);
return pi.setScale(digits, BigDecimal.ROUND_HALF_UP);
}

public static BigDecimal arctan(int inverseX, int scale) {
BigDecimal result, numer, term;
BigDecimal invX = BigDecimal.valueOf(inverseX);
BigDecimal invX2 = BigDecimal.valueOf(inverseX * inverseX);

numer = BigDecimal.ONE.divide(invX, scale, roundingMode);

result = numer;
int i = 1;
do {
numer = numer.divide(invX2, scale, roundingMode);
int denom = 2 * i + 1;
term = numer.divide(BigDecimal.valueOf(denom), scale, roundingMode);
if ((i % 2) != 0) {
result = result.subtract(term);
} else {
result = result.add(term);
}
i++;
} while (term.compareTo(BigDecimal.ZERO) != 0);
return result;
}
}

ComputePi类:

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
package client;

import java.io.Serializable;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.math.BigDecimal;
import java.util.Arrays;

import compute.Compute;

public class ComputePi {
public static void main(String args[]) {
if (System.getSecurityManager() == null) {
System.setSecurityManager(new SecurityManager());
}
try {
String name = "Compute1";

/**
* 1、通过“own.variable.registry.host”环境变量,获取rmiregistry服务地址<br/>
* 2、使用默认端口1099连接rmiregistry服务<br/>
* 3、使用“Compute1”名称从rmiregistry服务中获取代理类实例stub
* */
Registry registry = LocateRegistry.getRegistry(System.getProperty("own.variable.registry.host"));

Compute stub = (Compute) registry.lookup(name);

/**
* 1、打印代理类实例实际类型<br/>
* 2、证明代理类实例属于“可序列化对象类型”
*/
System.out.println("Proxy Class: " + stub.getClass());
System.out.println("Prove implementing Serializable interface: " + (stub instanceof Serializable));

Pi task = new Pi(4);

// 通过代理类实例stub,定位RMI Server,定位所引用的Remote Object实例,并执行远程方法调用
BigDecimal pi = stub.executeTask(task);
System.out.println(pi);

// 接下来是与上面类似的另外一个远程方法调用过程

name = "Compute2";

stub = (Compute) registry.lookup(name);

task = new Pi(10);

BigDecimal pi2 = stub.executeTask(task);

System.out.println(pi2);
} catch (Exception e) {
System.err.println("ComputePi exception:");
e.printStackTrace();
}
}
}

3.3、编译打包,部署,运行及结果

3.3.1、编译打包

执行以下命令编译打包公共接口部分:

1
2
javac compute/Compute.java compute/Task.java
jar cvf compute.jar compute/*.class

执行以下命令编译RMI Server部分:

1
javac -cp compute.jar engine/ComputeEngine.java

执行以下命令编译RMI Client部分:

1
javac -cp compute.jar client/ComputePi.java client/Pi.java

3.3.2、部署

部署Web Server:

1
scp -r compute.jar engine client 10.110.20.53:~/classes/

部署RMI Server:

1
2
scp -r engine compute.jar 10.110.20.62:~/classes/
scp -r engine compute.jar 10.110.20.63:~/classes/

部署RMI Client:

1
scp -r client compute.jar 10.110.20.96:~/classes/

3.3.3、运行及结果

1、Web Server服务运行及结果
在“10.110.20.53”上通过以下命令开启一个简单HTTP Server服务(默认监听8000端口):

1
2
cd ~
python -m SimpleHTTPServer

2、rmiregistry服务运行及结果
在“10.110.20.61”上通过以下命令开启rmiregistry服务(默认监听1099端口),并指定Web Server下类定义资源的地址:

1
rmiregistry -J-Djava.rmi.server.codebase=http://10.110.20.53:8000/classes/compute.jar

3、RMI Server运行及结果
在“10.110.20.62”上通过以下命令运行RMI Server:

1
2
cd ~/classes
java -cp ".:compute.jar" -Down.variable.registry.host=10.110.20.61 -Djava.rmi.server.codebase=http://10.110.20.53:8000/classes/ -Djava.security.policy=server.policy -Djava.rmi.server.hostname=10.110.20.62 engine.ComputeEngine "server1"

同理,在“10.110.20.63”上通过以下命令运行RMI Server:

1
2
cd ~/classes
java -cp ".:compute.jar" -Down.variable.registry.host=10.110.20.61 -Djava.rmi.server.codebase=http://10.110.20.53:8000/classes/ -Djava.security.policy=server.policy -Djava.rmi.server.hostname=10.110.20.63 engine.ComputeEngine "server2"

运行RMI Server,必须设置“java.rmi.server.codebase(指定Web Server下类定义资源的地址)”和“java.security.policy(指定安全访问策略)”环境变量,其他根据需要进行设置。
“server.policy”文件内容如下:

1
2
3
grant {
permission java.security.AllPermission;
};

4、RMI Client运行及结果
在“10.110.20.96”上通过以下命令运行RMI Client:

1
2
cd ~/classes
java -cp ".:compute.jar" -Down.variable.registry.host=10.110.20.61 -Djava.rmi.server.codebase=http://10.110.20.53:8000/classes/ -Djava.security.policy=client.policy client.ComputePi

运行RMI Client,必须设置“java.rmi.server.codebase(指定Web Server下类定义资源的地址)”和“java.security.policy(指定安全访问策略)”环境变量,其他根据需要进行设置。
“client.policy”文件内容如下:

1
2
3
grant {
permission java.security.AllPermission;
};

最终打印如下结果:

1
2
3
4
Proxy Class: class com.sun.proxy.$Proxy0
Prove implementing Serializable interface: true
3.1416
3.1415926536

四、其他

4.1、RMI应用程序架构非常灵活

图1只是给出了最基本的RMI应用程序的架构,事实上,RMI应用程序的架构非常灵活。一个RMI应用程序可包含多个Web Server,rmiregistry,RMI Client和RMI Server,只需要最终的架构能够正常工作即可。

4.2、监听端口验证

4.2.1、Web Server

在“10.110.20.53”上运行Web Server服务,执行netstat -anp | grep '8000'命令,可得:

1
tcp        0      0 0.0.0.0:8000            0.0.0.0:*               LISTEN      27593/python

4.2.2、rmiregistry

在“10.110.20.61”上运行rmiregistry服务,执行netstat -anp | grep '1099',可得:

1
tcp6       0      0 :::1099                 :::*                    LISTEN      16289/rmiregistry

4.2.3、RMI Server

在“10.110.20.62”和“10.110.20.63”上运行有RMI Server,RMI Server会监听用于绑定Remote Object实例的端口。
以“10.110.20.62”上的RMI Server为例,进行验证。
首先执行jps命令,得到RMI Server进程的进程ID,假定为7974。
接着执行netstat -anp | grep '7974',可得:

1
tcp6       0      0 :::56707                :::*                    LISTEN      7974/java

参考文献: [1]https://zh.wikipedia.org/wiki/Java%E8%BF%9C%E7%A8%8B%E6%96%B9%E6%B3%95%E8%B0%83%E7%94%A8 [2]https://docs.oracle.com/javase/tutorial/rmi/overview.html [3]http://www.linuxjournal.com/content/tech-tip-really-simple-http-server-python [4]http://stackoverflow.com/questions/464687/running-rmi-server-classnotfound [5]http://stackoverflow.com/questions/32913180/rmi-server-vs-rmi-registry [6]http://docs.oracle.com/javase/7/docs/technotes/guides/rmi/enhancements-7.html [7]http://docs.oracle.com/javase/6/docs/technotes/guides/rmi/codebase.html
您的支持将鼓励我继续分享!