RMI學習以及深入講解

Java RMI (Remote Method Invocation 遠程方法調用)是用Java在JDK1.1中實現的,它大大增強了Java開發分佈式應用的能力。Java作爲一種風靡一時的網絡開發語言,其巨大的威力就體現在它強大的開發分佈式網絡應用的能力上,而RMI就是開發百分之百純Java的網絡分佈式應用系統的核心解決方案之一。其實它可以被看作是RPC的Java版本。但是傳統RPC並不能很好地應用於分佈式對象系統。而Java RMI 則支持存儲於不同地址空間的程序級對象之間彼此進行通信,實現遠程對象之間的無縫遠程調用。RMI目前使用Java遠程消息交換協議JRMP(Java Remote Messaging Protocol)進行通信。JRMP是專爲Java的遠程對象制定的協議。因此,Java RMI具有Java的"Write Once,Run Anywhere"的優點,是分佈式應用系統的百分之百純Java解決方案。用Java RMI開發的應用系統可以部署在任何支持JRE(Java Run Environment Java,運行環境)的平臺上。但由於JRMP是專爲Java對象制定的,因此,RMI對於用非Java語言開發的應用系統的支持不足。不能與用非Java語言書寫的對象進行通信。本文擬從程序的角度舉例介紹怎樣利用RMI實現Java分佈式應用。 

 
一、RMI系統運行機理

RMI應用程序通常包括兩個獨立的程序:服務器程序和客戶機程序。典型的服務器應用程序將創建多個遠程對象,使這些遠程對象能夠被引用,然後等待客戶機調用這些遠程對象的方法。而典型的客戶機程序則從服務器中得到一個或多個遠程對象的引用,然後調用遠程對象的方法。RMI爲服務器和客戶機進行通信和信息傳遞提供了一種機制。

在與遠程對象的通信過程中,RMI使用標準機制:stub和skeleton。遠程對象的stub擔當遠程對象的客戶本地代表或代理人角色。調用程序將調用本地stub的方法,而本地stub將負責執行對遠程對象的方法調用。在RMI中,遠程對象的stub與該遠程對象所實現的遠程接口集相同。調用stub的方法時將執行下列操作:(1) 初始化與包含遠程對象的遠程虛擬機的連接;(2) 對遠程虛擬機的參數進行編組(寫入並傳輸);(3) 等待方法調用結果;(4) 解編(讀取)返回值或返回的異常;(5) 將值返回給調用程序。爲了向調用程序展示比較簡單的調用機制,stub將參數的序列化和網絡級通信等細節隱藏了起來。在遠程虛擬機中,每個遠程對象都可以有相應的skeleton(在JDK1.2環境中無需使用skeleton)。Skeleton負責將調用分配給實際的遠程對象實現。它在接收方法調用時執行下列操作:(1) 解編(讀取)遠程方法的參數;(2) 調用實際遠程對象實現上的方法;(3) 將結果(返回值或異常)編組(寫入並傳輸)給調用程序。stub和skeleton由rmic編譯器生成。

利用RMI編寫分佈式對象應用程序需要完成以下工作:(1) 定位遠程對象。應用程序可使用兩種機制中的一種得到對遠程對象的引用。它既可用RMI的簡單命名工具rmiregistry來註冊它的遠程對象,也可以將遠程對象引用作爲常規操作的一部分來進行傳遞和返回。(2)與遠程對象通信。遠程對象間通信的細節由RMI處理,對於程序員來說,遠程通信看起來就像標準的Java方法調用。(3)給作爲參數或返回值傳遞的對象加載類字節碼。因爲RMI允許調用程序將純Java對象傳給遠程對象,所以,RMI將提供必要的機制,既可以加載對象的代碼又可以傳輸對象的數據。在RMI分佈式應用程序運行時,服務器調用註冊服務程序以使名字與遠程對象相關聯。客戶機在服務器上的註冊服務程序中用遠程對象的名字查找該遠程對象,然後調用它的方法。

二、對象序列化

在RMI分佈式應用系統中,服務器與客戶機之間傳遞的Java對象必須是可序列化的對象。不可序列化的對象不能在對象流中進行傳遞。對象序列化擴展了核心Java輸入/輸出類,同時也支持對象。對象序列化支持把對象編碼以及將通過它們可訪問到的對象編碼變成字節流;同時,它也支持流中對象圖形的互補重構造。序列化用於輕型持久性和藉助於套接字或遠程方法調用(RMI)進行的通信。序列化中現在包括一個 API(Application Programming Interface,應用程序接口),允許獨立於類的域指定對象的序列化數據,並允許使用現有協議將序列化數據域寫入流中或從流中讀取,以確保與缺省讀寫機制的兼容性。

