摘 要 由於Java具有跨平台、代碼可移植性、安全高效等廣泛而強大的功能,因而在開發網路分布式應用的時候,可以用它自身的機制實現分散式運算,一種基於Java的遠程方法調用(RMI)為我們開發企業分布式應用提供了行之有效解決方案。
關鍵詞 Java RMI 企業分布式應用
概述
隨著電力公司資訊化建設的不斷深入和發展,企業內部和企業與企業之間對資訊、對資料的交換量大大增加,這些資訊與資料越來越需要在不同的電腦網路間傳 送和交流。同時,由於各單位、各部門之間的現存的電腦網路硬體裝置與作業系統千差萬別,應用水平也參差不齊,因此,開發出跨平台、可移植、高效安全的網 絡分布式應用來服務於電力企業,就顯得尤為重要。
在當今的編程術語裡,分散式運算已經成為很常見的詞,它將企業的業務資料和程式分布在網路的不同物理位置上,通過調動網路上多台電腦的處理能力,發揮遠程調用資料的功能。
遠程方法調用(Remote Method Invocation ,RMI),可以在不同的Java虛擬機器(JVM)之間實現對象與對象的通訊。JVM可以位於相同或不同電腦上,在多個JVM中,一個JVM可以調用儲存在其它JVM的對象的方法。
本文主要介紹RMI的特點,分析應用RMI進行企業分散式運算的原理,以及利用RMI實現基於Java的企業分布式應用的具體步驟。
遠程方法調用(RMI)的特點
1、TCP編程的缺點
由於Java程式設計語言設計之初就是物件導向和支援網路的,因此,基於對象的RMI機制已經內建在Java平台中。
我們經常會在網路開發中使用TCP/IP編程,這樣,自然而然地就會涉及到Socket(通訊端)編程。但是,使用Socket編程需要大量重複編碼, 在複雜分布式操作時顯得非常麻煩,而且易於出錯。因此,如何快速、高效、安全、可擴充地進行網路分散式運算,是開發人員們一貫追求和倡導的主題。直到RMI 的出現,這種繁雜、低效的開發情況才有很大改觀。
2、RMI編程的特點
當我們利用對象序列化在網路上指派至時,RMI提供了非Java平台無法匹敵的獨特而強大的分散式運算模型,RMI主要有以下特點:
客戶機可以向本地方法一樣調用遠程伺服器上的方法;
可以根據介面指定客戶機/伺服器編程合約;
可以從伺服器對象預設二進位類檔案,自動產生調動/反調動代碼;
將Java編程模型擴充到機器邊界(和Java虛擬機器(JVM)邊界之外),不需要任何特殊文法;
還可以和一個遠程方法調用中的資料同時傳輸行為(代碼)。
儘管RMI不是唯一的企業級遠程對象訪問方案,但它卻是最容易實現的。
3、RMI與CORBA
作為分布式應用程式架構的規範,COBRA首當其衝,它是由對象管理組織(OMG)開發的。與CORBA不同的是,CORBA能夠利用不同程式設計語言(例 如C/C++、Basic等)開發實現分布式應用,而RMI是一種純Java解決方案。在RMI中,程式的所有部分都由Java語言編寫,這樣,開發出來 的程式完全符合Java規範,便於實現跨平台訪問、擴充和移植。按照筆者所在西北電力建設集團公司的情況看,伺服器作業系統主要有Linux和 Windows2000 Server,分別存在於公司和部門當中,它們是不同的系統平台;同時,公司下屬各個工程項目部又距離很遠,近的幾十公裡,遠則達到上千公裡甚至位於國 外,因此跨平台和遠端存取這兩大功能在開發公司專屬應用程式系統時就必須考慮,而RMI恰恰能夠用它的自身特點來滿足編程需要。
RMI基本體繫結構簡介
RMI通過TCP/IP在內部使用Socket,象其名稱暗示的那樣,它能夠協助我們尋找並執行遠程對象的方法。RMI的目的是讓位於不同JVM中的對象,在外觀及行為上都像是本地的對象。
通常,我們把調用這種遠程對象的JVM,稱為客戶機;而把包括這種遠程對象的JVM,稱為伺服器。
儘管對一個遠程對象的引用和獲得對本機物件的引用有所不同,但我們可以把遠程對象像本機物件一樣使用。應用程式並不知道一個對象是遠端還是本地的。實際上,遠程對象上被調用的方法與本機物件上調用的方法,具有相同的文法結構。
作為RMI的底層(會包含複雜的Socket操作),它會自動截獲方法調用,找到遠程對象,然後處理遠程請求。筆者認為,RMI設計的重要之處,就在於不但在設計上實現了遠端存取功能,而且實現了設計的透明性。
RMI的基本體繫結構,概括起來說,由三個抽象層組成:
1、存根/架構層(Stubs/Skeletons Layer)
RMI為我們引入了兩種特殊類型的對象,稱為存根(Stub)和架構(Skeleton),它們組成了RMI的第一層。
在遠程通訊的時候,要利用TCP/IP協議,做很多底層資料的打包傳輸。運用Java技術,我們先要把資料或者對象轉換成位元組流(byte stream),便於網路傳輸,這個過程叫彙集(marshaling);當收到遠程傳來的位元組流後,我們要把流資訊轉換成對象或者資料,這個過程叫解讀 (unmarshaling),它與彙集剛好相反。
Stub和Skeleton層位於實際應用程式之下,建立在Proxy(代理)設 計方案之上。Stub類的作用是遠程伺服器實現的代理的角色,Stub是客戶方對象;Skeleton類用於協助對象通過RMI連結與Stub通訊,它從 鏈路中讀取方法調用的參數,向遠程服務實現對象進行調用,接受傳回值,然後再把傳回值寫回到Stub。
2、遠端參照層(Remote Reference Layer)
遠端參照層定義和支援著RMI串連的調用語義(semantics)。
RMI進行遠端存取要用到JRMP(Java Remote Method Protocol,即Java遠程方法協議),這一層提供專用於JRMP的RemoteRef對象,它位於java.rmi.server包內,代表著遠 程對象的一個控制代碼。RemoteRef使用遠端參照來執行遠程對象的一個遠程方法調用。
3、傳輸層(Transport Layer)
傳輸層在JVM之間建立基於流的網路連接,並且負責設定和管理這些串連。這時候,RMI使用一種線級(wire-level)協議進行基於TCP/IP的串連,該協議就是Java遠程方法協議(JRMP,即Java Remote Method Protocol)。
在JDK版本1.2開始,JRMP不再需要Skeleton,而是使用reflection來建立與遠程服務的串連。為了產生Stub,我們須用rmic。
當前的RMI實現中,傳輸層建立在TCP/IP基礎上,設計用於在客戶和伺服器之間建立一條串連(即使連網有障礙)。
開發的基本步驟
我們使用RMI編寫Client/Server模式(客戶/伺服器)應用程式,包括6個基本步驟:
1) 定義遠程介面
2) 實現遠程介面
3) 準備遠程調用的伺服器對象
4) 產生殘根Stub(客戶代理)和架構Skeleton(伺服器實體)
5) 用rmiregistry找到遠程對象
6) 運行測試RMI分布式應用
開發公司資訊發布系統執行個體
在開發RMI進行分布式訪問之前,需要將各項功能模組化,即把實際應用抽象成符合Java規範的類和介面模型,使這些類和介面之間互相協作,能實現各自獨立的功能,最後,可以把它們組合成統一的網路分布式系統。
現在,我們就以開發公司資訊發布系統為例,把主模組(主要的類檔案)的名稱暫訂為InfoDistributeService(資訊發布服務),為了保持應用開發的資料一致性和清晰度,接下來涉及的其它模組命名也將以這個模組命名為基準。
1、定義遠程介面
Java RMI運行環境要求任何可以遠程調用的方法必須放在遠程介面中。
該遠程介面用來擴充java.rmi.Remote介面,在Java API中,可以發現它沒有任何方法,只是個標誌性介面,這樣,可以讓Java運行環境(JRE)認識每個介面的特殊屬性,以便能夠遠端存取。
因此,按照資訊發布服務的命名(InfoDistributeService),首先須將InfoDistributeRemote定義為遠程介面,同 時僅放入一個供測試的方法 getRemoteInfo()來實現編碼,將所有模組至於建立的enterprise.distribute包中,代碼如下:
// -----------InfoDistributeRemote.java------------------- package enterprise. distribute; import java.rmi.Remote; import java.rmi.RemoteException; public interface InfoDistributeRemote extends Remote{ public String getRemoteInfo() throws RemoteException; } |
2、實現遠程介面
這是一個實現遠程對象的類。如果實現了遠程介面,就能夠覆蓋(override)該對象中的所有方法,因此,遠程對象的實作類別將真正包含我們希望匯出的方法的代碼。
在遠程資訊發布系統中,我們至少實現一個遠程介面的對象,它就是遠程可訪問的對象。這裡,InfoDistributeService類可以為我們產生遠程可訪問對象的執行個體:
// -----------InfoDistributeService.java------------------ package enterprise. distribute; import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; public class InfoDistributeService extends UnicastRemoteObject implements InfoDistributeRemote{ public InfoDistributeService() throws RemoteException{ super(); } // The return value of the method only for testing... public String getRemoteInfo(){ return "Hello! I am a remote object."; } } |
InfoDistributeService類實現遠程介面InfoDistributeRemote,並繼承 java.rmi.server.UnicastRemoteObject。由於符合InfoDistributeRemote介面,因此該類除構造方法 之外,還應有getRemoteInfo()方法,而且必須將該方法實現。
同時我們注意到,getRemoteInfo()方法拋出 了java.rmi.RemoteException異常。由於遠程方法調用過程中,要進行很多的低級網路操作,因此網路錯誤可能在調用過程中隨時發生, 這樣,遠程介面中的每個方法(儘管這裡只有一個getRemoteInfo()方法)都必須拋出RemoteException異常,而 且,java.rmi.RemoteException都要在代碼中顯式處理,即將所有RMI活動涉及的代碼都要放在try-catch塊中。
3、準備供遠程調用的伺服器對象
這是一個作為伺服器使用的類,它是相對於要訪問遠程方法的用戶端而言的。它儲存著綁定的字串和對象。
將遠程對象設定成接受遠程調用就像啟動了偵聽ServerSocket對象的socket伺服器。事實上在使用RMI時,TCP/IP協議的傳輸方式,在幕後發生了很多底層網路操作,但是此刻,使用者不需要知道這些細節,只需要逐步匯出遠程對象:
在Java開發工具的較新版本JDK 1.4中,可以選擇java.rmi.UnicastRemoteObject或者java.rmi.Activation.Activatable。其 中,更多用到的是UnicastRemoteObject,因為它顧名思義,就是在客戶機與伺服器對象執行個體之間建立一對一串連。
4、產生Stub和Skeleton
由RMI產生的調動與反調動的遠程調動代碼也和Java的其它代碼一樣,都包含在尾碼是.class的檔案中。
運用RMI之後,在客戶機上產生的調動參數和反調動傳回值的代碼稱為殘根(Stub),伺服器上產生的反調動參數和進行實際方法調用調動傳回值的代碼稱為架構(Skeleton)。
可以使用RMI內建的命令列工具rmic(即RMI Compiler),先掃描遠程對象的.class檔案,隨之產生殘根與架構代碼。工具rmic的原理1。
5、遠程用戶端:這是一個協助我們訪問遠程方法提供協助的類,它也是終端使用者。我們將使用尋找和調用遠程方法的方法在該類中調用遠程方法。
典型的rmic調用如下(在目前的目錄): 我們有了遠程介面和實現,就可以用rmic對類進行編譯,產生Stub和Skeleton,在命令列視窗中使用以下程式碼:
// compile all java source files javac enterprise\distribute\*.java // make stub and skeleton code rmic enterprise.distribute.InfoDistributeService |
運行完畢後,在目前的目錄產生下列檔案(即調動代碼):
InfoDistributeService_Stub.class
InfoDistributeService_Skel.class
5、用rmiregistry找到遠程對象
匯出伺服器方的對象之後,就可以遠端存取,但客戶機還要設法與這些遠程對象取得聯絡。
由於分布式應用程式可能涉及許多不同機器,因此通訊的所有機器對之間需要建立初始串連,換句話說,就是要設法找到初始遠程對象,這項工作通過rmiregistry命令執行。
JDK開發工具提供了公用程式RMI Registry,用來維護文本名和遠程對象之間的映射,可以進行遠端存取。
在客戶機方,RMI註冊表可以通過同一java.rmi.Naming類的lookup()靜態方法用程式訪問。例如,遠程主機為iServer,遠程 對象執行個體為InfoDistributeService執行個體,遠程連接埠號碼為5678,則客戶機如果尋找遠程主機iServer中的遠程 InfoDistributeService執行個體,那麼引用執行個體時須使用遠程介面InfoDistributeRemote,代碼如下:
InfoDistributeRemote iServer=( InfoDistributeRemote)Naming.lookup("rmi://iServer:5678/InfoDistributeService"); |
在伺服器方,匯出的遠程對象可以通過java.rmi.Naming類在本地註冊rmiregistry的運行執行個體。例如,用rebind()方法將 iService中的InfoDistributeService的執行個體與名稱InfoDistributeService相關聯。
InfoDistributeService iService=new InfoDistributeService(); Naming.rebind("/ InfoDistributeService",iService); |
綜上所述,InfoDistributeRemote是個Java介面,因此iServer實際上是實現InfoDistributeRemote介面 的本機物件執行個體,它不在遠程,而是遠程InfoDistributeService的本地表示。也就是說,它是由前述rmic工具自動產生的殘根碼 InfoDistributeService_Stub.class的本地執行個體。圖3顯示了遠程對象調用的工作原理。
從圖2可以看出,客戶機實現通過iServer變數維護遠程對象的調用。事實上,iServer引用變數是實現InfoDistribute介面的對象 的本地引用,該對象是InfoDistributeService殘根實現。這個殘根和伺服器方的架構一起通過InfoDistributeRemote 介面調動/反調動所有遠程調用。
當我們把InfoDistributeService看成是實現InfoDistributeRemote介面的本地執行個體的時候,殘根代碼只在幕後進行工作,而實現這一切則變得非常透明。
由上述原理,可以進一步設計完善客戶機和伺服器代碼,進而編寫出完整的應用程式。
6、運行測試RMI分布式應用
在確認已經設計好必須的幾大類別模組後,我們開始按以下步驟,運行並測試該資訊發布系統的準系統(即僅僅實現遠程介面中聲明的getRemoteInfo()方法)。
在前面定義的包enterprise.distribute中,執行javac命令編譯尾碼為.java的源檔案。
javac enterprise/distribute/*.java |
接著,用RMIC工具產生殘根與架構。
rmic enterprise.distribute.InfoDistributeService |
編譯之後,需要確定客戶機與伺服器發行版本的內容。因此,需用jar命令,將客戶機與伺服器發行版本封裝成 .jar檔案。 其中,封裝伺服器的檔案命令如下:
jar cvf InfoDistributeService.jar enterprise\distribute\InfoDistributeService.class enterprise\distribute\InfoDistributeRemote.class enterprise\distribute\InfoDistributeService_Stub.class enterprise\distribute\InfoDistributeService_Skel.class |
同樣,封裝客戶機的類似於以上命令。
運行RMI應用程式
完成了第一階段的所有RMI實驗,然後運行資訊發布應用程式。按照Java規範,需要依次啟動下列項目:
啟動RMIRegistry
在代碼目錄中用以下命令啟動rmiregistry執行個體,使之在控制台開始運行。
啟動伺服器
直接啟動伺服器,產生InfoDistributeService遠程對象的執行個體,並向註冊表註冊。
start java -classpath InfoDistributeService.jar enterprise.distribute.InfoDistributeService |
啟動客戶機
最後,用java -classpath RemoteClient.jar 命令啟動客戶機,然後通過rmiregistry找到遠程資訊發布服務,再通過遠程調用得到所需要的遠程資訊。
結論
本文簡要闡述了Java RMI的特點,以及用RMI開發企業分布式應用的主要步驟。以遠程資訊發布系統為例,簡要地說明了遠程對象訪問、遠程方法調用在資訊發布時的原理和實現過程。
為了開發出更符合實際的企業分布式應用,RMI還可以結合對象序列化實現更加強大的功能,為我們開發更加靈活、高效的網路分布式應用系統提供方便。