摘要:RMI是開發(fā)Java網(wǎng)絡(luò)分布式應(yīng)用系統(tǒng)的一個重要框架,開發(fā)人員通過運用RMI框架將更易于分布式系統(tǒng)的開發(fā)。該文詳細(xì)介紹了RMI的運行機制,并對運用RMI框架進(jìn)行分布式系統(tǒng)的開發(fā)步驟進(jìn)行了闡述,最后給出了RMI技術(shù)的具體應(yīng)用實例和實現(xiàn)方法。
關(guān)鍵詞:RMI;Java;Stub;分布式
中圖分類號:TP393 文獻(xiàn)標(biāo)識碼:A 文章編號:1009-3044(2014)01-0051-03
1 概述
由于單臺計算機的計算能力有限,在實際的應(yīng)用中,我們常常要將計算任務(wù)分成多個子任務(wù),將每個子任務(wù)放到網(wǎng)絡(luò)中不同的計算機上進(jìn)行計算,實現(xiàn)分布式并行計算,以加快計算速度。在分布式計算模型中不管采取何種方式,主要是將分部在不同計算機上的對象間發(fā)送的消息轉(zhuǎn)換為字節(jié)序列,然后通過套接字建立連接并傳輸這些字節(jié)序列。在網(wǎng)絡(luò)連接和傳輸消息時還要考慮出現(xiàn)的各種故障和安全問題以及對象垃圾收集機制等等問題。
在具體的分布式系統(tǒng)實現(xiàn)過程中,一種是采用基于消息方式實現(xiàn)各個節(jié)點間的通信。當(dāng)系統(tǒng)要通信時就向外發(fā)送消息,消息可以是字節(jié)流、字節(jié)數(shù)組,其他系統(tǒng)接收到消息后則進(jìn)行相應(yīng)的業(yè)務(wù)處理。這種系統(tǒng)間通信的方式,通?;诰W(wǎng)絡(luò)協(xié)議來實現(xiàn),常用的實現(xiàn)系統(tǒng)間通信的協(xié)議有:TCP/IP和UDP/IP。另一種是采用基于遠(yuǎn)程調(diào)用方式實現(xiàn)系統(tǒng)間的通信。這種方式當(dāng)系統(tǒng)間要通信時,可通過調(diào)用本地的一個Java接口的方法,透明地調(diào)用遠(yuǎn)程的Java實現(xiàn)。具體的細(xì)節(jié)則由Java或框架來完成,盡可能地使系統(tǒng)間的通信和系統(tǒng)內(nèi)一樣,讓使用者感覺調(diào)用遠(yuǎn)程方法同調(diào)用本地方法一樣。
因為開發(fā)一個完善的分布式軟件系統(tǒng)相當(dāng)復(fù)雜,如果采用基于消息的方式實現(xiàn)分布式通信,相當(dāng)麻煩。開發(fā)人員不僅僅要關(guān)注對數(shù)據(jù)的業(yè)務(wù)處理,還要關(guān)注很多純技術(shù)細(xì)節(jié)。而基于遠(yuǎn)程調(diào)用方式實現(xiàn)分布式通信的方法解放了開發(fā)人員的一些純技術(shù)細(xì)節(jié)問題,使開發(fā)人員能夠更專注于業(yè)務(wù)數(shù)據(jù)的處理。Java為我們開發(fā)分布式網(wǎng)絡(luò)應(yīng)用提供了比較完善的遠(yuǎn)程方法框架,那就是Java RMI(Remote Method Invocation,遠(yuǎn)程方法調(diào)用)。通過RMI,可以很方便地讓Java程序調(diào)用網(wǎng)絡(luò)中其他計算機上的Java方法。
2 RMI系統(tǒng)運行機制
RMI是Java用于實現(xiàn)透明遠(yuǎn)程調(diào)用的重要機制。在遠(yuǎn)程調(diào)用中,客戶端僅有服務(wù)器端提供的接口??蛻舳送ㄟ^此接口實現(xiàn)對遠(yuǎn)程服務(wù)器端的方法調(diào)用。
RMI服務(wù)器端通過啟動RMIRegistry (RMIRegistry是運行在服務(wù)器上的一個后臺進(jìn)程,且必須在服務(wù)進(jìn)程啟動之前啟動)在一個端口上監(jiān)聽對外提供的接口,其實現(xiàn)實例以字符串的方式綁定到RMI注冊對象上。RMI客戶端程序采用命名服務(wù)機制通過注冊表獲取遠(yuǎn)程對象的存根stub。當(dāng)要調(diào)用遠(yuǎn)程方法時,通過此stub將被訪問的遠(yuǎn)程對象的名字、被調(diào)用的方法描述和相關(guān)的參數(shù)封裝成一個對象,序列化成流后傳輸?shù)絉MI服務(wù)器端。RMI服務(wù)器端skeleton接收到客戶端的請求對象后,解析其中的對象字符串、方法和參數(shù),通過對象字符串和訪問的方法名稱來反射獲取到方法實例對象,傳入?yún)?shù)完成對服務(wù)器端對象實例的調(diào)用。然后獲得方法調(diào)用產(chǎn)生的返回值或者異常,并對其進(jìn)行序列化然后返回給客戶端??蛻舳说膕tub接收到服務(wù)器端skeleton發(fā)送過來的返回值或者異常的序列化字節(jié)流后,對其進(jìn)行反序列化,就得到調(diào)用遠(yuǎn)程方法的返回結(jié)果。RMI的具體運行機制如圖1。
圖1 RMI運行機制
3 RMI技術(shù)應(yīng)用實例開發(fā)步驟
3.1 創(chuàng)建遠(yuǎn)程接口
RMI中要求遠(yuǎn)程對象所屬的類實現(xiàn)一個遠(yuǎn)程接口,遠(yuǎn)程對象必須通過遠(yuǎn)程接口聲明服務(wù)。在遠(yuǎn)程接口中聲明可以被客戶程序訪問的遠(yuǎn)程方法。此接口必須要直接或間接繼承java.rmi.Remote接口。由于遠(yuǎn)程方法調(diào)用依賴于網(wǎng)絡(luò)通信,而網(wǎng)絡(luò)通信是不可靠的,一旦服務(wù)器端或客戶端有一方斷開連接,或者網(wǎng)絡(luò)出現(xiàn)故障,此次通信就會失敗。所以在接口中的所有方法需要聲明拋出java.rmi.RemoteException異常。當(dāng)遠(yuǎn)程方法調(diào)用出現(xiàn)網(wǎng)絡(luò)通信異常時,RMI框架拋出RemoteException異常,客戶端捕獲這種異常,并進(jìn)行相應(yīng)的處理。
實例代碼如下:
import java.rmi.*;
public interface Account extends Remote{
public int add(int i,int j) throws RemoteException;
}
3.2 實現(xiàn)遠(yuǎn)程接口
遠(yuǎn)程接口中定義的遠(yuǎn)程方法的具體實現(xiàn)都在遠(yuǎn)程接口的實現(xiàn)類中,遠(yuǎn)程接口的實現(xiàn)類中也可以定義一些本地方法,這些本地方法不需要在遠(yuǎn)程接口中聲明,也無需拋出RemoteException異常,本地方法只能被本地調(diào)用,不能被遠(yuǎn)程調(diào)用。遠(yuǎn)程接口的實現(xiàn)類需要繼承java.rmi.server.UnicastRemoteObject類。因為RMI框架中關(guān)于遠(yuǎn)程對象的生命周期、基于TCP的連接傳輸、客戶端和服務(wù)器端的遠(yuǎn)程對象、方法、參數(shù)序列化后的流協(xié)議交流功能在UnicastRemoteObject類中實現(xiàn)。所以遠(yuǎn)程接口的實現(xiàn)類必須直接或間接繼承UnicastRemoteObject類。
實例代碼如下:
import java.rmi.*;
import java.rmi.server.UnicastRemoteObject;
public class AccountImpl extends UnicastRemoteObject implements Account{
public AccountImpl() throws RemoteException {
super();
}
public int add (int i,int j) throws RemoteException{ //遠(yuǎn)程方法
System.out.println("調(diào)用add()方法");
int result = i + j;
System.out.println("Server:3 + 5 = " + result);
return result;
}}
3.3 創(chuàng)建服務(wù)器端
服務(wù)器端首先要創(chuàng)建遠(yuǎn)程對象實例,然后向rmiregistry注冊表注冊此遠(yuǎn)程對象實例,把遠(yuǎn)程對象實例與一個名字綁定。rmiregistry注冊表的相關(guān)功能由JDK的安裝目錄里的一個提供命名服務(wù)的注冊表程序rmiregistry.exe完成。
實例代碼如下:
import java.net.*;
import java.rmi.*;
import java.rmi.registry.LocateRegistry;
public class RMIServer {
private static final int PORT = 10002;
private static final String HOST_NAME = "localhost";
public RMIServer() throws RemoteException, MalformedURLException,NotBoundException {
//啟動RMI注冊表線程,并將RMI服務(wù)器綁定在PORT端口上
LocateRegistry.createRegistry(PORT);
System.out.println("Registry created on host computer " + HOST_NAME +
" on port " + Integer.toString(PORT));
Account ac = new AccountImpl(); //實例化遠(yuǎn)程對象
System.out.println("Remote AccountService implementation object created");
String urlString = "http://" + HOST_NAME + ":" + PORT + "/" + "AccountService";
Naming.rebind(urlString, ac); //將遠(yuǎn)程對象的名字綁定到遠(yuǎn)程對象的引用上
System.out.println("Bindings Finished, waiting for client requests.");
}
public static void main(String[] args) {
System.setSecurityManager(new RMISecurityManager());
try {
RMIServer rmi = new RMIServer();
}catch(Exception e){
e.printStackTrace();
}} }
3.4 創(chuàng)建客戶端
客戶端程序首先需要獲得遠(yuǎn)程對象的存根對象,然后通過此存根對象來調(diào)用方法。這些存根由rmic生成。
實例代碼如下:
import java.rmi.*;
import java.rmi.server.*;
import java.rmi.registry.LocateRegistry;
public class RMIClient {
private static final int PORT = 10002;
private static final String HOST_NAME = "localhost";
public RMIClient() {
try {
Account ac = (Account) Naming.lookup("rmi://" + HOST_NAME + ":" + PORT + "/AccountService"); //通過遠(yuǎn)程對象名字查找遠(yuǎn)程對象,并返回遠(yuǎn)程接口的引用
System.out.println("AccountService lookup successful");
int result = ac.add(3,5); //調(diào)用遠(yuǎn)程對象的方法
System.out.println("Client:3 + 5 = " + result);
} catch (Exception e){
e.printStackTrace();
} }
public static void main(String[] args) {
RMIClient rmi = new RMIClient();
} }
4 編譯與運行RMI系統(tǒng)步驟
對程序的具體執(zhí)行步驟需要先編譯然后執(zhí)行注冊程序、服務(wù)器端程序和客戶端程序,在具體的執(zhí)行過程中需要將JDK的安全策略文件java.policy中的java.net.SocketPermission賦予listen,connect,accept權(quán)限。具體編譯與運行RMI系統(tǒng)步驟如下:
1) 使用javac編譯遠(yuǎn)程接口類、遠(yuǎn)程接口實現(xiàn)類、服務(wù)端和客戶端類文件。
運行cmd命令打開控制臺,在控制臺窗口執(zhí)行
c:\rmi>javac *.java
2) 使用rmic編譯器生成實現(xiàn)類的Stub和Skeleton,注意在新版的RMI機制下,只生成Stub文件,Skeleton文件不會生成。
c:\rmi>rmic AccountImpl
3) 運行rmiregistry命令,啟動RMI注冊監(jiān)聽進(jìn)程。
c:\rmi>start rmiregistry (下轉(zhuǎn)第66頁)
(上接第53頁)
4) 運行服務(wù)器端類,注冊RMI對象,服務(wù)器端準(zhǔn)備就緒,等待遠(yuǎn)程客戶端的調(diào)用。
c:\rmi>start java RMIServer
5) 啟動客戶端
c:\rmi>java RMIClient
5 結(jié)束語
通過以上分析可知Java RMI的結(jié)構(gòu)非常清楚,程序員可以利用此框架非常方便、快速的進(jìn)行分布式程序的編寫,而不需要對底層的一些通信細(xì)節(jié)進(jìn)行考慮,大大提高了程序員編寫代碼的效率。而且由于Java自身的純面向?qū)ο筇匦?,其實現(xiàn)非常方便且透明,為使用分布式對象技術(shù)提供了一個可靠的平臺。
參考文獻(xiàn):
[1] Robert Orfali,Dan Harkey.Java與CORBA客戶/服務(wù)器編程[M].2版.北京:電子工業(yè)出版社,2004.
[2] 林昊.分布式Java應(yīng)用基礎(chǔ)與實踐[M].北京:電子工業(yè)出版社,2010.
[3] 孫衛(wèi)琴.Java網(wǎng)絡(luò)編程精解[M].北京:電子工業(yè)出版社,2009.
[4] 孟憲福.分布式對象技術(shù)及其應(yīng)用[M].北京:清華大學(xué)出版社,2008.
[5] 劉丹,程曉,侯德林.一種基于RMI的分布式架構(gòu)設(shè)計[J].計算機應(yīng)用與軟件,2007(9):206-208.