Java RPC通信機制之RMI

以下文字摘自:http://blog.csdn.net/billdavid/archive/2006/08/18/1095706.aspx


大衛注1<o:p></o:p>

寫完CORBA系列後,本想接着寫寫其它幾種典型的遠程通信協議:RMIXML-RPCSOAP,但由於工作的原因,加之房子裝修等麻煩事,一直沒有心情動筆。今天接到裝修公司老闆電話說開工證要晚幾天辦下來,要停工4-5天,狂怒後突然有了靜下心來完成原本想寫的東西的想法,既來之,則安之(i.e.鬱悶啊,鬱悶啊,就習慣了...<o:p></o:p>

大衛注2<o:p></o:p>

這個系列基本上是一份筆記,沒有加入太多我自己的東西,僅僅記錄了自己在使用過程中遇到的問題,及其解決辦法。<o:p></o:p>

 <o:p></o:p>

在傳統的RPC編程接口逐漸淡出人們視線的同時,新的、更便於使用且附加了更多特性的RPC編程接口也不斷涌現,CORBA作爲分佈式對象計算技術的典範,在很長一段時間內極大地吸引了大家的注意,但是由於CORBA規範試圖覆蓋過多的內容,使得CORBA顯得過於複雜,也極大地限制了CORBA的應用範圍,本系列將向大家介紹幾種輕量級的,更適於在Java開發中使用的RPC編程接口:RMIXML-RPCSOAP<o:p></o:p>

RMIRemote Method Invocation

與本系列將介紹的其它兩種RPC編程接口不同,RMIRemote Method Invocation)顯得有些老舊,它是在Java-IDL加入J2SE之前被引入的。RMI開發流程與CORBA如出一轍(從出現的時間上無法確定RMI是否是按照CORBA規範定製的),因此,其開發過程相對比較煩瑣,但是由於RMIEJB的基礎,因此,它在Java開發中具有十分重要的地位。<o:p></o:p>

以下是創建遠程方法調用的5個步驟:<o:p></o:p>

1.   定義一個擴展了Remote接口的接口,該接口中的每一個方法必須聲明它將產生一個RemoteException異常;<o:p></o:p>

2.   定義一個實現該接口的類;<o:p></o:p>

3.   使用rmic程序生成遠程實現所需的存根和框架;<o:p></o:p>

4.   創建一個客戶程序和服務器進行RMI調用;<o:p></o:p>

5.   啓動rmiregistry並運行自己的服務程序和客戶程序。<o:p></o:p>

下面舉一個簡單、而且被無數次引用的例子:Echo<o:p></o:p>

1、定義Echo接口

//Echo.java<o:p></o:p>

//The Echo remote interface<o:p></o:p>

package demo.rmi;<o:p></o:p>

 <o:p></o:p>

import java.rmi.*;<o:p></o:p>

 <o:p></o:p>

public interface Echo extends Remote {<o:p></o:p>

      String echo(String msg) throws RemoteException;<o:p></o:p>

}<o:p></o:p>

2、實現Echo接口

//EchoServer.java<o:p></o:p>

//The implementation of the Echo remote object<o:p></o:p>

package demo.rmi;<o:p></o:p>

 <o:p></o:p>

import java.net.*;<o:p></o:p>

import java.rmi.*;<o:p></o:p>

import java.rmi.registry.*;<o:p></o:p>

import java.rmi.server.*;<o:p></o:p>

 <o:p></o:p>

public class EchoServer<o:p></o:p>

      extends UnicastRemoteObject<o:p></o:p>

      implements Echo {<o:p></o:p>

       //默認構件器,也要“擲”出RemoteException違例<o:p></o:p>

      public EchoServer() throws RemoteException {<o:p></o:p>

            super();<o:p></o:p>

      }<o:p></o:p>

 <o:p></o:p>

      public String echo(String msg) throws RemoteException {<o:p></o:p>

            return "Echo: " + msg;<o:p></o:p>

      }<o:p></o:p>

 <o:p></o:p>

      public static void main(String [] args) {<o:p></o:p>

            /*創建和安裝一個安全管理器,令其支持RMI作爲Java開發包的一部分,適用於RMI唯一一個是RMISecurityManager.*/<o:p></o:p>

            System.setSecurityManager(new RMISecurityManager());<o:p></o:p>

 <o:p></o:p>

            try {<o:p></o:p>

                     /*創建遠程對象的一個或多個實例,下面是EchoServer對象*/<o:p></o:p>

                  EchoServer es = new EchoServer();<o:p></o:p>

                  /*向RMI遠程對象註冊表註冊至少一個遠程對象。一個遠程對象擁有的方法即可生成指向其他遠程對象的句柄,這樣,客戶到註冊表裏訪問一次,得到第一個遠程對象即可.*/<o:p></o:p>

                  Naming.rebind("EchoServer", es);<o:p></o:p>

                  System.out.println("Ready to provide echo service...");<o:p></o:p>

            } catch (Exception e) {<o:p></o:p>

                  e.printStackTrace();<o:p></o:p>

            }<o:p></o:p>

      }<o:p></o:p>

}<o:p></o:p>

這個實現類使用了UnicastRemoteObject去連接RMI系統。在我們的例子中,我們是直接的從UnicastRemoteObject這個類上繼承的,事實上並不一定要這樣做,如果一個類不是從UnicastRmeoteObject上繼承,那必須使用它的exportObject()方法去連接到RMI。(否則,運行時將被告知無法序列化。)<o:p></o:p>

如果一個類繼承自UnicastRemoteObject,那麼它必須提供一個構造函數並且聲明拋出一個RemoteException對象(否則,會遇到編譯錯誤)。當這個構造函數調用了super(),它就激活UnicastRemoteObject中的代碼完成RMI的連接和遠程對象的初始化。<o:p></o:p>

3、運行rmic編譯實現類,產生_Stub

demo.rmi.EchoServer.java上級目錄下運行如下命令:<o:p></o:p>

rmic demo.rmi.EchoServer<o:p></o:p>

4、編寫客戶程序

//EchoClient.java<o:p></o:p>

//Uses remote object EchoServer<o:p></o:p>

package demo.rmi;<o:p></o:p>

 <o:p></o:p>

import java.rmi.*;<o:p></o:p>

import java.rmi.registry.*;<o:p></o:p>

 <o:p></o:p>

public class EchoClient {<o:p></o:p>

      public static void main(String [] args) {<o:p></o:p>

            System.setSecurityManager(new RMISecurityManager());<o:p></o:p>

 <o:p></o:p>

            try {<o:p></o:p>

                  Echo t = (Echo)Naming.lookup("EchoServer");<o:p></o:p>

 <o:p></o:p>

                  for (int i = 0; i < 10; i++) {<o:p></o:p>

                        System.out.println(t.echo(String.valueOf(i)));<o:p></o:p>

                  }<o:p></o:p>

            } catch (Exception e) {<o:p></o:p>

                  e.printStackTrace();<o:p></o:p>

            }<o:p></o:p>

      }<o:p></o:p>

}<o:p></o:p>

5、運行

編碼的工作就只有這些,現在可以依次啓動rmiregistry(啓動rmiregistry時可以附加一個端口,一般使用默認的端口1099即可,這是默認的Naming Service運行端口)、EchoServerEchoClient了。但是,雖然有些RMI的資料沒有提到,但你運行時不可避免會遇到如下兩個錯誤:<o:p></o:p>

1java.security.AccessControlException: access denied (java.net.SocketPermission 127.0.0.1:1099 connect,resolve)<o:p></o:p>

原因很簡單,RMI Server/Client程序試圖通過Socket連接訪問本機的rmiregistry服務(即RMINaming Service,其運行的默認端口是1099)。要解決這個問題,可以在運行Server/Client時指定一個Policy文件(關於Policy的更多信息,見參考2),如下:<o:p></o:p>

java -Djava.security.policy=demo/rmi/policyfile.txt demo.rmi.EchoServer<o:p></o:p>

Policy文件的內容爲:<o:p></o:p>

grant{<o:p></o:p>

      permission java.net.SocketPermission "localhost:1099", "connect, resolve";<o:p></o:p>

};<o:p></o:p>

即允許訪問本機的1099端口。<o:p></o:p>

或者乾脆來個徹底開放:<o:p></o:p>

grant {<o:p></o:p>

      permission java.security.AllPermission "", "";<o:p></o:p>

};<o:p></o:p>

2java.rmi.ServerException: RemoteException occurred in server thread; nested exception is:<o:p></o:p>

        java.rmi.UnmarshalException: error unmarshalling arguments; nested exception is:<o:p></o:p>

        java.lang.ClassNotFoundException: demo.rmi.EchoServer_Stub<o:p></o:p>

        ...<o:p></o:p>

如果你湊巧用啓動rmiregistry的終端窗口啓動了EchoServer,那麼你很走運,你看不到上面的錯誤,但如果你不是在看完這篇文章後就再也用不到RMI,那麼,這個錯誤在那裏等着你,:)<o:p></o:p>

錯誤很明顯,rmiregistry找不到與EchoServer放在同一目錄下的EchoServer_Stub,因爲package所在demo.rmi目錄的上級目錄不在rmiregistryclasspath中,這個問題有兩種解決方案:<o:p></o:p>

a)在啓動rmiregistry前先調整一下CLASSPATH環境變量,以目錄E:\爲例,執行:<o:p></o:p>

set CLASSPATH=%CLASSPATH%;E:\<o:p></o:p>

b)修改code,在EchoServer中通過如下代碼:<o:p></o:p>

Registry r = LocateRegistry.createRegistry(8111);<o:p></o:p>

r.rebind("EchoServer", es);<o:p></o:p>

在程序內部創建一個LocateRegistry,並將自身註冊到該LocateRegistry,其中的數值8111表示LocateRegistry運行的端口。<o:p></o:p>

同樣,對於客戶程序,也需要作相應的調整:<o:p></o:p>

Registry r = LocateRegistry.getRegistry("localhost", 8111);<o:p></o:p>

Echo e = (Echo)r.lookup("EchoServer");<o:p></o:p>

而不是像上面例子中一樣訪問Naming類的static方法來訪問默認的rmiregistry服務。<o:p></o:p>

參考:

1.      Java RMI Tutorial, http://www.ccs.neu.edu/home/kenb/com3337/rmi_tut.html<o:p></o:p>

2.      Policy Tool - Policy File Creation and Management Tool. http://java.sun.com/j2se/1.4.2/docs/tooldocs/windows/policytool.html<o:p></o:p>

3.     Java RMI入門實戰,http://www.huihoo.com/java/rmi/index.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章