简单理解RMI

RMI (远程方法)是 Java 平台中建立分布式计算的基础, 2 年前我刚开始接触 J2EE 时,怎么看书都是不得要领,最近这几天闲着没事又翻了翻以前没有看懂的书,突然之间顿 悟了。

一、 简单的 RMI 示例:

要快速入门,最简单的方法就是看简单的例子。下面是我写的一个简单的示例:

首先,定义一个接口 IServer ,代码如下:

IServer.java1 package rmistudy;23 import java.rmi.Remote;45  public  interface IServer extends Remote  {6     public  void doSomeThing() throws java.rmi.RemoteException;7 }89

需要注意的是,这个接口从java.rmi.Remote接口扩展,并且这个接口中定义的方法都需 要抛出java.rmi.RemoteException异常。

接着,我们要根据这个接口来实现自己的服务器对象,所谓服务器对象,就是我们大脑中 想的远程对象,这个对象中定义的方法都是被别人来调用的。代码如下:

ServerImp.javapackage rmistudy;import java.rmi. * ;import java.rmi.server. * ;public  class ServerImp extends UnicastRemoteObject implements IServer  {     public ServerImp() throws RemoteException  {        super ();    }     public  void doSomeThing() throws RemoteException  {        System.out.println( " 不带参数的远程函数doSomeThing()被调用,该 信息显示在服务器端。 " );    }     public  static  void main(String[] args)  {        ServerImp server =  null ;        try {           server =  new ServerImp();        } catch (Exception e) {           System.out.println( " 创建远程对象失败: " );           System.out.println(e.getMessage());           System.exit( 0 );        }         try {           java.rmi.Naming.rebind( " //localhost/MyServer " , server);           System.out.println( " 远程对象绑定成功。 " );        } catch (Exception e) {           System.out.println( " 远程对象绑定失败: " );           System.out.println(e.getMessage());           System.exit( 0 );        }    }}

这个类很容易理解, doSomeThing() 方法只简单的输出被调用的信息。唯一的难点就在 main() 函数中,我们通过 java.rmi.Naming.rebind() 把我们的远程对象注册到 rmi 注册 表中,这样,别人就可以通过 java.rmi.Naming.lookup() 来查找我们的远程对象。那么, rmi 注册表在哪里呢? J2SDK 的 bin 目录下有一个程序 rmiregistry ,运行它就可以得到 一个注册表进程,我们可以通过它来绑定或者查找远程对象, java.rmi.Naming.rebind 函 数的第一个参数就是要指定注册表进程的位置,因为我这里运行在自己的机器上,所以是 //localhost/ ,如果是在别的机器上,可以用 IP 地址代替。

最后,我们写一个客户机,来调用这个远程对象的方法。代码如下:

Client.java

1 package rmistudy;23 import java.rmi. * ;45  public  class Client  {67     public  static  void main(String[] args)  {8        IServer server =  null ;910         try {11           server = (IServer)Naming.lookup( " //localhost/MyServer " );12           System.out.println( " 查找远程对象成功。 " );13        } catch (Exception e) {14           System.out.println( " 查找远程对象失败: " );15           System.out.println(e.getMessage());16           System.exit( 0 );17        }1819         try {20           server.doSomeThing();21           System.out.println( " 调用doSomeThing()成功。 " );22        } catch (Exception e) {23           System.out.println( " 调用doSomeThing()失败: " );24           System.out.println(e.getMessage());25           System.exit( 0 );26        }27    }28 }29

可以看到,我们的客户端程序只用到了 IServer 接口,而不需要 ServerImp 类,它只通 过 java.rmi.Naming.lookup() 来查找远程对象的引用。

下面,我们就可以开始测试我们的程序了。先编译以上程序,然后:

第一步,要先启动 Rmi 注册表,如下:

第二步,使用 rmic 对 ServerImp.class 进行编译,生成代理类 ServerImp_Stub.class ,如下:

第三步,启动服务器端程序,如下:

第四步,启动客户端程序,我们多调用几次,如下:

这个时候,我们再看看服务器端是什么反应:

可以看到,服务器端的方法被调用,在服务器端的控制台上打印出了这样几行消息。

下面,我们使用一个简单的图表来表示客户机、服务器和 RMI 注册表之间的关系,绿色 的数字代表顺序:

二、参数传递

前面的例子没有涉及到参数的传递。如果我们需要向远程方法传递参数,或者要从远程方 法接受返回值,是不是有什么特殊的约定呢?不错,如果我们要在客户机和服务器之间传递 参数,则该对象要么是实现Serializable接口的对象,要么是扩展自UnicastRemoteObject的 对象,这两种对象是有差别的。

如果参数是实现Serializable接口的对象,则该对象是按值传递的,也就是把这整个对象 传递到远程方法中。请看下面的例子,我们定义了一个ISerializableWorker接口,扩展自 Serializable接口,客户端创建一个SerializableWorkerImp对象wk,并把它传递到服务器端 ,服务器端调用wk.work()方法,这个方法在服务器端执行,这就说明了我们成功把这个对象 传递到了服务器端。服务器端返回的String对象,也可以成功传递到客户端。

ISerializableWorker.javapackage rmistudy;23 import java.io.Serializable;45  public  interface ISerializableWorker extends Serializable  {6   public  void work();7 }SerializableWorkerImp.java1package rmistudy;23public class SerializableWorkerImp implements ISerializableWorker {45  public void work() {6    System.out.println("该信息由SerializableWorker对象输出。");7  }89}IServer.java1package rmistudy;23import java.rmi.Remote;4import java.rmi.RemoteException;56public interface IServer extends Remote {7  public void doSomeThing() throws RemoteException;8  public String doSomeThing(ISerializableWorker wk) throws RemoteException;9}ServerImp.java1package rmistudy;23import java.rmi.*;4import java.rmi.server.*;56public class ServerImp extends UnicastRemoteObject implements IServer {78  public ServerImp() throws RemoteException {9    super();10  }111213  public void doSomeThing() throws RemoteException {1415    System.out.println("不带参数的远程函数doSomeThing()被调用,该信息显示 在服务器端。");1617  }1819  public String doSomeThing(ISerializableWorker wk) throws RemoteException {20    wk.work();21    return new String("调用成功,该信息来自服务器端。");22  }2324  /** *//**25   * @param args26   */27  public static void main(String[] args) {28    ServerImp server = null;2930    try{31      server = new ServerImp();32    }catch(Exception e){33      System.out.println("创建远程对象失败:");34      System.out.println(e.getMessage());35      System.exit(0);36    }3738    try{39      java.rmi.Naming.rebind("//localhost/MyServer", server);40      System.out.println("远程对象绑定成功。");41    }catch(Exception e){42      System.out.println("远程对象绑定失败:");43      System.out.println(e.getMessage());44      System.exit(0);45    }46  }4748}

Client.java

1package rmistudy;23import java.rmi.*;45public class Client {67  /** *//**8   * @param args9   */10  public static void main(String[] args) {11    IServer server = null;1213    try{14      server = (IServer)Naming.lookup("//localhost/MyServer");15      System.out.println("查找远程对象成功。");16    }catch(Exception e){17      System.out.println("查找远程对象失败:");18      System.out.println(e.getMessage());19      System.exit(0);20    }2122    try{23      server.doSomeThing();24      System.out.println("调用doSomeThing()成功。");25      String str = server.doSomeThing(new SerializableWorkerImp());26      System.out.println("调用带序列化参数的doSomeThing()成功");27      System.out.println("从服务器端返回的字符串:"+str);28    }catch(Exception e){29      System.out.println("调用doSomeThing()失败:");30      System.out.println(e.getMessage());31      System.exit(0);32    }3334  }3536}37

程序的运行方法同前,我就不再罗嗦了。这里需要注意的是,该示例在单机上运行可以, 但是真的在分布环境下运行就会出错,毕竟,别人要把一个对象传递到你的机器上,怎么着 你也要放着别人的对象搞破坏吧。最后我们会讨论安全问题。

另外一种参数的传递方式,就是按照引用传递,如果作为参数的对象是扩展自 java.rmi.server.UnicastRemoteObject类的话,那么该对象传递给远程方法的只是它的引用 。比如,客户端创建了一个扩展自java.rmi.server.UnicastRemoteObject的对象A,把对象A 传递到服务器端,这个时候服务器端得到的只是对象A的引用,如果服务器调用对象A的方法 ,这个方法就会在客户端执行。

下面的例子说明了这一点,我们定义IRefWorker接口和RefWorkerImp类,在客户端创建 RefWorkerImp类的对象,把该对象传递到服务器端,服务器端调用该对象的方法,你会发现 该方法在客户端执行。

