【雜談】對RMI(Remote Method Invoke)的認識

貓毛·波拿巴發表於2018-11-15

前言

  對RMI接觸的也比較早,基本上剛學完Java基礎不久就機緣巧合遇到了。當時有嘗試著去了解,但是沒看到比較好的教程,而且對網路程式設計相關知識不太瞭解,看了不少文章,也沒弄明白。現在對網路和I/O有了一定了解,理解起來也比較順暢了。以下,是我對RMI的認識。

RMI的相關概念

  "In computing, the Java Remote Method Invocation (Java RMI) is a Java API that performs remote method invocation, the object-oriented equivalent of remote procedure calls (RPC), with support for direct transfer of serialized Java classes and distributed garbage-collection." ——維基百科

  上述定義指明幾個點

  • RMI是Java用於呼叫遠端方法的API ——①屬於Java  ②指的是一個API,不是協議或者其他什麼東西
  • 相當於物件導向版的RPC —— RPC是遠端過程呼叫(程式導向)
  • 支援直接傳輸可序列化的Java類 ——①傳輸的類是方法呼叫需要的引數和返回值,而不是實現類,實現類的方法在伺服器執行。
  • 支援分散式垃圾回收(暫未涉獵)

遠端呼叫

  遠端呼叫並不是載入遠端的類到本地執行,而是通過傳遞方法引數到伺服器,讓伺服器執行相應的方法並返回結果給客戶端。

網路協議與IO

  RMI所指的遠端呼叫,說的其實是從一個JVM的物件呼叫另一個JVM環境下物件方法的過程。兩個JVM可以在不同的主機上,也可以在同一臺主機上。如果在不同主機上,則方法呼叫必然需要網路傳輸,網路傳輸則必然與Socket通訊以及相關的通訊協議掛鉤,RMI用的底層協議是JRMIP。值得一提的是,使用RMI這個API的好處就是,使用者不需要實現底層的網路通訊與IO,或者說網路傳輸對使用者來說是透明的。

遠端介面

  客戶端和服務端都有的介面,只不過介面的實現類在服務端。

RMI的工作原理

  

  值得一提的是,我在學習RMI的時候,故我根據理解畫了這張圖。書上講到需要用rmic指令生成stub和skeleton。但是實際上,Java後面已經廢除了這種方式,skeleton被認為不再需要,但是伺服器仍然有一些東西負責skeleton的行為(誰負責,暫不清楚,尚未了解底層實現)。而stub可以動態生成。所以用這張圖理解RMI還是沒問題的。

RMI遠端服務的實現

相關概念

Remote

  java.rmi.server包下的定義的介面,遠端介面必須繼承與RMI的Remote介面,表明其遠端介面的身份。

RMI registry

  RMI的登錄檔服務,伺服器需要將對應的服務註冊到登錄檔上,此操操作將會繫結對應服務的stub(存根)。客戶端會根據伺服器的IP地址找到伺服器,然後在伺服器的登錄檔上找對應的存根。

UnicastRemoteObject

   遠端介面的實現類必須繼承此類,以用於生成與底層JRMP協議相關的通訊物件,以及用於與通訊物件連線的stub(存根)。

服務端實現步驟

  1. 定義遠端介面

    定義一個介面,繼承於Remote介面。注意,定義的方法需要丟擲RemoteException。因為網路和I/O是不安全的,必須讓客戶意識到這點,做相應的異常處理準備。

  2. 定義遠端介面實現

    定義一個類實現上述介面,並繼承UnicastRemoteObject。可在此類,或其他類中的main方法中,註冊服務到登錄檔表。

    Naming.rebind("serviceName", service);   

  3. 啟動RMI registry登錄檔服務

    利用命令列啟動。啟動命令所在的目錄必須能夠訪問到你的類。最好在實現類目錄啟動。

    %rmiregistry           

  4. 開啟遠端服務(註冊服務到登錄檔)

    執行,帶有註冊操作的類。

客戶端實現步驟

  1. 定義遠端介面

    與服務端的介面相同,直接拷貝過來即可。

  2. 定義訪問遠端介面的類

    客戶端需要定義一個類,用於連線到服務端的登錄檔,根據服務的相關資訊查詢並獲取stub(存根)。

    Naming.lookup("rmi://ip:port/serviceName");   

   3. 根據stub訪問遠端介面方法。

RMI樣例程式碼

檔案圖

 

注:在單機環境下模擬,客戶端服務端在不同的資料夾。 

服務端

MyRemote.java

import java.rmi.*;

public interface MyRemote extends Remote {
    public String sayHello() throws RemoteException;
}
View Code

MyRemoteImpl.java

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

public class MyRemoteImpl extends UnicastRemoteObject implements MyRemote {
    //實現遠端方法
    public String sayHello() {
        return "Server says, 'Hey'";
    }

    public MyRemoteImpl() throws RemoteException {

    }

    //主方法,註冊服務到登錄檔
    public static void main(String[] args) {
        try {
            //建立遠端物件
            MyRemote service = new MyRemoteImpl();
            //繫結到登錄檔
            //rebind方法會覆蓋同名方法,相當於先unbind再bind.
            Naming.rebind("RemoteHello", service);
        } catch(Exception e) {
            e.printStackTrace();
        }
    }
}
View Code

啟動RMI Registry登錄檔服務,注意,此命令沒有任何響應,就是開啟成功了,不要關閉此彈窗

啟動服務,開啟另一個控制檯,執行主程式。此主程式同樣沒有任何輸出。

客戶端

MyRemote同上

MyRemoteClient.java

import java.rmi.*;

public class MyRemoteClient {
    public static void main(String[] args) {
        new MyRemoteClient().go();
    }

    public void go() {
        try {
            //查詢對應服務,並獲取到存根
            MyRemote service = (MyRemote) Naming.lookup("rmi://127.0.0.1/RemoteHello");
            //利用存根呼叫介面方法
            String s = service.sayHello();
            //輸出返回值
            System.out.println(s);
        } catch(Exception e) {
            e.printStackTrace();
        }
    }
}
View Code

啟動客戶端,開啟另一個控制檯,啟動客戶端主程式

 為什麼遠端介面可直接呼叫Stub

  遠端介面能直接呼叫stub,那很明顯,stub就是遠端介面的實現類。前面我們提到了,用rmic命令工具生生成stub的方式已經被廢除了。stub可以動態生成。但是使用rmic命令工具還是可以生成_stub的class檔案。雖然有相關的警告。

我們可以用反編譯工具,來檢視其原始碼。我使用的是jd-gui。

即,stub確實就是遠端介面的實現類。注意,只是語法上實現,真正的實現在服務端。stub算是一個代理,讓客戶端可以像操作實現類那樣操作stub,好像實現類就在本地一樣。

 

相關文章