Jerome Kwok〖旧日的足迹〗

仁者不忧,知者不惑,勇者不惧
posts - 0, comments - 0, trackbacks - 0, articles - 81
  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理

RMI实例详解

Posted on 2014-02-10 17:39 Jerome Kwok 阅读(145) 评论(0)  编辑  收藏 所属分类: RMI

1. 功能需求:实现最简单的远程文件访问。Client能够获取在Server上的远程文件信息。实例代码中,客户端获取了服务器端的文件“D:\\test\testDate.java”的内容和文件名。

2. 实现结构:

a)  Server包含:        
i.     FileInformation.java
描述文件信息的接口,作为远程访问方法的返回值类型       
ii.     FileInformationImple.java
描述文件信息的接口实现     
iii. Hello.java
提供的远程访问方法的接口       
iv.HelloImple.java
远程访问方法的接口实现        
v.     Server.java
服务器开启程序

b)  Client 包含:        
i. FileInformation.java描述文件信息的接口 (与上同)      
ii.Hello.java提供的远程访问方法的接口(与上同)      
iii. Test.java客户端的运行程序

3. 服务器端实现的具体步骤:

a)  设计远程接口涉及的参数类型接口    

    package rmi;

    import java.io.Serializable;

    /所有远程接口使用到的参数类型和返回值类型都必须可序列化。

    public interface FileInformation extends Serializable {

         public String getContent();//获取文件内容

         public String getFileName();//获取文件名

         public boolean setFileInformation(String name, String currentDir);

     //通过指定文件路径来设置文件信息

    }

    b)  设计远程接口

    package rmi;   

    import java.rmi.Remote;   

    import java.rmi.RemoteException;   

    //远程接口必须扩展接口java.rmi.Remote   

    public interface Hello extends Remote {   

        //远程接口方法必须抛出 java.rmi.RemoteException   

        //远程接口方法中的参数和返回值都必须可序列化   

        FileInformation getFile(String name) throws RemoteException;   

    }

    c)  实现参数类型接口

    package rmi;  

    import java.io.BufferedInputStream;  

    import java.io.File;   

    import java.io.FileInputStream;   

    //实现返回值类型接口,与其它接口实现类没有区别   

    public class FileInformationImple implements
                    FileInformation {   

        private static final long serialVersionUID = -8484348346023818166L;   

        private String fileName = null;   

        private String content = null;   

        public String getContent() {   

           return content;   

        }   

        public String getFileName() {   

           return fileName;   

        }   

        public boolean setFileInformation(String name, String currentDir) {   

           String path = currentDir + "/" + name;   

           try {   

               BufferedInputStream in = new    BufferedInputStream(new  FileInputStream(new File(path)));   

               byte[] b = new byte[10000];   

               int i = in.read(b);   

               if (i == -1) {   

                  return false;   

               }   

               content = new String(b);   

               return true;   

           } catch (Exception e) {   

               e.printStackTrace();   

               return false;   

           }   

        }   

    }

d)  实现远程接口

    package rmi;   

    import java.io.Serializable;   

    import java.rmi.RemoteException;   

    import java.rmi.server.UnicastRemoteObject;   

    //扩展了UnicastRemoteObject类,并实现远程接口 HelloSerializable   

    public class HelloImple extends UnicastRemoteObject implements Hello, Serializable {   
        //必须定义构造方法,因为它必须抛出RemoteException异常    

        protected HelloImple() throws RemoteException {   

           super();   

       }   

        private static final long serialVersionUID = -7671514615926445886L;   

        //远程接口方法的实现   

        public FileInformation getFile(String name) throws RemoteException {   

           FileInformation fInfor = new FileInformationImple();   

           if(!fInfor.setFileInformation(name, "D:/test")) {   

               throw new RemoteException("invalid path");   

           }   

           return fInfor;   

        }   

    }

     e)   编写服务器启动程序

    package rmi;             

    import java.rmi.Naming;              

    import java.rmi.RemoteException;              

    import java.rmi.registry.LocateRegistry;             

    //启动 RMI 注册服务并进行对象注册             

    public class Server {              

        public static void main(String[] args) {              

           try {              

    //启动RMI注册服务,指定端口为10991099为默认端口),也可以通过命令启动$java_home/bin/rmiregistry 1099                 

    //这里用这种方式避免了再打开一个DOS窗口,而且用命令rmiregistry启动注册服务还必须事先用RMIC生成一个stub类为它所用                 

               LocateRegistry.createRegistry(1099);              

    //创建远程对象的一个或多个实例,下面是hello对象,用不同名字注册不同的实例                 

               Hello hello = new HelloImple();              

    //hello注册到另一台启动了RMI注册服务的机器上,命名为Hello              

               Naming.rebind("rmi:/127.0.0.1:1099/Hello", hello);              

               System.out.println("Server start!");              

           } catch (Exception e1) {              

           e    1.printStackTrace();              

           }              

        }             

    }