IRefWorker.java1package rmistudy;23import java.rmi.Remote;4import java.rmi.RemoteException;56public interface IRefWorker extends Remote {7  public void work() throws RemoteException;8}RefWorkerImp.java1package rmistudy;23import java.rmi.RemoteException;4import java.rmi.server.RMIClientSocketFacTory;5import java.rmi.server.RMIServerSocketFacTory;6import java.rmi.server.UnicastRemoteObject;78public class RefWorkerImp extends UnicastRemoteObject implements IRefWorker {910  public RefWorkerImp() throws RemoteException {11    super();12  }1314  public void work() throws RemoteException {15    System.out.println("该方法在服务器端调用,在客户端执行。");16  }1718}IServer.java1package rmistudy;23import java.rmi.Remote;4import java.rmi.RemoteException;56public interface IServer extends Remote {7  public void doSomeThing() throws RemoteException;8  public String doSomeThing(ISerializableWorker wk) throws RemoteException;9  public void doSomeThing(IRefWorker wk) throws RemoteException;10}

ServerImp.java

该类中实现接口中定义的方法,和前面的代码相比,多了如下一行

1public void doSomeThing(IRefWorker wk) throws RemoteException{2    wk.work();3  }Client.java1package rmistudy;23import java.rmi.*;45public class Client {67  /** *//**8   * @param args9   */10  public static void main(String[] args) {11    IServer server = null;1213    try{14      server = (IServer)Naming.lookup("//localhost/MyServer");15      System.out.println("查找远程对象成功。");16    }catch(Exception e){17      System.out.println("查找远程对象失败:");18      System.out.println(e.getMessage());19      System.exit(0);20    }2122    try{23      server.doSomeThing();24      System.out.println("调用doSomeThing()成功。");25      String str = server.doSomeThing(new SerializableWorkerImp());26      System.out.println("调用带序列化参数的doSomeThing()成功");27      System.out.println("从服务器端返回的字符串:"+str);28      server.doSomeThing(new RefWorkerImp());29      System.out.println("调用带引用参数的doSomeThing()成功");30    }catch(Exception e){31      System.out.println("调用doSomeThing()失败:");32      System.out.println(e.getMessage());33      System.exit(0);34    }3536  }3738}

程序的运行方法同前,不再重复。

三、安全管理与授权策略

前面提到过,前面的示例代码,如果真正运行到分布式环境下的话,是会出错的,原因就 在于安全性问题。J2EE中的安全管理广泛,我们这里仅仅只用到授权,比如我们可以只授权 远程程序访问某一个文件夹或某一个文件,或者只授权远程程序访问网络等等。

要使用授权,需要一个授权文件,我们新建一个Policy.txt文件,为了简单起见,我们授 权远程程序可以访问所有的本地资源:

1grant{2 permission java.security.AllPermission "","";3};

然后,我们需要在服务器端程序中载入安全管理器,我们这里使用默认的 RMISecurityManager,下面是经过修改了的ServerImp.java中的mian()函数:

1public static void main(String[] args) {2    ServerImp server = null;3    4    try{5      System.setSecurityManager(new RMISecurityManager());6    }catch(Exception e){7      System.out.println("加载安全管理器失败:");8      System.out.println(e.getMessage());9      System.exit(0);10    }1112    try{13      server = new ServerImp();14    }catch(Exception e){15      System.out.println("创建远程对象失败:");16      System.out.println(e.getMessage());17      System.exit(0);18    }1920    try{21      java.rmi.Naming.rebind("//localhost/MyServer", server);22      System.out.println("远程对象绑定成功。");23    }catch(Exception e){24      System.out.println("远程对象绑定失败:");25      System.out.println(e.getMessage());26      System.exit(0);27    }28  }

然后,我们需要这样运行服务器端:

java -Djava.security.policy=Policy.txt rmistudy.ServerImp

给几个贴图:

1.运行服务器:

2.运行客户端:

3.运行客户端后服务器的反应:

总结

J2EE规范虽然庞大而复杂,但是如果我们分开来学习,也是可以逐步理解的。J2EE包含企 业数据、企业通讯、企业服务、企业Web支持和企业应用程序等方面。而我们的RMI就是企业 通讯中的一种,另外一种就是日益流行起来的Web Service通讯,至于其它通讯架构,我们大 可以到需要的时候再去学习,比如CORBA。

EJB是架构在RMI基础上的,但是它太复杂,很多时候使用简单的RMI就可以解决很多问题 ,比如科学领域的分布式计算。大家一定听说过找外星人的SETI项目,它就是利用全球志愿 者的个人PC来进行分布式的运算。在我们中国,大型机还比较缺乏,如果我们的某个实验室 需要强大的计算能力,大可以向SETI项目学习。而使用RMI,建立分布式计算平台是多么的简 单。

有希望在的地方,痛苦也成欢乐

简单理解RMI

相关文章:

你感兴趣的文章:

标签云: