程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 簡單理解RMI

簡單理解RMI

編輯:關於JAVA

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

可以看到,我們的客戶端程序只用到了 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.java
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.運行服務器:

2.運行客戶端:

3.運行客戶端後服務器的反應:

總結

J2EE規范雖然龐大而復雜,但是如果我們分開來學習,也是可以逐步理解的。J2EE包含企 業數據、企業通訊、企業服務、企業Web支持和企業應用程序等方面。而我們的RMI就是企業 通訊中的一種,另外一種就是日益流行起來的Web Service通訊,至於其它通訊架構,我們大 可以到需要的時候再去學習,比如CORBA。

EJB是架構在RMI基礎上的,但是它太復雜,很多時候使用簡單的RMI就可以解決很多問題 ,比如科學領域的分布式計算。大家一定聽說過找外星人的SETI項目,它就是利用全球志願 者的個人PC來進行分布式的運算。在我們中國,大型機還比較缺乏,如果我們的某個實驗室 需要強大的計算能力,大可以向SETI項目學習。而使用RMI,建立分布式計算平台是多麼的簡 單。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved