使用RMI和CORBA進行分布式java程序設計
(
by huihoo.com lizhi)
分布式程序設計討論>>> 英文原文:http://developer.java.sun.com/developer/technicalArticles/RMI/rmi_corba/
RMI和corba是兩種最重要和使用最廣泛的分布式對象系統。 每種都有它的長處和短處。這兩種系統都在從電子商務到衛生保健等不同的行業成功的使用。在項目中使用這兩種分布機制中的任何一種都是一項很困難的任務。本文介紹了RMI和corba的機理和最主要的是顯示了如何開發一個有用的程序(一個從遠程站點下載文件的程序)。于是有以下內容:
- 一個分布式系統的簡介
- 一個RMI和corba的簡介
- 讓你體驗開發一個RMI和corba程序的滋味。
- 說明如何使用RMI和corba從遠程機器交換文件
- 提供一個RMI和Corba的比較。
客戶/服務端模式
客戶/服務模式是一個分布式計算應用。它通過使用一個應用程序(客戶)和另一個程序(服務端)交換數據。在這樣的一個例子里面客戶端和服務端一般使用同樣的語言來編寫,使用相同的協議來相互通信。
在客戶/服務模式應用到各種各樣的地方的過程中,使用低層次的socket來開發是很典型的。使用socket來開發客戶/服務端模式意味著我們必須自己設計一種協議,該協議包含客戶端和服務端都統一的命令集 ,使得客戶端和服務端能夠通過這個協議來通信。例如:HTTP協議提供了一個get的方法。所有的web服務器軟件都集成了該功能,而所有的瀏覽器軟件都能夠使用該功能來獲得資料。
分布式對象模式
分布式對象系統是一個對象集合,通過定義很完善的統一的接口來分隔開的要求服務(客戶端)和功能服務(服務端)。換句話說客戶端和公共服務的提供分隔開,這些服務包括數據表現和執行的代碼。這是一個分辨分布式對象模式和客戶/服務模式的主要不同。
在分布式對象模式里,客戶端發送一個消息到一個對象,由這個對象解釋這個消息然后決定應該由什么服務來完成。這個服務,方法或選擇的完成可能是被一個對象或是被一個broker。RMI和corba就是這種模式的例子。
RMI
RMI是一個分布式對象模式。它使得使用java開發分布式程序更加容易。由于不需要設計協議(這基本是一個錯誤的任務) 使得使用RMI開發分布式程序比使用socket更加容易。在RMI里面設計者就象在調用一個本地的類的方法一樣,而實際上是在調用的時候相應的參數被發送到遠端的對象和然后被解釋。最后結果返回給調用者。
一個 RMI應用的流程
使用 RMI開發一個分布式應用包括如下幾個步驟
1)定義一個遠端的接口
2)實現這個遠端的接口
3)開發一個服務端
4)開發一個客戶端
5)生成Stubs 和Skeletons,運行RMI注冊器,服務端 和客戶端
我們現在通過開發一個文件交換程序來解釋這些步驟
例子:文件交換程序
這個應用允許客戶端從服務端交換(或下載)所有類型的文件。第一步是定義一個遠程的接口,這個接口指定了的簽名方法將被服務端提供和被客戶端調用。
定義一個遠程接口
這個程序的遠程接口在代碼例子1中列出,接口FileInterface提供一個downloadFile這個帶一個字符竄(文件名)變量的的方法,然后返回以一個字符竄序列的形式的相應文件數據。
代碼例子1: FileInterface.java
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface FileInterface extends Remote {
public byte[] downloadFile(String fileName) throws
RemoteException;
}
注意接口FileInterface的如下特征:
- 它必須定義成public,這是為了讓客戶端能夠通過調用遠程接口來間接調用遠程的對象。
- 必須使用從Remote接口擴展過來,這是創建一個遠程的對象的需要。
- 每個接口方法中必須拋出java.rmi.RemoteException錯誤。
實現遠程的接口
下一步是實現遠程的接口FileInterface。實現的例子在代碼例子 2中列出。類FileImpl從UnicastRemoteObject擴展來。這顯示出FileImpl類是用來創建一個單獨的,不能復制的,遠程的對象,
這個對象使用RMI 的默認的基于TCP的通信方式。
代碼例子 2: FileImpl.java
import java.io.*;
import java.rmi.*;
import java.rmi.server.UnicastRemoteObject;
public class FileImpl extends UnicastRemoteObject
implements FileInterface {
private String name;
public FileImpl(String s) throws RemoteException{
super();
name = s;
}
public byte[] downloadFile(String fileName){
try {
File file = new File(fileName);
byte buffer[] = new byte[(int)file.length()];
BufferedInputStream input = new
BufferedInputStream(new FileInputStream(fileName));
input.read(buffer,0,buffer.length);
input.close();
return(buffer);
} catch(Exception e){
System.out.println("FileImpl: "+e.getMessage());
e.printStackTrace();
return(null);
}
}
}
編寫服務端
第3步是實現一個服務端。有3件事服務端需要去做:
1)創建一個RMISecurityManager實例,然后安裝它。
2)創建一個遠程對象的實例(這個例子中是FileImpl )
3)使用RMI注冊工具來注冊這個對象。
代碼例子 3中顯示了如何操作的。
代碼例子 3: FileServer.java
import java.io.*;
import java.rmi.*;
public class FileServer {
public static void main(String argv[]) {
if(System.getSecurityManager() == null) {
System.setSecurityManager(new RMISecurityManager());
}
try {
FileInterface fi = new FileImpl("FileServer");
Naming.rebind("http://127.0.0.1/FileServer", fi);
} catch(Exception e) {
System.out.println("FileServer: "+e.getMessage());
e.printStackTrace();
}
}
}
聲明Naming.rebind("http://127.0.0.1/FileServer", fi) 中假定了RMI注冊工具(RMI registry )使用1099端口并在運行中。如果你在其他的端口運行了RMI注冊工具,你必須在這個聲明中定義。例如如果RMI注冊工具在4500端口運行。你的聲明要變成
Naming.rebind("http://127.0.0.1:4500/FileServer", fi)
另外我們已經同時假定了我們的服務端和RMI注冊工具是運行在同一臺機器上的。如果不是的話你要修改rebind方法中的地址。
編寫客戶端 下一步是編寫一個客戶端,客戶端可以遠程調用遠程接口(FileInterface)中說明的任何一個方法。無論如何實現,客戶端必須先從RMI注冊工具獲得一個遠程對象的引用。當引用獲得后方法downloadFile 被調用。客戶端的例子在代碼例子4中,執行過程中客戶端從命令行中獲得兩個參數,第一個是要下載的文件名,第二個是要下載的機器的地址。對應地址的機器上運行服務端。
代碼例子 4: FileClient.java
import java.io.*;
import java.rmi.*;
public class FileClient{
public static void main(String argv[]) {
if(argv.length != 2) {
System.out.println("Usage: java FileClient fileName machineName");
System.exit(0);
}
try {
String name = "http://" + argv[1] + "/FileServer";
FileInterface fi = (FileInterface) Naming.lookup(name);
byte[] filedata = fi.downloadFile(argv[0]);
File file = new File(argv[0]);
BufferedOutputStream output = new
BufferedOutputStream(new FileOutputStream(file.getName()));
output.write(filedata,0,filedata.length);
output.flush();
output.close();
} catch(Exception e) {
System.err.println("FileServer exception: "+ e.getMessage());
e.printStackTrace();
}
}
}
運行程序
為了運行程序我們必須生成stubs 和 skeletons,為了生成stubs 和 skeletons,我們使用rmic來編譯:
prompt> rmic FileImpl
將會生成兩個文件FileImpl_Stub.class和 FileImpl_Skel.class. stub是客戶端的代理而skeleton是服務端的框架。
下一步是編譯服務端和客戶端。使用javac來編譯。注意如果服務端和客戶端在兩個不同的機器,為了編譯客戶端你必須復制一個FileInterface接口。
最后,到你運行RMI注冊工具和運行服務端和客戶端的時候了。使用rmiregistry 或者 start rmiregistry 命令來運行RMI注冊工具到window系統的默認的端口上,要運行RMI注冊工具在一個其他的端口的話使用端口參數。
prompt> rmiregistry portNumber
RMI注冊工具運行之后,你要運行服務FileServer,因為RMI的安全機制將在服務端發生作用,所以你必須增加一條安全策略。以下是對應安全策略的例子
grant {
permission java.security.AllPermission "", "";
};
注意:這是一條最簡單的安全策略,它允許任何人做任何事,對于你的更加關鍵性的應用,你必須指定更加詳細安全策略。
現在為了運行服務端,你需要除客戶類(FileClient.class)之外的所有的類文件。確認安全策略在policy.txt文件之后,使用如下命令來運行服務器。
prompt> java -Djava.security.policy=policy.txt FileServer
為了在其他的機器運行客戶端程序你需要一個遠程接口(FileInterface.class) 和一個stub(FileImpl_Stub.class)。 使用如下命令運行客戶端
prompt> java FileClient fileName machineName
這里fileName是要下載的文件名,machineName 是要下載的文件所在的機器(也是服務端所在的機器)
如果所有都可以了話,當客戶端運行后,這個文件將下載到本地。
我們提到如果要運行客戶端,我們需要遠程接口和stub,另外一個更好的方法是使用RMI動態類加載,這個方法使得你不必要復制遠程接口和stub。取而代之的是,它們能夠在一個共同的目錄里面被客戶端和服務端查找到。為了做到這個你必須使用以下命令來運行客戶端。
使用如下命令
java -Djava.rmi.server.codebase=http://hostname/locationOfClasses FileClient fileName machineName.
更多的信息看這里Dynamic Code Loading using RMI.
CORBA
CORBA(The Common Object Request Broker Architecture:通用對象請求代理結構)是對象管理組織(ORG)在分布式對象項目方面資助的一個工業標準。CORBA只是一個標準,一個CORBA服務以ORB(Object Request Broker對象要求代理)的形式來運行,市場上有很多可用的CORBA ORB服務軟件例如VisiBroker, ORBIX, 和其他一些ORB軟件。JavaIDL 是其中的一個,它是jdk1.3或jdk1.3以上版本的一個核心開發包。
CORBA的設計是獨立于平臺和語言的,因此CORBA可以在任何平臺上運行,可以定位在網絡的任何地方,能夠使用任何有IDL(Interface Definition Language )映射的語言。
和RMI相類似,CORBA對象使用接口來描述,然而接口在CORBA中是定義在IDL中的。IDL很類似于C++,但是IDL不是編程語言,更多的關于CORBA的介紹在這里有Distributed Programming with Java: Chapter 11 (Overview of CORBA).
CROBA程序的編寫過程
開發CORBA有很多復雜的步驟,如下
1.定義一個IDL
2.把IDL接口映射到java
3.開發server端
4.開發client端
5.運行名字服務,服務端 和客戶端
我們現在一步步的解釋一個基corba的文件交換程序的開發,這有點類似于我們上面講的RMI程序的開發,我們這里使用JavaIDL(一個jdk1.3的核心開發包).
定義接口
當你定義一個corba的接口時,考慮一下服務要支持的操作,在這個程序里面客戶端將包含一個下載文件的方法。代碼例子5顯示了一個FileInterface接口,Data是一個用typedef關鍵字引入的新的類型描述。sequence 在IDL中除了不能定義固定大小外其他都類似于數組,octet是一個8字節的量,相當于java中的byte。downloadFile 方法中的參數是一個string類型并被定義成in 類型。IDL定義了3中傳輸模式:in(用來接收客戶端到服務端的輸入),out(用來接收服務端到客戶端的輸出)和inout(同時用來輸入和輸出)。
代碼例子 5: FileInterface.idl
interface FileInterface {
typedef sequence<octet> Data;
Data downloadFile(in string fileName);
};
一旦你定義了一個IDL接口,你就要開始編譯它。JDK1.3+包含了一個idlj 編譯器,它可以用來映射IDL到java的聲明。
idlj 可以通過不同的命令產生不同的輸出如客戶端的stubs,服務端的skeletons。
-f<side> 參數用來指定產生什么。side是如下的client, server, 或 all 參數分別用來表示客戶端的stubs 和服務端的 skeletons。在這個例子里由于服務端和客戶端在兩個不同的機器上,所以我們在客戶端上使用-fclient 在服務端上使用-fserver 。
現在讓我們編譯FileInterface.idl 來產生服務端的skeletons,使用如下命令
prompt> idlj -fserver FileInterface.idl
執行接口
現在我們提供一個對downloadFile方法的執行。這個執行就像一個仆人,你可以從例子6中看出 FileServant 類從_FileInterfaceImplBase 類擴展過來。從這個類可以看出這個仆人是一個corba對象
例子6代碼: FileServant.java
import java.io.*;
public class FileServant extends _FileInterfaceImplBase {
public byte[] downloadFile(String fileName){
File file = new File(fileName);
byte buffer[] = new byte[(int)file.length()];
try {
BufferedInputStream input = new
BufferedInputStream(new FileInputStream(fileName));
input.read(buffer,0,buffer.length);
input.close();
} catch(Exception e) {
System.out.println("FileServant Error: "+e.getMessage());
e.printStackTrace();
}
return(buffer);
}
}
開發服務端
下一步是編寫corba服務端。在例子7中實現了一個corba服務端的類 FileServer。它通過以下的步驟來實現。
1)定義一個orb
2)創建一個FileServant對象
3)登記到corba命名服務中(COS Naming)
4)打印狀態消息
5)等待客戶端請求
例子7代碼: FileServer.java
import java.io.*;
import org.omg.CosNaming.*;
import org.omg.CosNaming.NamingContextPackage.*;
import org.omg.CORBA.*;
public class FileServer {
public static void main(String args[]) {
try{
// create and initialize the ORB
ORB orb = ORB.init(args, null);
// create the servant and register it with the ORB
FileServant fileRef = new FileServant();
orb.connect(fileRef);
// get the root naming context
org.omg.CORBA.Object objRef =
orb.resolve_initial_references("NameService");
NamingContext ncRef = NamingContextHelper.narrow(objRef);
// Bind the object reference in naming
NameComponent nc = new NameComponent("FileTransfer", " ");
NameComponent path[] = {nc};
ncRef.rebind(path, fileRef);
System.out.println("Server started....");
// Wait for invocations from clients
java.lang.Object sync = new java.lang.Object();
synchronized(sync){
sync.wait();
}
} catch(Exception e) {
System.err.println("ERROR: " + e.getMessage());
e.printStackTrace(System.out);
}
}
}
一旦FileServer 獲得一個orb, 它能夠登記corba服務。它使用由omg建議的由Java IDL 實現的corba名字服務(COS Naming Service )來登記。它是目錄服務中從根節點開始的一個目錄節點。這是一個普通的corba對象。可以當成NamingContext對象來使用。它必須能夠被narrowed down (換句話說是分級(casted))
到相應的類型。這是使用一個聲明來實現的。
NamingContext ncRef = NamingContextHelper.narrow(objRef);
ncRef對象現在是org.omg.CosNaming.NamingContext對象。你可以使用它來登記一個corba服務。 rebind 調用方法可以實現。
開發客戶端
下一步是開發一個客戶端,在例子8里面實現了,一旦得到了一個到名字服務的引用,它就能夠被用來進入名字服務和能夠用來查找一些服務(例如這里查找的是FileTransfer 服務)當FileTransfer 服務被找到的時候,downloadFile 方法將被調用。
例子8代碼:FileClient
import java.io.*;
import java.util.*;
import org.omg.CosNaming.*;
import org.omg.CORBA.*;
public class FileClient {
public static void main(String argv[]) {
try {
// create and initialize the ORB
ORB orb = ORB.init(argv, null);
// get the root naming context
org.omg.CORBA.Object objRef =
orb.resolve_initial_references("NameService");
NamingContext ncRef = NamingContextHelper.narrow(objRef);
NameComponent nc = new NameComponent("FileTransfer", " ");
// Resolve the object reference in naming
NameComponent path[] = {nc};
FileInterfaceOperations fileRef =
FileInterfaceHelper.narrow(ncRef.resolve(path));
if(argv.length < 1) {
System.out.println("Usage: java FileClient filename");
}
// save the file
File file = new File(argv[0]);
byte data[] = fileRef.downloadFile(argv[0]);
BufferedOutputStream output = new
BufferedOutputStream(new FileOutputStream(argv[0]));
output.write(data, 0, data.length);
output.flush();
output.close();
} catch(Exception e) {
System.out.println("FileClient Error: " + e.getMessage());
e.printStackTrace();
}
}
}
運行應用
最后一步是運行這個應用(萬里長征最后一步 哈哈高興吧)。這里有幾個小的步驟。
1)運行CORBA名字服務。這里可以使用tnameserv命令。它默認運行在900端口。你還可以改變端口號
例如使用如下命令讓你的服務運行在2500端口。
prompt> tnameserv -ORBinitialPort 2500
2)運行服務端
如果CORBA名字服務在默認的端口的時候使用如下命令。
prompt> java FileServer
如果你的CORBA名字服務在自己定義的端口如2500,使用如下命令,這里使用-ORBInitialPort 來定義端口
prompt> java FileServer -ORBInitialPort 2500
3)創建客戶端的Stubs。我們在運行客戶端之前我們必須創建一個Stubs。首先我們要得到一個FileInterface.idl 的復制,然后用如下命令編譯。
prompt> idlj -fclient FileInterface.idl
4)運行客戶端。現在你可以運行客戶端了,以下命令是假定corba名字服務在2500端口處監聽。
prompt> java FileClient hello.txt -ORBInitialPort 2500
這樣hello.txt文件可以從服務端下載下來了。
另外的,有些選項通過代碼定義的屬性來定義。比如代替初始化orb的代碼如下
ORB orb = ORB.init(argv, null);
它能修改一些參數,使得orb在2500端口提供和corba服務的名字叫gosling。例子如下。
Properties props = new Properties();
props.put("org.omg.CORBA.ORBInitialHost", "gosling");
props.put("orb.omg.CORBA.ORBInitialPort", "2500");
ORB orb = ORB.init(args, props);
練習
在這個文件交換程序里面,客戶端(不管是corba 還是rmi)需要知道將要下載的文件名,可是沒有方法提供列出可以用到的文件。作為一個練習,你可能想加入一些方法來比如提供列出可以用到的文件名來提高這個應用程序的可使用性。另外,你可能想開發一個基于圖形的客戶端來代替字符客戶端。當客戶端運行的時候它調用一個在服務端的方法來得到文件列表然后彈出一個菜單來顯示所有用戶可以選擇下載的文件。用戶可以選擇一個或多個文件來下載. 如下 圖1

