一、含义 英文全称为“Java Remote Method Invocation”,即“Java远程方法调用”。RMI是一种用于实现远程方法调用的应用程序编程接口,它使得RMI Client上的Java程序能够远程调用RMI Server上Java对象的方法。
二、详细介绍 RMI应用程序基本架构如图1所示。
图1
RMI应用程序数据流如下:
运行Web Server服务
运行rmiregistry服务
运行RMI Server,生成一个Remote Object实例,使用自定义名称将其注册到rmiregistry服务中(具体实现细节是,在生成一个Remote Object实例之后,再生成一个代理类实例stub,stub内含有该Remote Object实例的引用,以及该RMI Server的主机地址和该Remote Object实例绑定到的端口号,使用自定义名称将stub注册到rmiregistry服务中)
运行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