| ||||
[b]摘要[/b] 本文先简单介绍RMI,随后将使用结合一个实例来详细说明RMI具体使用。本文的目的是RMI入门,适用于不了解RMI的java初学者。 关键字 : RMI,RMI-IIOP [b]一、什么是RMI[/b] RMI(Java Remote Method Invocation)是java解决分布式应用的最初方案,它允许运行在一个虚拟机上的对象调用另一台虚拟机上对象的方法,提供了程序间的远程调用的机制。RMI允许编写分布式对象,可以使得对象在内存中、跨Java虚拟机和跨物理设备进行通信。 [b]二、为什么要学习RMI[/b] RMI是java最初分布式解决方案,是Java分布式应用的基础。Java RMI-IIOP(Java Remote Method Invocation over the Internet Inter-ORB Protocol)是J2EE真正实现强大的网络功能的机制。RMI-IIOP是同CORBA兼容的RMI的扩展版本,被强制使用在EJB和J2EE中。它同时吸收了RMI和CORBA的分布对象的优点,在传输协议上使用的IIOP。为了能够更好的理解EJB,了解其技术的本质,编写出自己的代码,首先必须了解RMI。 [b]三、RMI框架结构[/b] RMI分为两个部分:服务器和客户端。服务器建立一些远程对象,让这些对象的引用可以接入,等待客户端的对象来调用这些远程对象的方法。RMI维护服务器和客户端的通信和数据的传输。 分布式对象应用一般包含下列步骤: 1. 远程对象的查找。 客户端利用registry获得对象的引用,服务器利用registry来绑定命名和远程对象。 2. 远程对象的通信。 远程对象间的通信由RMI处理,使用时就如用普通对象一样。 3. 类的加载。 图一 客户端与服务器结构 一个对象变成远程的必须实现java.rmi.remote接口。RMI特殊的对待远程对象,它传递远程对象的stub到接收者,这个stub作为远程对象在客户端的local表示,也可看作是远程对象的代理,对调用者来说它就是远程对象的一个引用。调用者调用local stub,local stub去调用远程的对象方法。其网络结构如图二所示。 RMI有rmic编译工具,把remote类编译成Stub(存根类)和Skeleton(骨架类)。客户端从服务器把Stub下载到本地,做为远程对象的代理。Stub使远程对象调用透明化,用户不用考虑具体的通信细节。同样,Skeleton作为远程对象的本地接口,使远程对象不用去考虑分布对象的实现。 [b]四、远程方法调用[/b] 上一节中,我们已经知道RMI管理了对象间在网络中的通信。本节将说明分布对象调用传递的参数。 我们知道对于普通方法调用有值传递和引用传递,对与远程方法调用来说,一台机器上的内存引用对象传递在另一台机器的内存空间中可能并没有引用的对象,所有远程方法调用都是值传递。传递的对象都是原对象的copy。Java中对象的Copy只需简单实现Serilization接口,这个接口告诉系统该类可以序列化并没有具体的方法。远程方法调用所有的参数和返回值都必要实现Serilization接口。 一个对象可能包含了其它对象的引用,只要这些对象都实现了Serilization接口,系统序列化的时会递归的执行序列化操作直到所有的引用都以序列化。因此,对一些大对象序列化时应该谨慎,应仔细考虑哪些字段不必要序列化,否则将严重影响网络传输的性能。Java中用tranisient关键字来标识数据不序列化。 [b]五、示例[/b] 用RMI开发一个远程方法调用一般需要如下步骤: 1. 定义一个远程接口 2. 实现这个远程接口 3. 开发服务器 4. 开发客户端 5. 生成Stubs和Skeletons,启动RMI registry,server和服务器。 本节将按照上诉步骤开发一个简单文件传输系统(见参考文献2)。 定义一个远程接口 设计RMI服务器,首先必须设计一个自己的remote 接口,它扩展java.rmi.remote,远程接口本质是开放给客户端的一个远程对象的视图,连接服务器和客户端。接口包含了对象间传递的核心部分。文件下载的remote接口定义见Code Sample 1。在FileInterface接口中定义一个downloadFile()的方法,参数是String型的文件名,返回文件的byte数组。 Code Sample 1: FileInterface.java import java.rmi.Remote; import java.rmi.RemoteException; public interface FileInterface extends Remote { public byte[] downloadFile(String fileName) throws RemoteException; } 注意:1.该接口必须扩展java.rmi.Remote接口。 2.每个方法必须抛出RemoteException异常。 实现这个远程接口 下一步是实现FileInterface接口,见Code Sample 2。注意FileImpl继承UnicastRemoteObject类,这说明FileImpl类使用来建立一个单一的,不能复制的远程对象,将使用RMI默认的基于TCP的传输协议。UnicastRemoteObject实现了Object中的方法,还包含了一些构造器和用来导出远程对象的静态方法,这些是远程对象能够接收来自客户端的调用。 远程对象的实现并不一定要继承UnicastRemoteObject,但是必须显示的调用UnicastRemoteObject的exportObject方法的一种,让RMI知道远程对象以便接收客户端的调用。通过继承UnicastRemoteObject,FileInpl类能够创建简单的远程对象,支持unicast(point-to-point)远程通信。 Code Sample 2: FileImpl.java import java.io.*; import java.rmi.*; import java.rmi.server.UnicastRemoteObject; public class FileImpl extends UnicastRemoteObject implements FileInterface { private String name; public FileImpl(String s) throws RemoteException{ super(); name = s; } public byte[] downloadFile(String fileName){ try { File file = new File(fileName); byte buffer[] = new byte[(int)file.length()]; BufferedInputStream input = new BufferedInputStream(new FileInputStream(fileName)); input.read(buffer,0,buffer.length); input.close(); return(buffer); } catch(Exception e){ System.out.println("FileImpl: "+e.getMessage()); e.printStackTrace(); return(null); } } } 开发服务器 第三步是开发服务器,服务器需要做三件事: 1. 创建一个RMISecurityManager并在系统中安装。 2. 创建一个Remote对象的实例。 3. 把这个实例注册在RMI registry中。 Code Sample 3: FileServer.java import java.io.*; import java.rmi.*; public class FileServer { public static void main(String argv[]) { if(System.getSecurityManager() == null) { System.setSecurityManager(new RMISecurityManager()); } try { FileInterface fi = new FileImpl("FileServer"); Naming.rebind("//127.0.0.1/FileServer", fi); } catch(Exception e) { System.out.println("FileServer: "+e.getMessage()); e.printStackTrace(); } } } 语句Naming.rebind("//127.0.0.1/FileServer", fi);假设RMI registry工作在默认的端口(1099),如果想让RMI registry的工作在不同的端口,可以指定你想要的端口。如你想工作在4500端口,该语句改为 Naming.rebind("//127.0.0.1:4500/FileServer", fi); 如果registry和服务器不是工作在同一台机器上,则可以修改127.0.0.1为实际的IP地址即可。 开发客户端 下一步将开发一个客户端。客户端可以远程的调用remote接口中的方法,但是在调用之前必须首先从RMI registry中获得remote对象的引用。客户端的程序见Code Sample 4。客户端的命令行接收两个参数,第一个是要下载的文件名,另一个是registry的IP地址。 Code Sample 4: FileClient.java import java.io.*; import java.rmi.*; public class FileClient{ public static void main(String argv[]) { if(argv.length != 2) { System.out.println("Usage: java FileClient fileName machineName"); System.exit(0); } try { String name = "//" + argv[1] + "/FileServer"; FileInterface fi = (FileInterface) Naming.lookup(name); byte[] filedata = fi.downloadFile(argv[0]); File file = new File(argv[0]); BufferedOutputStream output = new BufferedOutputStream(new FileOutputStream(file.getName())); output.write(filedata,0,filedata.length); output.flush(); output.close(); } catch(Exception e) { System.err.println("FileServer exception: "+ e.getMessage()); e.printStackTrace(); } } } 运行 编译过程: 服务器端对象用javac编译成class文件后,用rmic生成远程对象的class文件stub和Skel。 javac FileImpl.java rmic -d . FileImpl 客户端与普通文件编译相同。 运行: 1.安全管理 运行RMI需要一个安全管理的配置文件,如果没有将不能运行。其默认文件名为policy.file 。在这个配置文件中可以写开放的端口,文件夹的操作权限。内容如下: grant { permission java.net.SocketPermission "*:1024-65535", "connect,accept"; permission java.io.FilePermission "c://home//ann//public_html//classes//-", "read"; permission java.io.FilePermission "c://home//jones//public_html//classes//-", "read"; }; 2.启动服务器。 启动服务器之前必须先启动rmiregistry ,如果rmiregistry在CLASSPATH中能够找到你的stub classes ,那么它将不能正确的从你指定的地址中加载stub class ,也就是说客户端load的stub classes并不是你的服务器所指定的,这将导致隐含的错误。启动rmiregistry命令 start rmiregistry 2001 后面的数字为端口号,默认为1099。 启动服务器的命令 java -Djava.rmi.server.codebase=file:/c:/home/ann/public_html/classes/ ――存放stubs classes的地址 -Djava.rmi.server.hostname=zaphod.east.sun.com ――服务器名 -Djava.security.policy=java.policy ―― 安全文件,在classpath 中找 FileImpl ――服务器主类 3.启动客户端。 命令java -Djava.rmi.server.codebase=file:/c:/home/jones/public_html/classes/ -Djava.security.policy=java.policy FileClient 192.168.123 20 后记: Java包含众多技术,但这些技术都是在一些基础的技术上搭件起来的,因此,在Java的学习中切忌好高骛远,心浮气躁。 参考文献: 【1】 Mastering Enterprise JavaBeans,Ed Roman。 【2】 Distributed Java Programming with RMI and CORBA,Qusay H.Mahmoud, http://java.sun.com/developer/technicalArticles/RMI/rmi_corba 【3】 The Java Tutorial , RMI部分 |