大衛(wèi)注1:
寫完CORBA系列后,本想接著寫寫其它幾種典型的遠(yuǎn)程通信協(xié)議:RMI、XML-RPC、SOAP,但由于工作的原因,加之房子裝修等麻煩事,一直沒有心情動筆。今天接到裝修公司老板電話說開工證要晚幾天辦下來,要停工4-5天,狂怒后突然有了靜下心來完成原本想寫的東西的想法,既來之,則安之(i.e.郁悶啊,郁悶啊,就習(xí)慣了...)
大衛(wèi)注2:
這個系列基本上是一份筆記,沒有加入太多我自己的東西,僅僅記錄了自己在使用過程中遇到的問題,及其解決辦法。
在傳統(tǒng)的RPC編程接口逐漸淡出人們視線的同時,新的、更便于使用且附加了更多特性的RPC編程接口也不斷涌現(xiàn),CORBA作為分布式對象計算技術(shù)的典范,在很長一段時間內(nèi)極大地吸引了大家的注意,但是由于CORBA規(guī)范試圖覆蓋過多的內(nèi)容,使得CORBA顯得過于復(fù)雜,也極大地限制了CORBA的應(yīng)用范圍,本系列將向大家介紹幾種輕量級的,更適于在Java開發(fā)中使用的RPC編程接口:RMI、XML-RPC、SOAP。
RMI(Remote Method Invocation)
與本系列將介紹的其它兩種RPC編程接口不同,RMI(Remote Method Invocation)顯得有些老舊,它是在Java-IDL加入J2SE之前被引入的。RMI開發(fā)流程與CORBA如出一轍(從出現(xiàn)的時間上無法確定RMI是否是按照CORBA規(guī)范定制的),因此,其開發(fā)過程相對比較煩瑣,但是由于RMI是EJB的基礎(chǔ),因此,它在Java開發(fā)中具有十分重要的地位。
以下是創(chuàng)建遠(yuǎn)程方法調(diào)用的5個步驟:
1. 定義一個擴(kuò)展了Remote接口的接口,該接口中的每一個方法必須聲明它將產(chǎn)生一個RemoteException異常;
2. 定義一個實現(xiàn)該接口的類;
3. 使用rmic程序生成遠(yuǎn)程實現(xiàn)所需的存根和框架;
4. 創(chuàng)建一個客戶程序和服務(wù)器進(jìn)行RMI調(diào)用;
5. 啟動rmiregistry并運行自己的服務(wù)程序和客戶程序。
下面舉一個簡單、而且被無數(shù)次引用的例子:Echo。
1、定義Echo接口
//Echo.java
//The Echo remote interface
package demo.rmi;
import java.rmi.*;
public interface Echo extends Remote {
String echo(String msg) throws RemoteException;
}
2、實現(xiàn)Echo接口
//EchoServer.java
//The implementation of the Echo remote object
package demo.rmi;
import java.net.*;
import java.rmi.*;
import java.rmi.registry.*;
import java.rmi.server.*;
public class EchoServer
extends UnicastRemoteObject
implements Echo {
//默認(rèn)構(gòu)件器,也要“擲”出RemoteException違例
public EchoServer() throws RemoteException {
super();
}
public String echo(String msg) throws RemoteException {
return "Echo: " + msg;
}
public static void main(String [] args) {
/*創(chuàng)建和安裝一個安全管理器,令其支持RMI。作為Java開發(fā)包的一部分,適用于RMI唯一一個是RMISecurityManager.*/
System.setSecurityManager(new RMISecurityManager());
try {
/*創(chuàng)建遠(yuǎn)程對象的一個或多個實例,下面是EchoServer對象*/
EchoServer es = new EchoServer();
/*向RMI遠(yuǎn)程對象注冊表注冊至少一個遠(yuǎn)程對象。一個遠(yuǎn)程對象擁有的方法即可生成指向其他遠(yuǎn)程對象的句柄,這樣,客戶到注冊表里訪問一次,得到第一個遠(yuǎn)程對象即可.*/
Naming.rebind("EchoServer", es);
System.out.println("Ready to provide echo service...");
} catch (Exception e) {
e.printStackTrace();
}
}
}
這個實現(xiàn)類使用了UnicastRemoteObject去連接RMI系統(tǒng)。在我們的例子中,我們是直接的從UnicastRemoteObject這個類上繼承的,事實上并不一定要這樣做,如果一個類不是從UnicastRmeoteObject上繼承,那必須使用它的exportObject()方法去連接到RMI。(否則,運行時將被告知無法序列化。)
如果一個類繼承自UnicastRemoteObject,那么它必須提供一個構(gòu)造函數(shù)并且聲明拋出一個RemoteException對象(否則,會遇到編譯錯誤)。當(dāng)這個構(gòu)造函數(shù)調(diào)用了super(),它就激活UnicastRemoteObject中的代碼完成RMI的連接和遠(yuǎn)程對象的初始化。
3、運行rmic編譯實現(xiàn)類,產(chǎn)生_Stub類
在demo.rmi.EchoServer.java上級目錄下運行如下命令:
rmic demo.rmi.EchoServer
4、編寫客戶程序
//EchoClient.java
//Uses remote object EchoServer
package demo.rmi;
import java.rmi.*;
import java.rmi.registry.*;
public class EchoClient {
public static void main(String [] args) {
System.setSecurityManager(new RMISecurityManager());
try {
Echo t = (Echo)Naming.lookup("EchoServer");
for (int i = 0; i < 10; i++) {
System.out.println(t.echo(String.valueOf(i)));
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
5、運行
編碼的工作就只有這些,現(xiàn)在可以依次啟動rmiregistry(啟動rmiregistry時可以附加一個端口,一般使用默認(rèn)的端口1099即可,這是默認(rèn)的Naming Service運行端口)、EchoServer、EchoClient了。但是,雖然有些RMI的資料沒有提到,但你運行時不可避免會遇到如下兩個錯誤:
1)java.security.AccessControlException: access denied (java.net.SocketPermission 127.0.0.1:1099 connect,resolve)
原因很簡單,RMI Server/Client程序試圖通過Socket連接訪問本機的rmiregistry服務(wù)(即RMI的Naming Service,其運行的默認(rèn)端口是1099)。要解決這個問題,可以在運行Server/Client時指定一個Policy文件(關(guān)于Policy的更多信息,見參考2),如下:
java -Djava.security.policy=demo/rmi/policyfile.txt demo.rmi.EchoServer
Policy文件的內(nèi)容為:
grant{
permission java.net.SocketPermission "localhost:1099", "connect, resolve";
};
即允許訪問本機的1099端口。
或者干脆來個徹底開放:
grant {
permission java.security.AllPermission "", "";
};
2)java.rmi.ServerException: RemoteException occurred in server thread; nested exception is:
java.rmi.UnmarshalException: error unmarshalling arguments; nested exception is:
java.lang.ClassNotFoundException: demo.rmi.EchoServer_Stub
...
如果你湊巧用啟動rmiregistry的終端窗口啟動了EchoServer,那么你很走運,你看不到上面的錯誤,但如果你不是在看完這篇文章后就再也用不到RMI,那么,這個錯誤在那里等著你,:)。
錯誤很明顯,rmiregistry找不到與EchoServer放在同一目錄下的EchoServer_Stub,因為package所在demo.rmi目錄的上級目錄不在rmiregistry的classpath中,這個問題有兩種解決方案:
a)在啟動rmiregistry前先調(diào)整一下CLASSPATH環(huán)境變量,以目錄E:/為例,執(zhí)行:
set CLASSPATH=%CLASSPATH%;E:/
b)修改code,在EchoServer中通過如下代碼:
Registry r = LocateRegistry.createRegistry(8111);
r.rebind("EchoServer", es);
在程序內(nèi)部創(chuàng)建一個LocateRegistry,并將自身注冊到該LocateRegistry,其中的數(shù)值8111表示LocateRegistry運行的端口。
同樣,對于客戶程序,也需要作相應(yīng)的調(diào)整:
Registry r = LocateRegistry.getRegistry("localhost", 8111);
Echo e = (Echo)r.lookup("EchoServer");
而不是像上面例子中一樣訪問Naming類的static方法來訪問默認(rèn)的rmiregistry服務(wù)。
參考:
1. Java RMI Tutorial, http://www.ccs.neu.edu/home/kenb/com3337/rmi_tut.html
2. Policy Tool - Policy File Creation and Management Tool. http://java.sun.com/j2se/1.4.2/docs/tooldocs/windows/policytool.html
3. Java RMI入門實戰(zhàn),http://www.huihoo.com/java/rmi/index.html