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