遠(yuǎn)程方法調(diào)用(RMI)機制可以把面向?qū)ο蟮乃枷脒M(jìn)一步擴展,因為你可以調(diào)用的對象不僅可以在本機上,也可以在別的主機上。本文就簡單介紹rmi的編程方法。
首先介紹一些簡單的rmi的概念。
1, 服務(wù)器和客戶:在rmi中,如果有一個對象進(jìn)行遠(yuǎn)程方法調(diào)用,這個對象就叫做客戶機對象,而遠(yuǎn)程對象則被稱為服務(wù)器對象。
2, 創(chuàng)建服務(wù)器對象的服務(wù)器程序:這個程序用來創(chuàng)建服務(wù)器對象,注冊這個對象,使得客戶可以通過注冊的名稱訪問服務(wù)器對象。
3, 接口(interface),接口可以讓客戶端了解服務(wù)器所能做的工作。更具體的說,就是它列出了可以在服務(wù)器上執(zhí)行的所有方法。客戶端程序必須能夠找到這個類,否則就不能執(zhí)行對服務(wù)器函數(shù)的調(diào)用。
4, 客戶樁(stub),有的書中翻譯成為代碼存根,它給客戶端程序提供一個樁,這個樁上"綁"著服務(wù)器對象。當(dāng)客戶程序需要調(diào)用遠(yuǎn)程對象時,這個樁被下載到客戶端(如果客戶端有這個類,則不需要下載)。然后客戶就可以像調(diào)用本地方法一樣調(diào)用遠(yuǎn)程的方法了。
這個客戶樁的作用是將客戶向服務(wù)器的請求進(jìn)行編碼、進(jìn)行傳輸,服務(wù)器執(zhí)行這次調(diào)用后將結(jié)果返回到客戶樁,客戶樁進(jìn)行解碼,將解碼后的結(jié)果傳送到客戶程序中。對于編寫客戶端的程序員來說,他不需要知道其中的具體過程。
客戶樁不需要自己編寫,后面會說明它的生成方法。它實現(xiàn)了前述的接口(interface)。
下面就通過一個例子來說明編寫的過程。
1, 編寫服務(wù)器的接口:這一步是最主要的部分,因為接口是連接客戶機與服務(wù)器的關(guān)鍵部分。在這個例子中,接口很簡單,代碼如下:
import java.rmi.*;
public interface Product extends Remote
{
String getDescription() throws RemoteException;
}
在這里應(yīng)注意的是,遠(yuǎn)程對象的接口一定要擴展(extend)Java.rmi包的Remote接口。同時接口中的所有的方法都要聲明拋出RemoteException異常。這是因為由于網(wǎng)絡(luò)連接的不可靠性,遠(yuǎn)程方法調(diào)用很可能失敗。如果不聲明異常,在遠(yuǎn)程方法調(diào)用失敗后,應(yīng)用程序就會無法結(jié)束。
2, 編寫服務(wù)器對象:
Java中具有一個可以直接使用的服務(wù)器類--UniCastRemoteObject。它存在于Java.rmi.server包中。我們可以直接擴展這個類,使它實現(xiàn)前述的接口。這樣就可以使服務(wù)器滿足我們的需要。
import java.rmi.server.*;
import java.rmi.*;
public class ProductImpl extends UnicastRemoteObject implements Product
{
public ProductImpl(String name) throws RemoteException
{
Desc = name;
}
public String getDescription() throws RemoteException
{
return "This is "+Desc+" product";
}
private String Desc;
}
可以看到,rmi服務(wù)器的實現(xiàn)和其他的方法代碼沒有什么不同。
3, 編寫創(chuàng)建服務(wù)器對象的服務(wù)器程序:
import java.rmi.*;
public class ProductServer
{
public static void main(String[] args)
{
try
{
System.out.println("Constructin Server implementations ....");
ProductImpl p1 = new ProductImpl("toaster");
ProductImpl p2 = new ProductImpl("microwave");
System.out.println("Binding server implementations to registry");
Naming.rebind("toaster",p1);
Naming.rebind("microwave",p2);
System.out.println("waiting for clients...");
}catch(Exception e)
{
System.out.println("Error "+e);
}
}
}
通過代碼可以看到,這個服務(wù)器首先創(chuàng)建了兩個服務(wù)器對象。然后使用Naming.rebind()方法,將這個對象和一個名稱聯(lián)系(綁定)在一起。這個名稱就是客戶機查找服務(wù)器對象所使用的名稱。Naming是java.rmi包中的類。這個類的作用是建立一套查找對象的命名機制。通過它就可以將綁定在特定名稱上的對象找到。
4, 編寫客戶端代碼:
import java.rmi.*;
import java.rmi.server.*;
public class ProductClient
{
public static void main(String[] args)
{
System.out.println("begin to invoke remote method");
System.setSecurityManager(new RMISecurityManager());
String url = "rmi://91.1.1.119:1099/";
try
{
file://查找遠(yuǎn)程對象
System.out.println("1");
Product c1 = (Product)Naming.lookup(url + "toaster");
Product c2 = (Product)Naming.lookup(url + "microwave");
file://調(diào)用遠(yuǎn)程方法
System.out.println("2");
System.out.println(c1.getDescription());
System.out.println("3");
System.out.println(c2.getDescription());
}catch (Exception ex)
{
System.out.println("error "+ex);
}
}
}
在這段代碼中,首先定義了一個字符串url。這個字符串中存儲了找到遠(yuǎn)程服務(wù)器對象的協(xié)議和地址信息。在rmi中,所使用的協(xié)議是rmi,端口號是1099。這個例子中,我的服務(wù)器對象存放在ip地址為91.1.1.119的主機上,所以,這個字符串的值為rmi://91.1.1.119:1099/。
接著,使用Naming.lookup()方法查找遠(yuǎn)程對象。參數(shù)就是服務(wù)器的位置信息和服務(wù)器對象所綁定的名稱。
這里需要注意的是,通過lookup方法得到其實不是服務(wù)器對象本身的引用,而是下載到客戶機上的客戶樁。但是,這個方法得到的是Object類型,要使用這個對象,必須將它類型轉(zhuǎn)換成服務(wù)器所實現(xiàn)的接口類型。
隨后,就可以像調(diào)用本地方法一樣調(diào)用遠(yuǎn)程方法。在這個例子中,遠(yuǎn)程方法是getDescription()。
因為這段代碼是對遠(yuǎn)程對象進(jìn)行操作,所以,它被放到一個try…catch塊中,來捕獲遠(yuǎn)程調(diào)用過程中的異常。
最后,就要將服務(wù)器和客戶機部署到機器上。
1,將所有的類文件編譯為class文件。然后在dos方式下使用
rmic ProductImpl
就可以生成客戶樁,名為ProductImpl_Stub.class。
2,把客戶端代碼和接口代碼拷貝到客戶機器上。
3,運行rmiregistry程序,啟動注冊系統(tǒng),使得服務(wù)器可以注冊在機器上,以供客戶調(diào)用。
4,啟動http服務(wù)。將接口類和客戶樁類放在http服務(wù)器上,使得客戶可以下載。假設(shè)這兩個文件的下載目錄是http://91.1.1.119/download/
5,使用start java -Djava.rmi.server.codebase= http://91.1.1.119/download/ ProductServer
運行創(chuàng)建服務(wù)器對象的程序。
其中的-Djava.rmi.server.codebase= http://91.1.1.119/download/ 指明客戶程序下載客戶樁的地址。
6,因為rmi有安全限制,所以在客戶端必須建立一個策略文件。假設(shè)名為client.policy
文件的內(nèi)容為
grant
{
permission java.net.SocketPermission "91.1.1.119:1024-65535","connect";
permission java.net.SocketPermission "91.1.1.119:80","connect";
};
使用 start java -Djava.security.policy=client.policy ProductClient 啟動客戶端,客戶端就可以連接80端口(http端口)和1024-65535的端口(其中包含了rmi的缺省端口1099)。之后就可以看到程序的執(zhí)行結(jié)果。
以上就是使用rmi進(jìn)行遠(yuǎn)程方法調(diào)用的基本過程。
但是,應(yīng)該注意到,rmi有一個很大的限制,那就是只能在java編寫的對象之間使用,如果要在不同的語言寫成的對象之間通訊,那就需要CORBA的幫助
?
-----------------------------------------------------------------------------------------------------------------------
為通過網(wǎng)絡(luò)執(zhí)行其他機器上的代碼,傳統(tǒng)的方法不僅難以學(xué)習(xí),而且易出錯。解決這個問題的最佳方法是:某些對象正好位于另一臺機器,我們可以發(fā)送一條消息,并獲得返回結(jié)果,就像位于自己的本機器一樣。Java遠(yuǎn)程方法調(diào)用(RMI)特性使客戶機上運行的程序可以調(diào)用遠(yuǎn)程服務(wù)器上的對象。遠(yuǎn)程方法調(diào)用特性使Java編程人員能夠在網(wǎng)絡(luò)環(huán)境中分布操作。
下面介紹一下必要的步驟,創(chuàng)建自己的RMI對象。
一、遠(yuǎn)程接口概念:
???? RMI對接口有著強烈的依賴。在需要創(chuàng)建一個遠(yuǎn)程對象的時候,我們通過傳遞一個接口來隱藏基層的實施細(xì)節(jié)。所以客戶得到遠(yuǎn)程對象的一個句柄正好同一些本地的根代碼連接,有后者負(fù)責(zé)通過網(wǎng)絡(luò)通信。但我們并不關(guān)心這些事情,通過自己的接口句柄發(fā)送消息即可。
?? 創(chuàng)建一個遠(yuǎn)程接口時,必須遵守下列規(guī)則:
1)??遠(yuǎn)程接口必須為public屬性(不能有“包訪問”;也就是說,他不能是“友好的”)。否則,一旦客戶試圖裝載一個實現(xiàn)了遠(yuǎn)程接口的遠(yuǎn)程對象,就會得到一個錯誤。
2)??遠(yuǎn)程接口必須擴展接口java.rmi.Remote。
3)??除與應(yīng)用程序本身有關(guān)的違例,遠(yuǎn)程接口中的每個方法都必須在自己的throws從句中聲明java.rmi.RemoteException.
4)??作為參數(shù)或返回值傳遞的一個遠(yuǎn)程對象(不管是直接,還是本地對象中嵌入)必須聲明為遠(yuǎn)程接口,不可聲明為實施類。
下面是一個遠(yuǎn)程接口示例,
//PerfectTimeI.java
//The PerfectTime remote interface
package test;
import java.rmi.*;
public interface PerfectTimeI extends Remote {
????long getPerfectTime() throws RemoteException;
}
它表面上與其他的接口類似,只是對Remote進(jìn)行了擴展,而且所有的方法都會“擲”出RemoteException.接口和方法都是Public的。
編譯PerfectTimeI.java,生成PerfectTimeI.class(test是包,編譯時注意路徑)
G:\RMI>javac test\PerfectTimeI.java
二、遠(yuǎn)程接口的實施:
????服務(wù)器必須包含一個擴展了UnicastRemoteObject類,并實現(xiàn)遠(yuǎn)程接口。這個類也可以含有附加的方法,但客戶只能使用遠(yuǎn)程接口中的方法。因為客戶是指向接口的一個句柄,而不是它的哪個類。
?? 必須為遠(yuǎn)程對象定義構(gòu)件器,即使只準(zhǔn)備定義一個默認(rèn)構(gòu)件器,用它調(diào)用基礎(chǔ)類構(gòu)件器。必須把它明確地編寫出來,因為它必須“擲”出RemoteException違例。
??下面列出遠(yuǎn)程接口PerfectTime的事實過程:他代表精確計時服務(wù)
//PerfectTime.java
//The implementation of the PerfectTime remote object
package test;
import java.net.*;
import java.rmi.*;
import java.rmi.registry.*;
import java.rmi.server.*;
public class PerfectTime extends UnicastRemoteObject implements PerfectTimeI
{
//默認(rèn)構(gòu)件器,也要“擲”出RemoteException違例。
????public PerfectTime() throws RemoteException {
????????super();
????}
public long getPerfectTime() throws RemoteException {
????return System.currentTimeMillis();
}
public static void main(String[] args) {
/*創(chuàng)建和安裝一個安全管理器,令其支持RMI.作為Java開發(fā)包的一部分,適用于RMI唯一一個是RMISecurityManager.*/
????System.setSecurityManager(new RMISecurityManager());
????try {
?? /*創(chuàng)建遠(yuǎn)程對象的一個或多個實例,下面是PerfectTime對象*/
????????PerfectTime pt = new PerfectTime();
?? /*向RMI遠(yuǎn)程對象注冊表注冊至少一個遠(yuǎn)程對象。一個遠(yuǎn)程對象擁有的方法即可生成指向其他遠(yuǎn)程對象的句柄,這樣,客戶到注冊表里訪問一次,得到第一個遠(yuǎn)程對象即可.*/
????????Naming.bind("PerfectTime", pt);
????????System.out.println("Ready to do Time");
????} catch (Exception e) {
????????e.printStackTrace();
????}
}
}
編譯PerfectTime.java,生成PerfectTime.class(test是包,編譯時注意路徑)
G:\RMI>javac test\PerfectTime.java
三、創(chuàng)建根和干:
??創(chuàng)建RemoteObject的主干和框架。要完成這個工作可使用rmic編譯器,rmic編譯器生成遠(yuǎn)程對象的存根和骨架。存根(Stub)是遠(yuǎn)程對象在客戶端的代理,它將RMI調(diào)用傳遞給服務(wù)器端的骨架(Skeleton),后者負(fù)責(zé)將該調(diào)用傳遞給實際的遠(yuǎn)程方法輸入如下:
G:\RMI>rmic -d G:\RMI test.PerfectTime
執(zhí)行這個命令,
若rmic成功運行,test目錄里就會多出兩個新類:
PerfectTime_Stub.class
PerfectTime_Skel.class
它們分別對應(yīng)的是根(stub)和干(skeleton).
四、使用遠(yuǎn)程對象:
RMI全部的宗旨就是可能簡化遠(yuǎn)程接口對象的使用。我們客戶程序中要做的唯一一件額外事情是查找從服務(wù)器取回遠(yuǎn)程接口。下面就是編寫的Java程序:將消息發(fā)給對象:
//DisplayPerfectTime.java
//Users remote object PerfectTime
package test;
import java.rmi.*;
import java.rmi.registry.*;
public class DisplayPerfectTime {
/*** DisplayPerfectTime 構(gòu)造子注解。*/
public DisplayPerfectTime() {
????super();
}
public static void main(String[] args) {
????System.setSecurityManager(new RMISecurityManager());
????try {
????????PerfectTimeI t = (PerfectTimeI) Naming.lookup("PerfectTime");
?????? for (int i = 0; i < 10; i++) {
?????????? System.out.println("PerfectTime:" + t.getPerfectTime());
?????? }
????} catch (Exception e) {
????????e.printStackTrace();
????}
}
}
編譯DisplayPerfectTime.java.
G:\RMI>javac test\DisplayPerfectTime.java
五、啟動注冊并運行代碼:
在運行PerfectTime類和DisplayPectTime類之前,用戶必須首先在將要宿主PerfectTime的計算機上啟動RMI注冊(Registry)程序,即使將要運行PerfectTime的計算機與運行DisplayPerfectTime的是同一臺機器,這一步也是必須的。注冊表服務(wù)器的名字是rmiregistry.在32位Windows環(huán)境中,可使用: start rmiregistry 令其在后臺運行。然后分別開兩個不同的進(jìn)程運行Server端和Client端:啟動注冊表服務(wù)器:
G:\RMI>start rmiregistry
綁定PerfectTime到注冊,運行服務(wù)端程序:在Windows下,輸入下列命令,在后臺啟動PerfectTime程序:
G:\RMI>java test.PerfectTime
Ready to do Time
運行客戶端程序:如下
G:\RMI>java test.DisplayPerfectTime
PerfectTime:961722589649
PerfectTime:961722589669
PerfectTime:961722589679
PerfectTime:961722589679
PerfectTime:961722589689
PerfectTime:961722589689
PerfectTime:961722589689
PerfectTime:961722589699
PerfectTime:961722589699
PerfectTime:961722589699