Figure 1: GUI-based File Transfer Client
CORBA vs. RMI
從編碼來看,很明顯,RMI對于java 開發人員來說很容易使用。他們不需要了解IDL語言 Interface Definition Language 。
然而一般來說corba和RMI在如下方面各不相同。
(1)corba的interfaces(接口)使用IDL來定義,RMI 接口使用java來定義。RMI-IIOP允許所有的interfaces(接口)使用java來編寫。
(2)COrba提供in 和 out參數。同時RMI不提供,這是因為本地對象能夠復制。
(3)CORBA使用一種特殊的語言來設計interfaces(接口),這意味著一些對象可以使用java來編寫 而另外一些類可以使用 另外的語言例如C++來編寫它們能夠協同的工作。因此corba是一種連接各種孤立語言的理想機制。而RMI只是對所有java編寫的對象設計的。但是RMI-IIOP提供協同工作的能力。
(4)CORBA對象不被自動回收.就像我們以上提到的,corba是一種獨立的特殊語言,另外一些語言(例如C++)不提供垃圾收集。這可能會造成不利的情況,因為corba對象一旦創建,它將一直存在直到你殺掉它,而決定一個對象什么時候將被殺掉不是一件很簡單的工作。而RMI對象能夠自動的被收集。
結論
可以使用RMI和JavaIDL(一個corba的實現)開發一個基于分布式對象的java應用程序。使用這兩種技術從第一步到定義一個對象的接口都是很相似的。
然而和RMI把對象接口(interfaces)定義在java中不同的是,Corba的對象接口(interfaces)定義在IDL(Interface Definition Language )中,這樣增加了其他層的復雜性,使得需要開發人員需要了解IDL和IDL到java 的映射。
在這兩種機制之間做選擇依靠手頭的項目和項目的要求。我希望本文能夠給你提供足夠的信息來開始開發一個基于對象分布式應用程序和給你提供足夠的指導
來選擇一個分布機制。
更多的相關參考:
- RMI
- CORBA Specification (OMG)
- JavaIDL
- Distributed Programming with Java book (Chapter 11: Overview of CORBA)
- CORBA Server and Servlet Client
- RMI-IIOP
關于作者
Qusay H. Mahmoud 專門提供關于 Java 的咨詢和培訓。他發表了許多關于java的文章 , 它還是
<<Distributed Programming with Java >> (1999)和 <<Learning Wireless Java>>(2000)的作者。