京山游侠

专注技术,拒绝扯淡
posts - 50, comments - 868, trackbacks - 0, articles - 0
  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理

对RMI的简单理解

Posted on 2006-09-15 22:45 京山游侠 阅读(2286) 评论(3)  编辑  收藏 所属分类: J2EE学习及探索

RMI 的简单理解

 

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

 

一、 简单的 RMI 示例:

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

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

IServer.java

 

1 package  rmistudy;
2
3 import  java.rmi.Remote;
4
5 public   interface  IServer  extends  Remote  {
6         public   void  doSomeThing()  throws  java.rmi.RemoteException;
7 }

8
9


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

 

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

ServerImp.java

 

package  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;
 2
 3 import  java.rmi. * ;
 4
 5 public   class  Client  {
 6
 7         public   static   void  main(String[] args)  {
 8               IServer server  =   null ;
 9
10                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               }

18
19                 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
30

 

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

 

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

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

1.JPG

 

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

2.JPG
 

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

3.JPG 

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

 4.JPG

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

5.JPG

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

 

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

 示意图.JPG

二、参数传递

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

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

1 package  rmistudy;
2
3 import  java.io.Serializable;
4
5 public   interface  ISerializableWorker  extends  Serializable  {
6      public   void  work();
7 }


SerializableWorkerImp.java
1package rmistudy;
2
3public class SerializableWorkerImp implements ISerializableWorker {
4
5    public void work() {
6        System.out.println("该信息由SerializableWorker对象输出。");
7    }

8
9}

IServer.java
1package rmistudy;
2
3import java.rmi.Remote;
4import java.rmi.RemoteException;
5
6public interface IServer extends Remote {
7    public void doSomeThing() throws RemoteException;
8    public String doSomeThing(ISerializableWorker wk) throws RemoteException;
9}

ServerImp.java
 1package rmistudy;
 2
 3import java.rmi.*;
 4import java.rmi.server.*;
 5
 6public class ServerImp extends UnicastRemoteObject implements IServer {
 7
 8    public ServerImp() throws RemoteException {
 9        super();
10    }

11
12
13    public void doSomeThing() throws RemoteException {
14        
15        System.out.println("不带参数的远程函数doSomeThing()被调用,该信息显示在服务器端。");
16
17    }

18    
19    public String doSomeThing(ISerializableWorker wk) throws RemoteException{
20        wk.work();
21        return new String("调用成功,该信息来自服务器端。");
22    }

23
24    /**
25     * @param args
26     */

27    public static void main(String[] args) {
28        ServerImp server = null;
29        
30        try{
31            server = new ServerImp();
32        }
catch(Exception e){
33            System.out.println("创建远程对象失败:");
34            System.out.println(e.getMessage());
35            System.exit(0);
36        }

37
38        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    }

47
48}

Client.java
 1package rmistudy;
 2
 3import java.rmi.*;
 4
 5public class Client {
 6
 7    /**
 8     * @param args
 9     */

10    public static void main(String[] args) {
11        IServer server = null;
12        
13        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        }

21        
22        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        }

33
34    }

35
36}

37

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

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

下面的例子说明了这一点,我们定义IRefWorker接口和RefWorkerImp类,在客户端创建RefWorkerImp类的对象,把该对象传递到服务器端,服务器端调用该对象的方法,你会发现该方法在客户端执行。
IRefWorker.java
1package rmistudy;
2
3import java.rmi.Remote;
4import java.rmi.RemoteException;
5
6public interface IRefWorker extends Remote {
7    public void work() throws RemoteException;
8}

RefWorkerImp.java
 1package rmistudy;
 2
 3import java.rmi.RemoteException;
 4import java.rmi.server.RMIClientSocketFactory;
 5import java.rmi.server.RMIServerSocketFactory;
 6import java.rmi.server.UnicastRemoteObject;
 7
 8public class RefWorkerImp extends UnicastRemoteObject implements IRefWorker {
 9
10    public RefWorkerImp() throws RemoteException {
11        super();
12    }

13
14    public void work() throws RemoteException {
15        System.out.println("该方法在服务器端调用,在客户端执行。");
16    }

17
18}

IServer.java
 1package rmistudy;
 2
 3import java.rmi.Remote;
 4import java.rmi.RemoteException;
 5
 6public 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.java
 1package rmistudy;
 2
 3import java.rmi.*;
 4
 5public class Client {
 6
 7    /**
 8     * @param args
 9     */

10    public static void main(String[] args) {
11        IServer server = null;
12        
13        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        }

21        
22        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        }

35
36    }

37
38}

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

三、安全管理与授权策略
  前面提到过,前面的示例代码,如果真正运行到分布式环境下的话,是会出错的,原因就在于安全性问题。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        }

11        
12        try{
13            server = new ServerImp();
14        }
catch(Exception e){
15            System.out.println("创建远程对象失败:");
16            System.out.println(e.getMessage());
17            System.exit(0);
18        }

19
20        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.运行服务器:
6.JPG

2.运行客户端:
7.JPG

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

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

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

评论

# re: 对RMI的简单理解  回复  更多评论   

2007-01-25 12:41 by anders0913
请问要是在Tomcat中直接使用RMI有什么好的方法可以解决安全问题。不想更改Tomcat的安全策略文件?

# re: 对RMI的简单理解  回复  更多评论   

2007-07-30 16:50 by 小白之家
恩,介绍算详细吧,可是blog主最好是给些代码

# re: 对RMI的简单理解  回复  更多评论   

2009-04-18 23:42 by 勉勉强强
谢谢LZ清晰地讲解,代码和运行结果,理论和注意事项都很完备,多谢~!

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


网站导航: