京山游俠

          專注技術,拒絕扯淡
          posts - 50, comments - 868, trackbacks - 0, articles - 0
            BlogJava :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理

          對RMI的簡單理解

          Posted on 2006-09-15 22:45 京山游俠 閱讀(2299) 評論(3)  編輯  收藏 所屬分類: J2EE學習及探索

          RMI 的簡單理解

          ?

          RMI (遠程方法)是 Java 平臺中建立分布式計算的基礎, 2 年前我剛開始接觸 J2EE 時,怎么看書都是不得要領,最近這幾天閑著沒事又翻了翻以前沒有看懂的書,突然之間頓悟了。

          ?

          一、 簡單的 RMI 示例:

          要快速入門,最簡單的方法就是看簡單的例子。下面是我寫的一個簡單的示例:

          首先,定義一個接口 IServer ,代碼如下:

          IServer.java

          ?

          1 package ?rmistudy;
          2
          3 import ?java.rmi.Remote;
          4
          5 public ? interface ?IServer? extends ?Remote? {
          6 ??????? public ? void ?doSomeThing()? throws ?java.rmi.RemoteException;
          7 }

          8
          9


          需要注意的是,這個接口從java.rmi.Remote接口擴展,并且這個接口中定義的方法都需要拋出java.rmi.RemoteException異常。

          ?

          接著,我們要根據這個接口來實現自己的服務器對象,所謂服務器對象,就是我們大腦中想的遠程對象,這個對象中定義的方法都是被別人來調用的。代碼如下:

          ServerImp.java

          ?

          package ?rmistudy;

          import ?java.rmi. * ;
          import ?java.rmi.server. * ;

          public ? class ?ServerImp? extends ?UnicastRemoteObject? implements ?IServer? {

          ???????
          public ?ServerImp()? throws ?RemoteException? {
          ??????????????
          super ();
          ???????}


          ???????
          public ? void ?doSomeThing()? throws ?RemoteException? {
          ??????????????System.out.println(
          " 不帶參數的遠程函數doSomeThing()被調用,該信息顯示在服務器端。 " );
          ???????}


          ???????
          public ? static ? void ?main(String[]?args)? {
          ??????????????ServerImp?server?
          = ? null ;

          ??????????????
          try {
          ?????????????????????server?
          = ? new ?ServerImp();
          ??????????????}
          catch (Exception?e) {
          ?????????????????????System.out.println(
          " 創建遠程對象失敗: " );
          ?????????????????????System.out.println(e.getMessage());
          ?????????????????????System.exit(
          0 );
          ??????????????}


          ??????????????
          try {
          ?????????????????????java.rmi.Naming.rebind(
          " //localhost/MyServer " ,?server);
          ?????????????????????System.out.println(
          " 遠程對象綁定成功。 " );
          ??????????????}
          catch (Exception?e) {
          ?????????????????????System.out.println(
          " 遠程對象綁定失敗: " );
          ?????????????????????System.out.println(e.getMessage());
          ?????????????????????System.exit(
          0 );
          ??????????????}

          ???????}

          }


          ?

          這個類很容易理解, doSomeThing() 方法只簡單的輸出被調用的信息。唯一的難點就在 main() 函數中,我們通過 java.rmi.Naming.rebind() 把我們的遠程對象注冊到 rmi 注冊表中,這樣,別人就可以通過 java.rmi.Naming.lookup() 來查找我們的遠程對象。那么, rmi 注冊表在哪里呢? J2SDK bin 目錄下有一個程序 rmiregistry ,運行它就可以得到一個注冊表進程,我們可以通過它來綁定或者查找遠程對象, java.rmi.Naming.rebind 函數的第一個參數就是要指定注冊表進程的位置,因為我這里運行在自己的機器上,所以是 //localhost/ ,如果是在別的機器上,可以用 IP 地址代替。

          ?

          最后,我們寫一個客戶機,來調用這個遠程對象的方法。代碼如下:

          Client.java

          ?

          ?1 package ?rmistudy;
          ?2
          ?3 import ?java.rmi. * ;
          ?4
          ?5 public ? class ?Client? {
          ?6
          ?7 ??????? public ? static ? void ?main(String[]?args)? {
          ?8 ??????????????IServer?server? = ? null ;
          ?9
          10 ?????????????? try {
          11 ?????????????????????server? = ?(IServer)Naming.lookup( " //localhost/MyServer " );
          12 ?????????????????????System.out.println( " 查找遠程對象成功。 " );
          13 ??????????????}
          catch (Exception?e) {
          14 ?????????????????????System.out.println( " 查找遠程對象失敗: " );
          15 ?????????????????????System.out.println(e.getMessage());
          16 ?????????????????????System.exit( 0 );
          17 ??????????????}

          18
          19 ??????????????? try {
          20 ?????????????????????server.doSomeThing();
          21 ?????????????????????System.out.println( " 調用doSomeThing()成功。 " );
          22 ??????????????}
          catch (Exception?e) {
          23 ?????????????????????System.out.println( " 調用doSomeThing()失敗: " );
          24 ?????????????????????System.out.println(e.getMessage());
          25 ?????????????????????System.exit( 0 );
          26 ??????????????}

          27 ???????}

          28 }

          29
          30

          ?

          可以看到,我們的客戶端程序只用到了 IServer 接口,而不需要 ServerImp 類,它只通過 java.rmi.Naming.lookup() 來查找遠程對象的引用。

          ?

          下面,我們就可以開始測試我們的程序了。先編譯以上程序,然后:

          第一步,要先啟動 Rmi 注冊表,如下:

          1.JPG

          ?

          第二步,使用 rmic ServerImp.class 進行編譯,生成代理類 ServerImp_Stub.class ,如下:

          2.JPG
          ?

          第三步,啟動服務器端程序,如下:

          3.JPG?

          第四步,啟動客戶端程序,我們多調用幾次,如下:

          ?4.JPG

          這個時候,我們再看看服務器端是什么反應:

          5.JPG

          可以看到,服務器端的方法被調用,在服務器端的控制臺上打印出了這樣幾行消息。

          ?

          下面,我們使用一個簡單的圖表來表示客戶機、服務器和 RMI 注冊表之間的關系,綠色的數字代表順序:

          ?示意圖.JPG

          二、參數傳遞

            前面的例子沒有涉及到參數的傳遞。如果我們需要向遠程方法傳遞參數,或者要從遠程方法接受返回值,是不是有什么特殊的約定呢?不錯,如果我們要在客戶機和服務器之間傳遞參數,則該對象要么是實現Serializable接口的對象,要么是擴展自UnicastRemoteObject的對象,這兩種對象是有差別的。

            如果參數是實現Serializable接口的對象,則該對象是按值傳遞的,也就是把這整個對象傳遞到遠程方法中。請看下面的例子,我們定義了一個ISerializableWorker接口,擴展自Serializable接口,客戶端創建一個SerializableWorkerImp對象wk,并把它傳遞到服務器端,服務器端調用wk.work()方法,這個方法在服務器端執行,這就說明了我們成功把這個對象傳遞到了服務器端。服務器端返回的String對象,也可以成功傳遞到客戶端。
          ISerializableWorker.java

          1 package ?rmistudy;
          2
          3 import ?java.io.Serializable;
          4
          5 public ? interface ?ISerializableWorker? extends ?Serializable? {
          6 ???? public ? void ?work();
          7 }


          SerializableWorkerImp.java
          1package?rmistudy;
          2
          3public?class?SerializableWorkerImp?implements?ISerializableWorker?{
          4
          5????public?void?work()?{
          6????????System.out.println("該信息由SerializableWorker對象輸出。");
          7????}

          8
          9}

          IServer.java
          1package?rmistudy;
          2
          3import?java.rmi.Remote;
          4import?java.rmi.RemoteException;
          5
          6public?interface?IServer?extends?Remote?{
          7????public?void?doSomeThing()?throws?RemoteException;
          8????public?String?doSomeThing(ISerializableWorker?wk)?throws?RemoteException;
          9}

          ServerImp.java
          ?1package?rmistudy;
          ?2
          ?3import?java.rmi.*;
          ?4import?java.rmi.server.*;
          ?5
          ?6public?class?ServerImp?extends?UnicastRemoteObject?implements?IServer?{
          ?7
          ?8????public?ServerImp()?throws?RemoteException?{
          ?9????????super();
          10????}

          11
          12
          13????public?void?doSomeThing()?throws?RemoteException?{
          14????????
          15????????System.out.println("不帶參數的遠程函數doSomeThing()被調用,該信息顯示在服務器端。");
          16
          17????}

          18????
          19????public?String?doSomeThing(ISerializableWorker?wk)?throws?RemoteException{
          20????????wk.work();
          21????????return?new?String("調用成功,該信息來自服務器端。");
          22????}

          23
          24????/**
          25?????*?@param?args
          26?????*/

          27????public?static?void?main(String[]?args)?{
          28????????ServerImp?server?=?null;
          29????????
          30????????try{
          31????????????server?=?new?ServerImp();
          32????????}
          catch(Exception?e){
          33????????????System.out.println("創建遠程對象失敗:");
          34????????????System.out.println(e.getMessage());
          35????????????System.exit(0);
          36????????}

          37
          38????????try{
          39????????????java.rmi.Naming.rebind("//localhost/MyServer",?server);
          40????????????System.out.println("遠程對象綁定成功。");
          41????????}
          catch(Exception?e){
          42????????????System.out.println("遠程對象綁定失敗:");
          43????????????System.out.println(e.getMessage());
          44????????????System.exit(0);
          45????????}

          46????}

          47
          48}

          Client.java
          ?1package?rmistudy;
          ?2
          ?3import?java.rmi.*;
          ?4
          ?5public?class?Client?{
          ?6
          ?7????/**
          ?8?????*?@param?args
          ?9?????*/

          10????public?static?void?main(String[]?args)?{
          11????????IServer?server?=?null;
          12????????
          13????????try{
          14????????????server?=?(IServer)Naming.lookup("//localhost/MyServer");
          15????????????System.out.println("查找遠程對象成功。");
          16????????}
          catch(Exception?e){
          17????????????System.out.println("查找遠程對象失敗:");
          18????????????System.out.println(e.getMessage());
          19????????????System.exit(0);
          20????????}

          21????????
          22????????try{
          23????????????server.doSomeThing();
          24????????????System.out.println("調用doSomeThing()成功。");
          25????????????String?str?=?server.doSomeThing(new?SerializableWorkerImp());
          26????????????System.out.println("調用帶序列化參數的doSomeThing()成功");
          27????????????System.out.println("從服務器端返回的字符串:"+str);
          28????????}
          catch(Exception?e){
          29????????????System.out.println("調用doSomeThing()失敗:");
          30????????????System.out.println(e.getMessage());
          31????????????System.exit(0);
          32????????}

          33
          34????}

          35
          36}

          37

          程序的運行方法同前,我就不再羅嗦了。這里需要注意的是,該示例在單機上運行可以,但是真的在分布環境下運行就會出錯,畢竟,別人要把一個對象傳遞到你的機器上,怎么著你也要放著別人的對象搞破壞吧。最后我們會討論安全問題。

          另外一種參數的傳遞方式,就是按照引用傳遞,如果作為參數的對象是擴展自java.rmi.server.UnicastRemoteObject類的話,那么該對象傳遞給遠程方法的只是它的引用。比如,客戶端創建了一個擴展自java.rmi.server.UnicastRemoteObject的對象A,把對象A傳遞到服務器端,這個時候服務器端得到的只是對象A的引用,如果服務器調用對象A的方法,這個方法就會在客戶端執行。

          下面的例子說明了這一點,我們定義IRefWorker接口和RefWorkerImp類,在客戶端創建RefWorkerImp類的對象,把該對象傳遞到服務器端,服務器端調用該對象的方法,你會發現該方法在客戶端執行。
          IRefWorker.java
          1package?rmistudy;
          2
          3import?java.rmi.Remote;
          4import?java.rmi.RemoteException;
          5
          6public?interface?IRefWorker?extends?Remote?{
          7????public?void?work()?throws?RemoteException;
          8}

          RefWorkerImp.java
          ?1package?rmistudy;
          ?2
          ?3import?java.rmi.RemoteException;
          ?4import?java.rmi.server.RMIClientSocketFactory;
          ?5import?java.rmi.server.RMIServerSocketFactory;
          ?6import?java.rmi.server.UnicastRemoteObject;
          ?7
          ?8public?class?RefWorkerImp?extends?UnicastRemoteObject?implements?IRefWorker?{
          ?9
          10????public?RefWorkerImp()?throws?RemoteException?{
          11????????super();
          12????}

          13
          14????public?void?work()?throws?RemoteException?{
          15????????System.out.println("該方法在服務器端調用,在客戶端執行。");
          16????}

          17
          18}

          IServer.java
          ?1package?rmistudy;
          ?2
          ?3import?java.rmi.Remote;
          ?4import?java.rmi.RemoteException;
          ?5
          ?6public?interface?IServer?extends?Remote?{
          ?7????public?void?doSomeThing()?throws?RemoteException;
          ?8????public?String?doSomeThing(ISerializableWorker?wk)?throws?RemoteException;
          ?9????public?void?doSomeThing(IRefWorker?wk)?throws?RemoteException;
          10}

          ServerImp.java
          該類中實現接口中定義的方法,和前面的代碼相比,多了如下一行
          1public?void?doSomeThing(IRefWorker?wk)?throws?RemoteException{
          2????????wk.work();
          3????}

          Client.java
          ?1package?rmistudy;
          ?2
          ?3import?java.rmi.*;
          ?4
          ?5public?class?Client?{
          ?6
          ?7????/**
          ?8?????*?@param?args
          ?9?????*/

          10????public?static?void?main(String[]?args)?{
          11????????IServer?server?=?null;
          12????????
          13????????try{
          14????????????server?=?(IServer)Naming.lookup("//localhost/MyServer");
          15????????????System.out.println("查找遠程對象成功。");
          16????????}
          catch(Exception?e){
          17????????????System.out.println("查找遠程對象失敗:");
          18????????????System.out.println(e.getMessage());
          19????????????System.exit(0);
          20????????}

          21????????
          22????????try{
          23????????????server.doSomeThing();
          24????????????System.out.println("調用doSomeThing()成功。");
          25????????????String?str?=?server.doSomeThing(new?SerializableWorkerImp());
          26????????????System.out.println("調用帶序列化參數的doSomeThing()成功");
          27????????????System.out.println("從服務器端返回的字符串:"+str);
          28????????????server.doSomeThing(new?RefWorkerImp());
          29????????????System.out.println("調用帶引用參數的doSomeThing()成功");
          30????????}
          catch(Exception?e){
          31????????????System.out.println("調用doSomeThing()失敗:");
          32????????????System.out.println(e.getMessage());
          33????????????System.exit(0);
          34????????}

          35
          36????}

          37
          38}

          程序的運行方法同前,不再重復。

          三、安全管理與授權策略
            前面提到過,前面的示例代碼,如果真正運行到分布式環境下的話,是會出錯的,原因就在于安全性問題。J2EE中的安全管理廣泛,我們這里僅僅只用到授權,比如我們可以只授權遠程程序訪問某一個文件夾或某一個文件,或者只授權遠程程序訪問網絡等等。

            要使用授權,需要一個授權文件,我們新建一個Policy.txt文件,為了簡單起見,我們授權遠程程序可以訪問所有的本地資源:
          1grant{
          2??permission?java.security.AllPermission?"","";
          3}
          ;

            然后,我們需要在服務器端程序中載入安全管理器,我們這里使用默認的RMISecurityManager,下面是經過修改了的ServerImp.java中的mian()函數:

          ?1public?static?void?main(String[]?args)?{
          ?2????????ServerImp?server?=?null;
          ?3????????
          ?4????????try{
          ?5????????????System.setSecurityManager(new?RMISecurityManager());
          ?6????????}
          catch(Exception?e){
          ?7????????????System.out.println("加載安全管理器失敗:");
          ?8????????????System.out.println(e.getMessage());
          ?9????????????System.exit(0);
          10????????}

          11????????
          12????????try{
          13????????????server?=?new?ServerImp();
          14????????}
          catch(Exception?e){
          15????????????System.out.println("創建遠程對象失敗:");
          16????????????System.out.println(e.getMessage());
          17????????????System.exit(0);
          18????????}

          19
          20????????try{
          21????????????java.rmi.Naming.rebind("//localhost/MyServer",?server);
          22????????????System.out.println("遠程對象綁定成功。");
          23????????}
          catch(Exception?e){
          24????????????System.out.println("遠程對象綁定失敗:");
          25????????????System.out.println(e.getMessage());
          26????????????System.exit(0);
          27????????}

          28????}

          然后,我們需要這樣運行服務器端:
          java -Djava.security.policy=Policy.txt rmistudy.ServerImp

          給幾個貼圖:
          1.運行服務器:
          6.JPG

          2.運行客戶端:
          7.JPG

          3.運行客戶端后服務器的反應:
          8.JPG

          總結
            J2EE規范雖然龐大而復雜,但是如果我們分開來學習,也是可以逐步理解的。J2EE包含企業數據、企業通訊、企業服務、企業Web支持和企業應用程序等方面。而我們的RMI就是企業通訊中的一種,另外一種就是日益流行起來的Web Service通訊,至于其它通訊架構,我們大可以到需要的時候再去學習,比如CORBA。

            EJB是架構在RMI基礎上的,但是它太復雜,很多時候使用簡單的RMI就可以解決很多問題,比如科學領域的分布式計算。大家一定聽說過找外星人的SETI項目,它就是利用全球志愿者的個人PC來進行分布式的運算。在我們中國,大型機還比較缺乏,如果我們的某個實驗室需要強大的計算能力,大可以向SETI項目學習。而使用RMI,建立分布式計算平臺是多么的簡單。

          評論

          # re: 對RMI的簡單理解  回復  更多評論   

          2007-01-25 12:41 by anders0913
          請問要是在Tomcat中直接使用RMI有什么好的方法可以解決安全問題。不想更改Tomcat的安全策略文件?

          # re: 對RMI的簡單理解  回復  更多評論   

          2007-07-30 16:50 by 小白之家
          恩,介紹算詳細吧,可是blog主最好是給些代碼

          # re: 對RMI的簡單理解  回復  更多評論   

          2009-04-18 23:42 by 勉勉強強
          謝謝LZ清晰地講解,代碼和運行結果,理論和注意事項都很完備,多謝~!
          主站蜘蛛池模板: 利津县| 九龙坡区| 县级市| 呼图壁县| 思南县| 遵化市| 安义县| 苍溪县| 汝阳县| 鲁山县| 松滋市| 兖州市| 庆安县| 洪泽县| 黎城县| 临潭县| 溆浦县| 津南区| 沈丘县| 深泽县| 新余市| 贵定县| 平顺县| 栾川县| 安岳县| 房产| 蒙城县| 彭阳县| 凤城市| 大田县| 五华县| 井研县| 龙川县| 吉隆县| 遵义县| 泸州市| 龙南县| 高邮市| 安新县| 玉田县| 丹江口市|