4. 编写客户端测试用例

package rmi;               

import java.rmi.Naming;              

public class Test {              

//查找远程对象并调用远程方法              

    public static void  main(String[] args) {              

       try {              

//从另一台启动了RMI注册服务的机器上查找hello实例              

           Hello h = (Hello) Naming.lookup("rmi:/127.0.0.1:1099/Hello");             

           //调用远程方法              

           FileInformation f = h.getFile("testDate.java");              

           System.out.println(f.getContent());              

       } catch (Exception e) {              

           e.printStackTrace();              

       }              

    }              

}

 5. 执行程序:可以在eclipse中直接执行,但是必须先运行服务器,再运行客户端。也可以在命令行下运行。次序要求同在eclipse中。步骤如下:

(1) 打开一个Dos窗口,执行命令java
rmi.Server 
启动服务器。启动成功会在命令行显示“Server start!”

(2) 打开另一个Dos窗口,执行命令java
rmi.Test
运行客户端程序。运行成功则会在屏幕打印“D:\\test\testDate.java”的文件内容。

6. 注意事项
(1)       本实例中并没有用到JDK所带的命令 rmic 编译实现类得到存根(Stub)类,也没用命令 rmiregistry 命令来启动RMI注册服务。在启动 rmiregistry之前必须能让它加载到相应的stub类,这就是造成**_Stub 类找不到的原因。本实例中通过“LocateRegistry.createRegistry(1099);避免了这种错误
(2)        如果要从另一台启动了RMI注册服务的机器上查找hello实例:

Hello hello = (Hello) Naming.lookup
("//192.168.1.105:1099/Hello") ;

其中的IP地址和端口号1099 RMI 注册服务器的IP和端口号,这样Client就可以在另一台机器运行。

(3)       无论是服务端还是客户端都可以用参数

        -Djava.rmi.server.codebase=http://YourServerName/YourPackagePathName/
          此时可以像JNP一样从网络上动态加载类文件。而你Web ServerYourServerName/YourPackagePathName/目录下应该有一个与你的包同名的目录,那个目录下有相应stub文件。
        
如果出现错误:
           java.lang.ClassNotFoundException:rmi.XXX_Stub(no            security manager:RMI class loader disabled)
        这是因为在Client端没有设置安全管理器,要象Server起动时那样给一个java.security.policy属性,允许它做相应socket工作(否则会出安全性异常),之后客户端就能自动从主机下载了stub文件了。 

 (4)       代码中 Server Client 省略了设置安全管理器的过程
  System.setSecurityManager(new RMISecurityManager());
  如果设置的安全管理则必须编写相应的访问策略文件。 在命令行下输入命令 policytool-->添加规则项目-->添加权限,在权限窗口的第一个下拉框中选择 All
Permission
,确认-->完成。然后在主菜单中选择文件->另存,将设置保存为 .java.policy. 并且在执行时指定参数。

(5)       可以拿单独一台机器运行 rmiregistry

            start rmiregistry

rmiregistry需要能加载到相应的stub类,可以通过设置classpath。或者使LocateRegistry.createRegistry(port)
,只作为 RMI远程对象的RMI集中注册的服务器,真正提供服务对象只往上注册,客户端只需从注册服务器上查找远程对象引用,然后调用远程方法,具体由谁提供服务由注册服务器来帮助联络。

(6)       还可以用 RMI Activation 编程方式来实现RMI远程方法调用,具体请参考 http://java.sun.com/j2se/1.4.2/docs/guide/rmi/activation.html

(7)       为什么要序列化远端接口中的参数(返回值)

这是因为需要将客户端的对象(参数)转化成byte stream,通过网络协议传输到服务端,再还原成服务端的对象进行调用。或者是需要将服务端中, java.lang包和java.util包下的类都已经实现了序列化,直接可以用在远程接口中作参数或返回值;所有的基本类型也可以直接用在远程接口中作参数或返回值;的对象(返回值)转化成byte stream,通过网络协议传输到服务端,再还原成客户端的对象进行调用。在
jdk




只有注册用户登录后才能发表评论。


网站导航: