1. 程式人生 > >簡單的RPC java實現

簡單的RPC java實現

 我也承認,RPC的名聲大噪之時是在2003年,那一個“衝擊波”病毒(Blaster Worm virus)襲捲全球的一年。而“衝擊波”正是用著RPC這把刀來敲開了遠端電腦的大門。當然RPC 有更多正面的應用,比如NFSWeb Service等等。

一、RPC的介紹

  什麼是RPCRemote Procedure Call,遠端過程呼叫。也就是說,呼叫過程程式碼並不是在呼叫者本地執行,而是要實現呼叫者與被呼叫者二地之間的連線與通訊。比較嚴格的定義是:Remote procedure call (RPC) is aprotocol that allows a computer program

 running on one computer to cause a subroutine on another computer to be executed without the programmer explicitly coding the details for this interaction. When the software in question is written using object-oriented principles, RPC may be referred to as remote invocation orremote method invocation
. 這樣一講,容易聯想到C/S模式的程式設計,我想是對的。RPC的基本通訊模型是基於Client/Server程序間相互通訊模型的一種同步通訊形式;它對Client提供了遠端服務的過程抽象,其底層訊息傳遞操作對Client是透明的。在RPC中,Client即是請求服務的呼叫者(Caller),而Server則是執行Client的請求而被呼叫的程式 (Callee)

  下圖是RPC呼叫協議圖:


  
   有很多文章對這張經典的圖作了很好的描述,歸納講即是:首先是建立RPC服務,約定底層的RPC傳輸通道(UDP或是TCP)。客戶端的呼叫引數根據傳輸前所提供的目的地址及RPC 上層應用程式號,通過底層的RPC

傳輸通道轉至相應的伺服器,即RPC Application Porgramme Server。客戶端隨即處於等待狀態,以伺服器等待應答或Time Out超時訊號。當伺服器端獲得了請求訊息,會根據註冊RPC時告訴RPC系統的程式入口地址執行相應的操作,並將結果返回至客戶端。當一次RPC呼叫結束後,相應執行緒傳送相應的訊號,客戶端程式便繼續執行。有三個要素來標識唯一的遠端過程:程式號、版本號、過程號。其中,程式號是用來區別一組相關的並且具有唯一過程號的遠端過程;一個程式可以有一個或幾個不同的版本;而每個版本的程式都包含一系列能被遠端呼叫的過程。(這句比較拗口難讀的話,一會兒用程式碼來解釋)同一個版本可以包含有許多可供遠端呼叫的過程,每個過程則有其唯一標示的過程號。通過版本的引入,使得不同版本下的 RPC能同時提供服務。

  至於更深入的RPC知識,就超出了文字討論的東西了,在這裡我們主要還是來談以Java實現的問題。

二、RPC應用開發步驟

  由我們上面對於RPC呼叫協議圖的講解看來,RPC的開發一般涉及三方面:
  1.定義客戶端、伺服器端的通訊協議。此處的通訊協議是指定義服務過程的名稱、呼叫引數的資料型別、返回引數的資料型別、底層傳輸型別(UDP/TCP)等等。

  2.開發客戶端程式。

  3.開發伺服器端程式。

對於RPC通訊協議的生成,最簡單的方法是利用協議編譯工具。常用的是rpcgen,不過這是一個用於生成實現RPC協議的C程式的生成器。要使用Java來實現的話,我們需要使用另外的生成器,即是下面要講的Remotetea

三、工具介紹

  說起Remotetea可能有很多朋友都不太熟悉,因為我在網上搜尋關於Remotetea的中文資料一篇也沒有。既然如此,我就略為寫幾筆吧:)

  Remotetea是一個基於GNULGPL的開源的專案,它完全在Java 2/1.1平臺上實現了ONC/RPC協議;由於是純的100%Java編寫,所以不需要任何本地的庫(native binary/libraries)

簡單的講,它就是今天我們用於代替rpcgen而開發純JavaRPC應用的工具。它的特點是:

  1. 100%的純Java開發

  2. 完整的客戶端功能,包括portmapper的訪問。

  3. 完整的伺服器端功能。

  4. 有為.x檔案設計的純Java協議編譯工具,與rpcgen相容。

  5. 基於Javaportmapper

  6. 開源的程式碼;文件支援。

下載Remotetea,請在http://sourceforge.net/project/showfiles.php?group_id=87517 這裡察看。下載bin包並解壓,可以在classes資料夾中找到jrpcgen.jaroncrpc.jarportmap.jar

四、簡單層RPC應用的Java實現

  1. RPC的不同層次介面

  其實在開發客戶端和伺服器端的程式時,RPC提供了不同層次的開發例程呼叫介面。不同層次的介面提供了對RPC不同程度級別的控制。一般可分為五個等級的程式設計介面:簡單層例程、高層例程、中間層例程、專家層例程、底層例程。其中,簡單層是為快速開發RPC應用服務而設計的,面向普通RPC應用;關於其他層例程,在這裡就暫不提及了。簡單層其函式列表如下:

  Rpc_reg( )——在某一特定型別的傳輸層上註冊一個過程,以作為提供服務的RPC程式。

  Rpc_call( )——可以遠端呼叫特定主機上的特定過程。

  Rpc_Broadcast( ) ——向指定型別的所有傳輸埠上廣播一個遠端過程呼叫請求。

實現簡單層時,便會用到我們剛才要提到的Remotetea。它可以將以類C語言語法的RPC語言進行原始碼編譯。在這裡先提一下所謂“類C語言語法的RPC語言”。

  2. RPC語言及其編譯

  RPC語言是XDR語言的控制擴充套件,與XDR語言一樣在RFC1014中定義。句法的注意事項:

  a. 有兩個保留字:“program”和“version”

  b. 一個程式定義中不能出現兩次版本名或版本號。

  c. 在一個版本的定義中,過程名稱至多隻能出現一次。

  d. 程式標識與常量和型別標識在同一空間中。

  e. 只有無符號常數才能被附值給程式,版本和過程。

所用到的檔案字尾名為.x,可以稱為x-檔案。下面即是測試用的一個test.x檔案的程式碼:

//////////////////////////////////////////////////////////

/*

* test.x: TEST Remote Procedure Function

*/

const MAXNAMELEN = 2048; /* maximum length of a test string */

typedef string test_string<MAXNAMELEN>; /* a directory entry */

/*

* THE TEST Remote Procedure Function program definition

*/

program TEST_RPC_FUNCTION_NUMBER


  version TEST_RPC_FUNCTION_VERSION

  { 
            mcps_string TEST_TEST(string) = 1; /* 
這是過程號 */

            mcps_string TEST_DO_PROCESS(string) = 2; /* 這是過程號 */

        } = 1; /* 這是程式號 */

} = 0x20000001; /* 這是版本號 */


//////////////////////////////////////////////////////////

有這個檔案以後,便可以在控制檯敲入:java -jar jrpcgen test.x,執行後則會生成這幾個檔案(jrpcgen可以支援引數編譯,請參照Remotetea的文件):testrpcClient.javatestrpc.javatestrpcServerStub.javatest_string.java

  3. 生成檔案說明

  通過用jrpcgen編譯.x檔案,將生成四個java檔案,下面看看每個檔案是幹什麼的。

  testrpc.java:這個檔案相當是c中的.h標頭檔案,它主要包括伺服器和客戶端程式變數、常量、型別等說明。

  test_string.java:從名字可以看出是字串變數相關的,我想應該也可以這麼講吧。它其實是一個XDR例程,可以對在testrpc.java檔案中定義的資料型別進行處理。

  testrpcClient.java:客戶端的標準程式框架,提供一組特定的在x-檔案中定義的遠端過程。該框架類繼承自OncRpcClientStub類:這是一個抽象類,用於在特定的客戶端上構建ONC/RPC程式的基礎類。

  testrpcServerStub.java:伺服器端的標準程式框架,提供一組特定的在x-檔案中定義的遠端過程。該框架類繼承自OncRpcServerStub類並實現OncRpcDispatchable介面:前者也是一個抽象類,用於在特定的伺服器端上構建ONC/RPC程式的基礎類;後者介面用於分配和處理來自客戶端的ONC/RPC請求。

  4. 開發客戶端程式

 有了以上的介紹,形勢就開始明朗了。我們的客戶端程式只需繼承自生成的testrpcClient這個客戶端框架類就可以了。程式碼如下:

