RMI 使得客户端和服务端隔离,而且服务端能够动态的加载客户端的代码,来执行客户端的任务。这样就可以把任务分割,让不同的机器来执行各个子任务,也可以充分使用高性能的服务端机器。而且客户端对于服务端的调用就像客户端本地的接口调用一样方便。这种动态的装载客户端代码是通过实行 RMI规范要求的接口java.rmi.Remote ,这个接口抛出 RemoteException 异常;或者实现 serialization 接口 ,使得 对象能够在 client 和 server 间进行传递来实现。下面记录下参照 tutorial来实现 demo 的过程。
1: 创建 client 和 server 间的 通讯接口:
Compute interface 和 Task interface
2: 创建服务端实现:
package engine;
import java.rmi.RemoteException;
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 {
String name = "Compute";
Compute engine = new ComputeEngine();
Compute stub =
(Compute) UnicastRemoteObject.exportObject(engine, 0);
Registry registry = LocateRegistry.getRegistry();
registry.rebind(name, stub);
System.out.println("ComputeEngine bound");
} catch (Exception e) {
System.err.println("ComputeEngine exception:");
e.printStackTrace();
}
}
}
这里需要控制程序的访问本地系统的权限,并注册提供服务给client端查找调用
3: 创建客户端,包括 调用服务端服务,和 定义需要执行的任务
package client;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.math.BigDecimal;
import compute.Compute;
public class ComputePi {
public static void main(String args[]) {
if (System.getSecurityManager() == null) {
System.setSecurityManager(new SecurityManager());
}
try {
String name = "Compute";
Registry registry = LocateRegistry.getRegistry(args[0]);
Compute comp = (Compute) registry.lookup(name);
Pi task = new Pi(Integer.parseInt(args[1]));
BigDecimal pi = comp.executeTask(task);
System.out.println(pi);
} catch (Exception e) {
System.err.println("ComputePi exception:");
e.printStackTrace();
}
}
}
这里也需要下载服务端的实现,需要控制下载的server端程序(stub 代理程序) 访问本地系统的权限。
客户端定义生成要执行的任务,并传递到 server端,通过stub 代理调用 远端的server 服务来执行任务,获取结果返回。
java.rmi包中提供了类java.rmi.registry.LocateRegistry,用于获取名字服务或创建名字服务.调用
LocateRegistry.createRegistry(int
port)方法可以在某一特定端口创建名字服务,从而用户无需再手工启动rmiregistry.此
外,LocateRegistry.getRegistry(String host,int port)方法可用于获取名字服务.
4:定义server 端 和 client 端的 权限
server.policy 如下:
grant codeBase "file:/d:/study/rmi/rmitest/server/" {
permission java.security.AllPermission;
};
client.policy 如下:
grant codeBase "file:/d:/study/rmi/rmitest/client/src/" {
permission java.security.AllPermission;
};
这里需要注意policy的格式
codeBase 需用声明协议类型,若为本地文件话,需以file:声明,FilePermission里面则直接使用目标文件名或通配符
5: 分别编译 server/client 端程序,注册server服务,并执行server 和client端程序这里的接口可以打包成jar包供 server 和 client 端用,无需下载
1: 打包交互接口
cd D:/study/rmi/rmitest/
javac compute/Compute.java compute/Task.java
jar cvf compute.jar compute/*.class
2: 编译服务端程序
cd D:/study/rmi/rmitest/
javac -cp D:/study/rmi/rmitest/client/compute.jar engine/ComputeEngine.java
3: 编译客户端程序
cd D:/study/rmi/rmitest/client/src
javac -cp D:/study/rmi/rmitest/client/compute.jar client/ComputePi.java client/Pi.java
4: 启动注册服务,并注册 server 提供的 service
start rmiregistry
这里的启动注册服务,需要删除 CLASSPATH 环境变量的设置(这里没有测试过不删除 CLASSPATH 环境变量的状态)
D:/study/rmi/rmitest/server>java -cp d:/study/rmi/rmitest/server;d:/study/rmi/rmitest/interface/src/compute.jar
-Djava.rmi.server.codebase=file:/d:/study/rmi/rmitest/interface/src/compute.jar
-Djava.rmi.server.hostname=localhost
-Djava.security.policy=server.policy
engine.ComputeEngine
这里的 java.rmi.server.hostname 属性设置的 提供server 服务的 stub 的地址,client 端通过这个地址来获取server端 提供的服务
5: 启动客户端程序
D:/study/rmi/rmitest/client/src>java -cp d:/study/rmi/rmitest/client/src/;d:/study/rmi/rmitest/interface/src/compute.jar
-Djava.rmi.server.codebase=file:/d:/study/rmi/rmitest/client/src/
-Djava.security.policy=client.policy client.ComputePi localhost 45
即可以看到执行的结果:
3.141592653589793238462643383279502884197169399
这里需要注意的是 权限控制里面设置的 codebase 地址值,以及执行程序的时候设置的 -cp 值。
参考
http://java.sun.com/docs/books/tutorial/rmi/overview.html