爲編寫應用程序,除多數瞬態應用程序外,都必須具備存儲和檢索 Java對象的能力。以序列化方式存儲和檢索對象的關鍵在於提供重新構造該對象所需的足夠對象狀態。存儲到流的對象可能會支持 Serializable(可序列化)或 Externalizable(可外部化)接口。對於Java對象,序列化形式必須能標識和校驗存儲其內容的對象所屬的 Java類,並且將該內容還原爲新的實例。對於可序列化對象,流將提供足夠的信息將流的域還原爲類的兼容版本。對於可外部化對象,類將全權負責其內容的外部格式。序列化 Java 對象的目的是:提供一種簡單但可擴充的機制,以序列化方式維護 Java對象的類型及安全屬性;具有支持編組和解編的擴展能力以滿足遠程對象的需要;具有可擴展性以支持 Java 對象的簡單持久性;只有在自定義時,才需對每個類提供序列化自實現;允許對象定義其外部格式。

三、分佈式應用的實現和運行步驟

編寫Java RMI分佈式應用程序的步驟主要包括以下幾步:

(1) 將遠程類的功能定義爲Java接口。在Java中,遠程對象是實現遠程接口的類的實例。在遠程接口中聲明每個要遠程調用的方法。遠程接口具有如下特點:1) 遠程接口必須聲明爲public。如果不這樣,則除非客戶端與遠程接口在同一個包內,否則當試圖裝入實現該遠程接口的遠程對象時會得到錯誤結果。2) 遠程對象擴展java.rmi.Remote接口。3) 除了所有應用程序特定的例外之外,每個方法還必須拋出java.rmi.RemoteException例外。4) 任何作爲參數或返回值傳送的遠程對象的數據類型必須聲明爲遠程接口類型,而不是實現類。

(2) 編寫和實現服務器類。該類是實現(1)中定義的遠程接口。所以在該類中至少要聲明實現一個遠程接口,並且必須具有構造方法。在該類中還要實現遠程接口中所聲明的各個遠程方法。

(3) 編寫使用遠程服務的客戶機程序。在該類中使用java.rmi.Naming中的lookup()方法獲得對遠程對象的引用,依據需要調用該引用的遠程方法,其調用方式和對本地對象方法的調用相同。

實現了服務器和客戶機的程序後,就是編譯和運行該RMI系統。其步驟有:
(1) 使用javac編譯遠程接口類,遠程接口實現類和客戶機程序。
(2) 使用rmic編譯器生成實現類的stub和skeleton。
(3) 啓動RMI註冊服務程序rmiregistry。
(4) 啓動服務器端程序。
(5) 啓動客戶機程序。

Java與.NET都提供了遠程處理功能,但不完全相同.Java遠程處理是通過一個“共享接口”實現的,而.NET可以通過一個“共享命令集”實現。下面就這兩種方式來具體說明。
  
   Java 遠程處理
   Java遠程方法調用(RMI)提供了Java程序語言的遠程通訊功能,這種特性使客戶機上運行的程序可以調用遠程服務器上的對象,使Java編程人員能夠在網絡環境中分佈操作。
   創建一個簡單的Java分佈式遠程方法調用程序可以按以下幾個步驟操作,
  
   一、定義遠程接口:
   在 Java 中,遠程對象是實現遠程接口的類的實例, 遠程接口聲明每個要遠程調用的方法。在需要創建一個遠程對象的時候,我們通過傳遞一個接口來隱藏基層的實施細節,客戶通過接口句柄發送消息即可。
   遠程接口具有如下特點:
   1) 遠程接口必須爲public屬性。如果不這樣,除非客戶端與遠程接口在同一個包內,否則 當試圖裝入實現該遠程接口的遠程對象時,調用會得到錯誤結果。
   2) 遠程接口必須擴展接口java.rmi.Remote。
   3) 除與應用程序本身特定的例外之外,遠程接口中的每個方法都必須在自己的throws從句中 聲明java.rmi.RemoteException。(或 RemoteException 的父類)。
   4) 作爲參數或返回值傳遞的一個遠程對象(不管是直接,還是本地對象中嵌入)必須聲明爲遠 程接口,而不應聲明爲實施類。
   下面是遠程接口的接口RmiSample的定義

java 代碼
  1. package com.robin.demo.rmi.interf;   
  2.   
  3. import java.rmi.Remote;   
  4. import java.rmi.RemoteException;   
  5.   
  6. public interface RmiSample extends Remote {   
  7.     public int sum(int a, int b) throws RemoteException;   
  8. }   

 二、實現遠程接口:
   遠程對象實現類必須擴展遠程對象java.rmi.UnicastRemoteObject類,並實現所定義的遠程接口。遠程對象的實現類中包含實現每個遠程接口所指定的遠程方法的代碼。這個類也可以含有附加的方法,但客戶只能使用遠程接口中的方法。因爲客戶是指向接口的一個句柄,而不是它的哪個類。必須爲遠程對象定義構造函數,即使只准備定義一個默認構造函數,用它調用基礎類構造函數。因爲基礎類構造函數可能會拋出 java.rmi.RemoteException,所以即使別無它用必須拋出java.rmi.RemoteException例外。
   以下是遠程對象實現類的聲明:

java 代碼
  1. package com.robin.demo.rmi.impl;   
  2.   
  3. import java.rmi.RemoteException;   
  4. import java.rmi.server.UnicastRemoteObject;   
  5.   
  6. import com.robin.demo.rmi.interf.RmiSample;   
  7.   
  8.   
  9. public class RmiSampleImpl extends UnicastRemoteObject implements RmiSample {   
  10.     /**  
  11.      *   
  12.      */  
  13.     private static final long serialVersionUID = 2742977636753958461L;   
  14.   
  15.     public RmiSampleImpl() throws RemoteException {   
  16.         super();   
  17.     }   
  18.   
  19.     public int sum(int a, int b) throws RemoteException {   
  20.         return a + b;   
  21.     }   
  22.   
  23. }   


  
   三、編寫服務器類:
   包含 main 方法的類可以是實現類自身,也可以完全是另一個類。下面通過RmiSampleServer 來創建一個遠程對象的實例,並通過java.rmi.registry.LocateRegistry類的createRegistry 方法從指定端口號啓動註冊服務程序,也可以通過執行 rmiregistry 命令啓動註冊服務程序,註冊服務程序的缺省運行端口爲 1099。必須將遠程對象名字綁定到對遠程對象的引用上: Naming.rebind("//localhost:8808/SAMPLE-SERVER" , Server);
   以下是服務器類的聲明:

java 代碼
  1. package com.robin.demo.rmi.server;   
  2.   
  3. import java.net.MalformedURLException;   
  4. import java.rmi.Naming;   
  5. import java.rmi.RemoteException;   
  6. import java.rmi.registry.LocateRegistry;   
  7.   
  8. import com.robin.demo.rmi.impl.RmiSampleImpl;   
  9.   
  10.   
  11. public class RmiSampleServer {   
  12.   
  13.     /**  
  14.      * @param args  
  15.      */  
  16.     public static void main(String[] args) {   
  17.         try{   
  18.             LocateRegistry.createRegistry(8808);   
  19.             RmiSampleImpl server= new RmiSampleImpl();   
  20.             Naming.rebind("//localhost:8808/SAMPLE-SERVER" , server);   
  21.         }catch (MalformedURLException me){   
  22.             System.out.println("Malformed URL: " + me.toString());   
  23.         }catch(RemoteException re){   
  24.             System.out.println("Remote Exception: "+re.toString());   
  25.         }   
  26.     }   
  27.   
  28. }   


  
   四、編寫使用遠程服務的客戶機類:
   客戶機類的主要功能有兩個,一是通過Naming.lookup方法來構造註冊服務程序 stub 程序實例,二是調用服務器遠程對象上的遠程方法。
   以下是服務器類的聲明:

java 代碼
  1. package com.robin.demo.rmi.client;   
  2.   
  3. import java.rmi.Naming;   
  4. import java.rmi.RemoteException;   
  5.   
  6. import com.robin.demo.rmi.interf.RmiSample;   
  7.   
  8.   
  9. public class RmiSampleClient {   
  10.   
  11.     /**  
  12.      * @param args  
  13.      */  
  14.     public static void main(String[] args) {   
  15.         try {   
  16.             String url = "//localhost:8808/SAMPLE-SERVER";   
  17.             RmiSample RmiObject = (RmiSample) Naming.lookup(url);   
  18.             System.out.println(" 1 + 2 = " + RmiObject.sum(1, 2));   
  19.         } catch (RemoteException rex) {   
  20.             System.out.println("Error in lookup: " + rex.toString());   
  21.         } catch (java.net.MalformedURLException me) {   
  22.             System.out.println("Malformed URL: " + me.toString());   
  23.         } catch (java.rmi.NotBoundException ne) {   
  24.             System.out.println("NotBound: " + ne.toString());   
  25.         }   
  26.   
  27.     }   
  28.   
  29. }   

   五、編譯代碼:
   要編譯 Java 源文件,請運行 javac 命令:
   javac RmiSample.java RmiSampleImpl.java RmiSampleServer.java RmiSampleClient.java
  
   六、爲遠程對象實現創建根和幹:
   要創建存根程序和骨架文件,應以包含遠程對象實現的已編譯類包全名運行 rmic 編譯器。
   存根(Stub)是遠程對象在客戶端的代理,它將RMI調用傳遞給服務器端的骨架(Skeleton),後者負責將該調用傳遞給實際的遠程方法輸入如下:
   D:/RMI>rmic -d D:/RMI RmiSampleImpl 執行這個命令, 若rmic成功運行,RMI目錄下就會多出兩個新類: RmiSampleImpl_Stub.class RmiSampleImpl_Skel.class 它們分別對應的是存根(stub)和骨架(skeleton).
  
   七、運行代碼:
   運行服務端程序:在Windows下,輸入下列命令,在後臺啓動RmiSampleServer程序:
   D:/RMI>java RmiSampleServer
   運行客戶端程序:
   D:/RMI>java RmiSampleClient
   客戶端輸出: 1 + 2 = 3 
 
修改上面內容:(因爲上面服務端程序和客戶端程序在同一臺電腦上)
服務器端程序不變
客戶端修改成  String url = "//服務端IP:8808/SAMPLE-SERVER";   
另外特別說明:經測試驗證了:客戶端的接口(包括包名)要和服務端接口和包名相同才能通過
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章