//////////////////////////////////////////////////////////

import java.io.IOException;

import java.net.InetAddress;

import java.net.UnknownHostException;

import org.acplt.oncrpc.OncRpcClient;

import org.acplt.oncrpc.OncRpcException;

import org.acplt.oncrpc.OncRpcProtocols;

import testrpcClient ;

/*

* @author Noshoeman

*/

public class TestClient extends testrpcClient {

//可以有很多種建構函式,有較大靈活性,這裡只寫一種。

    /**

    * @param host

    * @param port

    * @param protocol

    * @throws OncRpcException

    * @throws IOException   

    */

    public TestClient(InetAddress host, int port, int protocol)

        throws OncRpcException, IOException {

        super(host, port, protocol);

        //不需要做任何事。

   }

    /**

    * @param args

    * 這裡是測試用的主函式

    */

    public static void main(String[] args) {

        //我們在單機測試,取得本地資訊

        System.out.println("--Start client.--");

        InetAddress address = null;

        try {

            address = InetAddress.getLocalHost();

        } catch (UnknownHostException e) {

            e.printStackTrace();

        }

        //構造客戶端並進行測試

        try {

            TestClient client = new TestClient(address,2023,OncRpcProtocols.ONCRPC_TCP);

            client.TEST_DO_PROCESS ("Hello!");

            client.close();

        } catch (OncRpcException e) {

            e.printStackTrace();

        } catch (IOException e) {

            e.printStackTrace();

        }

    }

}

//////////////////////////////////////////////////////////

  整個過程沒有什麼需要多提的;注意的是在new客端的時候,埠2023是我隨便寫的,最後的OncRpcProtocols.ONCRPC_TCP,即是我們在前面的提到的約定底層的RPC傳輸通道,這裡我們使用的是TCP(數值好像是6),也可以換為UDP。這樣,一個簡單的客戶端測試程式就寫好了。

  5. 開發伺服器端程式

  和客戶端程式的開發經驗類似,也是繼承自 testrpcServerStub 這個框架類。與客戶端不同的是,在這裡我們就需要實現遠端過程的響應。示例程式碼如下:

//////////////////////////////////////////////////////////

import java.net.UnknownHostException;

import java.io.IOException;

import java.net.InetAddress;

import org.acplt.oncrpc.OncRpcException;

import test_string;

import testrpcServerStub;


/**

* @author Noshoeman

*/

public class TestServer extends testrpcServerStub {

    /**

    * @param bindAddr

    * @param port

    * @throws OncRpcException

    * @throws IOException

    */

    public TestServer(InetAddress bindAddr, int port) throws OncRpcException,

        IOException {

        super(bindAddr, port);

    }

    /*

    * 這是第一個遠端過程

    * @see testrpcServerStub#TEST_TEST(java.lang.String)

    */

    public mcps_string TEST_TEST(String arg1) {

        System.out.println("This is test function! " + arg1);

        return null;

    }


    /*

    * 這是第二個遠端過程

    * @see testrpcServerStub#TEST_DO_PROCESS(java.lang.String)

    */

    public mcps_string TEST_DO_PROCESS(String arg1) {

        System.out.println("Got msg from client: " + arg1);

        return null;

    }


    /**

    * 伺服器端的主函式

    * @param args

    */

    public static void main(String[] args) {

        try {

            System.out.println("Server starting...");

            InetAddress address = null;

            try {

                address = InetAddress.getLocalHost();

                System.out.println(address.toString());

            } catch (UnknownHostException e) {

                System.out.println("-------");

                e.printStackTrace();

            }

            TestServer server = new TestServer(address, 2023);

            System.out.println("Is server null? " + (server == null ? true : false));

            server.run();

        } catch (Exception e) {

            e.printStackTrace();

        }

    }