基础知识
分布式计算是一门计算机科学,它研究如何把一个需要非常巨大的计算能力才能解决的问题分成许多小的部分,然后把这些部分分配给许多计算机进行处理,最后把这些计算结果综合起来得到最终的结果。 常见的分布式技术有:CORBA、DCOM和RMI。
其中,面向对象的远程方法调用(Remote Method Invocation,RMI)是Enterprise JavaBeans的支柱技术,是建立分布式Java应用程序的方便途径。RMI是Java的一组开发分布式应用程序的API,它使用Java语言接口定义了远程对象,集合了Java序列化和Java远程方法协议(Java Remote Method Protocol)。
通过RMI技术,使原先的程序在同一操作系统的方法调用,变成了不同操作系统之间程序的方法调用。由于J2EE是分布式程序平台,它的RMI机制允许程序组件在不同操作系统之间的通信。比如,一个EJB可以通过RMI调用Web上另一台机器上的EJB远程方法。
在与远程对象的通信过程中,RMI使用标准机制:桩stub和架构skeleton。桩和架构是应用程序与系统其他部分的接口,它们使用RMI的rmic编译器产生。桩通常负责:初始化远程调用、序列化、远程方法调用、反序列化、远程方法调用完成等;构架负责:反序列化客户端参数、调用实际远程对象、序列化返回客户端参数。
RMI:面向对象的远程方法调用(Remote Method Invocation)是Enterprise JavaBeans的支柱,是建立分布式Java应用程序的方便途径。
开发工具包包括:
l java.rmi 客户端的rmi类,接口和异常
l java.rmi.sever 服务器端的rmi类,接口和异常
l java.rmi.registry 用于管理rmi命名服务的类
l java.rmi.dgc 用于管理分布式垃圾收集的类
l java.rmi.activation 用于按需激活的rmi服务的类
应用工具有:
n Rmic.exe 编译器,生成stub和skeleton
n rmiregistry.exe 为rmi提供命名服务的服务器,这项服务把名字和对象关联在一起
n rmid.exe 支持rmi激活框架的服务器
RMI实现流程
1、生成一个远程接口。所有的远程服务都需要继承Remote接口,该接口是一个不定义方法的标记接口,其全部内容只有一行public interface Remote{ }。
2、实现远程对象(服务器端程序) ,该类需要继承UnicastRemoteObject,并实现远程服务接口,而且还要使用RMISecurityManager;
3、生成桩程序和架构(服务器端程序) ;
4、编写服务器程序;
5、编写客户程序;
6、注册远程对象;
7、启动远程对象。
RMI在Windows下运行步骤,一共分为5步,分别是:
1.进入命令行窗口,进入自己项目的文件夹内
javac *.java
2.使用rmic serverName
产生serverName_Stub.class的文件
3.使用start rmiregistry [port]
4.输入java -Djava.security.policy=policyName serverName 运行服务器
5.如果成功另外打开一个命令行窗口进入自己项目文件夹
输入 java -Djava.security.policy=policyName clientName
(注意,有的时候要在后面加上参数localhost)
在RMI中,策略文件是一个文本文件,里面记录了一些对计算机资源访问的方式;比如对本地文件的访问控制,对端口的访问控制等。其文件后缀名为”*.policy”。系统策略文件的缺省位置为:java_jre.home/lib/security/java.policy (Solaris) ,java_jre.home\lib\security\java.policy (Windows) ,其格式为:
grant {
permission java.net.SocketPermission "*:1024-65535",
"connect,accept";
permission java.net.SocketPermission "*:80","connect";};
为了差别不同的安全策略,可建立自己的策略文件,如c:\MyPolicy.policy ,假设运行本地的TCP
2005端口可以连接、接受连接和解析,则内容为:grant {
permission java.net.SocketPermission
"localhost:2005","connect,accept,resolve";}
当运行RMI服务器程序时,指定了安全策略文件
java -Djava.security.policy=c:\MyPolicy.policy rmiServer
代码示例
编写RMI基础
//实现一个Remote接口
import java.rmi.*;
public interface Arith extends java.rmi.Remote{
int [] add(int a[], int b[])throws java.rmi.RemoteException;
}
远程接口定义说明了服务器提供的方法特性,包含了方法的名字和参数。这样的接口必备的特性:
l 必须声明为public
l 必须extends java.rmi.Remote接口
异常处理时,必须throws java.rmi.RemoteException
客户端:
//客户端,访问指定的服务器端
import java.rmi.*;
import java.net.*; public class ArithApp{
public static void main(String [] argv){
int a[] = {1,2,3,4,5,6,7,8,9,10}; //需要累加的数据
int b[] = {1,2,3,4,5,6,7,8,9,10}; int result [] = new int[10]; try{
Arith obj=(Arith)Naming.lookup("//localhost:3000/ArithServer"); //查找指定服务
result = obj.add(a,b); //调用RMI服务
}catch(Exception e){
System.out.println("ArithServer error" + e.getMessage());
e.printStackTrace();
} System.out.print("The sum=");
for(int i=0;i<result.length;i++){
System.out.println(result[i] + "");
}
System.out.println();
}
}
要运行这个程序,简单的方法是修改Java_JRE\lib\security\java.policy的内容
找到 grant{
在接下来的第一行添加以下语句,取消所有的安全限制。
permission java.security.AllPermission;
服务端:
//服务器端,实现一个RMI服务
import java.rmi.*;
import java.rmi.server.UnicastRemoteObject; public class ArithImpl extends UnicastRemoteObject implements Arith{
private String objectName; public ArithImpl(String s)throws RemoteException{
super();
objectName = s;
} public int [] add(int a[], int b[]){ //服务内容是将两个组中对应元素累加
int c[] = new int[10]; for(int i=0; i<10; i++)
c[i] = a[i] + b[i]; return c;
} public static void main(String argv[]){
RMISecurityManager sm = new RMISecurityManager();
System.setSecurityManager(sm); //设置访问安全管理器 try{
ArithImpl obj = new ArithImpl("ArithServer"); //注册RMI服务名
Naming.rebind("//localhost:3000/ArithServer", obj); //绑定服务端口
System.out.println("ArithServer bound in registry");
}catch(Exception e){
System.out.println("ArithServer error" + e.getMessage());
e.printStackTrace();
}
}
}
程序运行:
运行一个RMI程序通常需要增加2个应用程序,分别是:
rmic 编译器,生成stub和skeleton
rmiregistry 一个为rmi提供命名服务的服务器,这项服务把名字和对象关联在一起
当需要运行一个RMI程序时,步骤为
•1、生成一个远程接口;
•2、实现远程对象(服务器端程序) ;
•3、生成桩程序和骨干网(服务器端程序) ;
•4、编写服务器程序;
•5、编写客户程序;
•6、注册远程对象;
•7、启动远程对象。 在WINDOWS下运行RMI,具体分为5步:
•1.进入命令行窗口,进入自己项目的文件夹内,编译所有的程序
javac *.java
•2.使用rmic对服务器端程序编译,生成一个serverName_Stub.class的桩文件
rmic serverName
•3.在服务器端启动rmi注册机,使用start rmiregistry
•4.输入java -Djava.security.policy=policyName serverName 运行服务器
•5.如果成功另外打开一个命令行窗口进入自己项目文件夹
•输入 java -Djava.security.policy=policyName clientName(注意,有的时候要在后面加上参数localhost)
在这里policyName是安全策略文件,它是一个文本文件,里面记录了一些对计算机资源访问的方式;比如对本地文件的访问控制,对端口的访问控制等。 后缀名需要是*.policy。
例如:
Grant codeBase “路径"
{
permission java.io.FilePermission "C:\\users\\cathy\\foo.bat", "read";
permission java.net.SocketPermission "*:1024-65535", "connect,accept";
permission java.security.AllPermission;
};
授权了指定文件能读操作,socket的1024-65535端口可以连接和接受请求,以及所有安全机制都允许等控制策略。
简单的策略文件,假设文件名为: c:\MyPolicy.policy ,内容为:
grant {
permission java.net.SocketPermission "localhost:2005","connect,resolve";
}
执行时用命令,指定了安全策略文件
java -Djava.security.policy=c:\MyPolicy.policy rmiServer
如果要允许客户端的连接,还需要进一步修改policy文件,如
grant {
permission java.net.SocketPermission "localhost:1023-65535","connect,accept, resolve";
}
表示本地的1023-65535端口,都可以进行连接、接受连接请求、解析的任务。
【更多参考】
有意义的RMI程序
在以前的多线程程序中,要求进行1-10000数字的累加,当时建议采用多线程的方法,每个线程分别执行一定数值范围的累加,最后将线程结果累加得到所希望的答案。现在利用RMI将累加过程分散到多台计算机上,实现分布式的累加。
客户端:单个服务器
//客户端,访问单一指定的服务器端
import java.rmi.*;
import java.net.*; public class AddApp{
public static void main(String [] argv){
int a = 1; //需要累加的数据
int b = 10000; int result = 0; try{
Add obj=(Add)Naming.lookup("//IP1:3000/AddServer"); //查找指定服务
result = obj.add(a,b); //调用RMI服务
}catch(Exception e){
System.out.println("AddServer error" + e.getMessage());
e.printStackTrace();
} System.out.print("The sum=" + result);
System.out.println();
}
}
客户端:多个服务器
//客户端,访问多个指定的服务器端
import java.rmi.*;
import java.net.*; public class AddApp implements Runnable{
int start, end;
String server;
int result=0; public AddApp(int start, int end, String server){
this.start = start;
this.end = end;
this.server = server;
} Public void run(){
try{
Add obj=(Add)Naming.lookup(“//” + server + ":3000/AddServer"); //查找指定服务
result = obj.add(a,b); //调用RMI服务
}catch(Exception e){
System.out.println("AddServer error" + e.getMessage());
e.printStackTrace();
} System.out.print("The sum=" + result);
System.out.println(); }
public static void main(String [] argv){
AddApp ap1 = new AddApp(1,2000, IP1);
AddApp ap2 = new AddApp(2001,4000, IP2);
AddApp ap3 = new AddApp(4001,6000, IP3);
AddApp ap4 = new AddApp(6001,8000, IP4);
AddApp ap5 = new AddApp(8001,10000, IP5); ap1.start(); ap2.start(); ap3.start(); ap4.start(); ap5.start(); ap1.join(); ap2.join(); ap3.join(); ap4.join(); ap5.join(); System.out.print("The sum=" + (ap1.result + ap2.result + ap3.result + ap4.result + ap5.result));
System.out.println();
}
}
服务端:
//服务器端,实现一个RMI服务
import java.rmi.*;
import java.rmi.server.UnicastRemoteObject; public class AddImpl extends UnicastRemoteObject implements Arith{
private String objectName; public ArithImpl(String s)throws RemoteException{
super();
objectName = s;
} public int add(int start, int end){ //服务内容是将两个组中对应元素累加
int c = 0; for(int i=start; i<=end; i++) //考虑如何实现多线程累加
c = c + i; return c;
} public static void main(String argv[]){
RMISecurityManager sm = new RMISecurityManager();
System.setSecurityManager(sm); //设置访问安全管理器 try{
AddImpl obj = new AddImpl("AddServer"); //注册RMI服务名
Naming.rebind("//不同的IP:3000/AddServer", obj); //绑定服务端口
System.out.println("AddServer bound in registry");
}catch(Exception e){
System.out.println("AddServer error" + e.getMessage());
e.printStackTrace();
}
}
}