Linux RPC編程

1. RPC簡介

RPC,英文全稱爲Remote Procedure Call Protocol,也就是遠程過程調用協議。所謂遠程,顧名思義,就不是本地,在傳統的編程概念中,過程是由程序員在本地編譯完成,並只能侷限在本地運行的一段代碼,也即其主程序和過程之間的運行關係是本地調用關係。但是,這種傳統調用模式有一個最大的弊端,就是無法充分利用網絡上其他主機的資源(如CPU, Memory等等)。而是通過RPC, 我們可以充分利用共享內存的多處理器環境(例如通過局域網連接的多臺工作站),這樣就可以簡單地將你的應用分佈在多臺工作站上,應用程序就像運行在一個多處理器的計算機上一樣。你可以方便的實現過程代碼共享,提高系統資源的利用率。

一般來說,我們目前談論的最多的就是SUN公司的RPC,由於其開源性,使得SUN RPC得以大規模的使用,所以,我們這裏討論的就是SUN RPC。當然還有其他公司定義的RPC,這個有興趣的請自行去了解下。

 

2. RPC工作原理

RPC採用客戶機/服務器模式,有點類似C/S Socket 編程模式,但要比它更高一層。請求程序就是一個客戶機,而服務提供程序就是一個服務器。首先,客戶機調用進程發送一個有進程參數的調用信息到服務進程,然後等待應答信息。在服務器端,進程保持睡眠狀態直到調用信息的到達爲止。當一個調用信息到達,服務器獲得進程參數,計算結果,發送答覆信息,然後等待下一個調用信息,最後,客戶端調用進程接收答覆信息,獲得進程結果,然後調用執行繼續進行。可參考以下圖來理解:

clipboard

這個圖很清楚的說明了RPC的工作流程,就不在一一說明了,更詳細的資料可以參考《UNIX網絡編程 第2卷 進程間通信》有關SUN RPC這一章節,裏面詳盡描述了整個RPC過程調用。

 

3. RPC開發流程:

一般而言在開發RPC時,我們通常分爲三個步驟:
(1)定義說明客戶/服務器的通信協議。
這裏所說的通信協議是指定義服務過程的名稱、調用參數的數據類型和返回參數的數據類型,還包括底層傳輸類型(可以是UDP或TCP),當然也可以由RPC底層函數自動選擇連接類型建立TI-RPC。最簡單的協議生成的方法是採用協議編譯工具,常用的有Rpcgen,整個是SUN RPC提供的運行例庫。
(2)開發客戶端程序。
(3)開發服務器端程序。

 

4. RPC編程開發一般思路:

客戶端: 調用相關的函數,創建句柄,使用tcp/udp進行通信。

服務器: 以線程運行,等待客戶端發過來的鏈接請求,再調用相關的函數處理後再返回相關的值。

重要相關知識要點:

(1) 如果有多個CLIENT *cln句柄的問題,要注意其創建及管理,同時要考慮多線程的問題,可以使用線程存儲來解決。

(2)創建TCP/UDP協議的CLIENT *clnt句柄。可以細分到創建tcp/udp類型的socket。

(3)客戶端中UPD PORT限制範圍的問題。比如,如何使用自己定義的範圍,而不是讓系統自動分配。

 

5. 關於程序號,版本號和過程號的說明

前面說到客戶/服務器之間的通信協議,不得不說下程序號,版本號和過程號。首先,我們要知道如何表示一個唯一存在的遠程過程?當一次RPC調用結束後,相應線程發送相應的信號,客戶端程序纔會繼續運行。當然,一臺服務主機上可以有多個遠程過程提供服務,那麼,問題來了,如何來表示一個唯一存在的遠程過程呢?一個遠程過程是有三個要素來唯一確定的:程序號、版本號和過程號。程序號是用來區別一組相關的並且具有唯一過程號的遠程過程。一個程序可以有一個或幾個不同的版本,而每個版本的程序都包含一系列能被遠程調用的過程,通過版本的引入,使得不同版本下的RPC能同時提供服務。每個版本都包含有許多可供遠程調用的過程,每個過程則有其唯一標示的過程號。

例如,下面就是一個實例,我們的rpc.x文件定義如下:

#define RPC1_RPC_PROG_NUM 0x38000023 //this should be bigger than 0x38000000

/* data Structure */

struct _rpc1_data_t {

        int type; /* 1: time, 2: pid number */

        char data[0];

};

typedef struct _rpc1_data_t rpc1_data_t;

program RPC1_RPC_PROG {

        version RPC1_RPC_VERS1 {

                int RPC1_RPCC(rpc1_data_t) = 1; /* Procedure number = 1 */

        } = 1; /* Version number = 1 */

} = RPC1_RPC_PROG_NUM; /* Program number */

 

6. 關於Rpcgen

rpcgen可以說是SUN公司提供的一個快速自動生成相關RPC代碼的一個工具。它的輸入爲一個規格說明文件,它的輸出爲一個C語言的源程序。規格文件(*.x)包含常量、全局數據類型以及遠程過程的聲明。Rpcgen產生的代碼包含了實現客戶機和服務器程序所需要的大部分源代碼。它包括參數整理、發送RPC報文、參數和結果的外部數據表示以及本地數據表示的轉換等。不過在由rpcgen生成的源文件中,沒有過程的具體實現,所以程序員必須要手工編輯這些文件,實現這些過程。

一般Linux上都默認安裝好了rpcgen, 通過查看幫助,可以快速產生rpc相關的代碼文件。

xx@xx ~ $ rpcgen --h

usage: rpcgen infile

rpcgen [-abkCLNTM][-Dname[=value]] [-i size] [-I [-K seconds]] [-Y path] infile

rpcgen [-c | -h | -l | -m | -t | -Sc | -Ss | -Sm] [-o outfile] [infile]

rpcgen [-s nettype]* [-o outfile] [infile]

rpcgen [-n netid]* [-o outfile] [infile]

options:

-a generate all files, including samples

-b backward compatibility mode (generates code for SunOS 4.1)

-c generate XDR routines

-C ANSI C mode

-Dname[=value] define a symbol (same as #define)

-h generate header file

-i size size at which to start generating inline code

-I generate code for inetd support in server (for SunOS 4.1)

-K seconds server exits after K seconds of inactivity

-l generate client side stubs

-L server errors will be printed to syslog

-m generate server side stubs

-M generate MT-safe code

-n netid generate server code that supports named netid

-N supports multiple arguments and call-by-value

-o outfile name of the output file

-s nettype generate server code that supports named nettype

-Sc generate sample client code that uses remote procedures

-Ss generate sample server code that defines remote procedures

-Sm generate makefile template

-t generate RPC dispatch table

-T generate code to support RPC dispatch tables

-Y path directory name to find C preprocessor (cpp)

For bug reporting instructions, please see:

<http://bugs.gentoo.org/>.

xx@xx ~ $

通過rpcgen,可以產生以下文件:

(1)一個頭文件(.h)包括服務器和客戶端程序變量、常量、類型等說明。

(2)一系列的XDR例程,它可以對頭文件中定義的數據類型進行處理。

(3) 一個Server 端的標準程序框架。

(4) 一個Client 端的標準程序框架。

舉例說明,比如如何利用前面的rpc.x文件,通過rpcgen來產生相關的客戶端和服務器rpc代碼呢?如下:

(1)#rpcgen -a [-C] rpc.x                           //產生所有的模板文件

(2)#rpcgen -h rpc.x  -o rpc.h                   //產生頭文件

(3)#rpcgen -Sc rpc.x -o rpc_client.c        //產生客戶端的模板文件

(4)#rpcgen -Ss rpc.x  -o rpc_server.c       //產生服務器的模板文件

(5)#rpcgen -Sm rpc.x                               //產生Makefile文件

(6)#rpcgen -l rpc.x -o rpc_clnt.c              //產生客戶端stub

(7)#rpcgen -m rpc.x -o rpc_svc.c             //產生服務器stub

(8)#rpcgen -c rpc.x -o rpc_xdr.c              //產生xdr文件

 

7. 關於rpc編程中需要注意的知識點

(1)關於rpc timeout問題

由於是遠程調用,所以相對於本地調用來說,rpc所要花費的時間更長,很容易出現timeout的問題。注意,因TCP是可靠的面向連接的協議,有自己的一套機制來保證端到端的傳輸可靠性,故使用TCP建立的應用不需要考慮超時重傳等問題。、

1. 總超時值:一個客戶端等待其服務器的應答的總時間量。TCP和UDP都是用該值;

2. 重試超時:只用於UDP, 是一個客戶等待其服務器的應答期間每次重傳請求的間隔時間;

默認的超時時間可以通過clnt_control(CLGET_TIMEOUT/CLGET_RETRY_TIMEOUT)函數獲取。

struct timeval tv;

clnt_control(clnt, CLGET_TIMEOUT, (char *)&tv);

clnt_control(clnt, CLGET_RETRY_TIMEOUT, (char *)&tv);

printf("timeout = %d : timeout = %d\n", tv.tv_sec, tv.tv_usec);

至於這兩個值具體是多少,我沒有驗證過,看到有網友說是:UDP的默認總超時值爲-1,重傳超時值爲5s(unp書上說是15s), TCP的默認總超時值爲0(unp書上說是30s),而爲什麼跟書上不同,他的解釋是由於RPC的版本不一樣,這一點,有待驗證。而這兩個值,我們可以通過替換成CLSET_TIMEOUT/CLSET_RETRY_TIMEOUT就可以設置我們需要的timeout值。

(2)如何指定RPC port的範圍

在原有的rpc port-mapping service中,sunrpc保留512~1024作爲保留的端口號使用,也就是說如果用原來的port-mapping, 就可能會使用到這個範圍的端口號,如果要使用1024以上的端口號,那麼改如果修改呢?

在svc_register中最後一個參數port,傳遞port=0進去,進而就不會再調用sunrpc本身自帶的pmap_set()函數了,我們可以重寫屬於我們自己的pmap_set()函數,跟自帶的pmap_set()函數的區別在於,自帶的函數使用的是socke == 1去調用clntudp_bufcreate(),而我們可以自己先創建好UDP的socket再去調用此函數。這樣,就可以使用我們指定的port number了。

 

8. 關於RPC 接口說明

開發客戶端和服務器端的程序時,RPC提供了我們不同層次的開發例程調用接口。不同層次的接口提供了對RPC不同程度控制。一般可分爲5個等級的編程接口,接下來我們分別討論一下各層所提供的功能函數.

(1)簡單層例程

簡單層是面向普通RPC應用,爲了快速開發RPC應用服務而設計的,他提供瞭如下功能函數。
函數名 功能描述
Rpc_reg( )                 在一特定類型的傳輸層上註冊某個過程,來作爲提供服務的RPC程序
Rpc_call( )                 遠程調用在指定主機上指定的過程
Rpc_Broadcast( )      向指定類型的所有傳輸端口上廣播一個遠程過程調用請求

(2)高層例程
在這一層,程序需要在發出調用請求前先創建一個客戶端句柄,或是在偵聽請求前先建立一個服務器端句柄。程序在該層可以自由的將自己的應用綁在所有的傳輸端口上,它提供瞭如下功能函數。
函數名    功能描述
Clnt_create( )            程序通過這個功能調用,告訴底層RPC服務器的位置及其傳輸類型
Clnt_create_timed( ) 定義每次嘗試連接的超時最大時間
Svc_create( )             在指定類型的傳輸端口上建立服務器句柄,告訴底層RPC事件過程的相應入口地址
Clnt_call()                     向服務器端發出一個RPC調用請求

(3)中間層例程
中間層向程序提供更爲詳細的RPC控制接口,而這一層的代碼變得更爲複雜,但運行也更爲有效,它提供瞭如下功能函數。
函數名        功能描述
Clnt_tp_create( )            在指定的傳輸端口上建立客戶端句柄
Clnt_tp_create_timed( ) 定義最大傳輸時延
Svc_tp_creaet( )             在指定的傳輸端口上建立服務句柄
Clnt_call( )                      向服務器端發出RPC調用請求

(4)專家層例程
這層提供了更多的一系列與傳輸相關的功能調用,它提供瞭如下功能函數。
函數名    功能描述
Clnt_tli_create( )    在指定的傳輸端口上建立客戶端句柄
Svc_tli_create( )     在指定的傳輸端口上建立服務句柄
Rpcb_set( )            通過調用rpcbind將RPC服務和網絡地址做映射
Rpcb_unset( )       刪除rpcb_set( ) 所建的映射關係
Rpcb_getaddr( )   調用rpcbind來犯會指定RPC服務所對應的傳輸地址
Svc_reg( )             將指定的程序和版本號與相應的時間例程建起關聯
Svc_ureg( )           刪除有svc_reg( ) 所建的關聯
Clnt_call( )            客戶端向指定的服務器端發起RPC請求

(5)底層例程
該層提供了所有對傳輸選項進行控制的調用接口,它提供瞭如下功能函數。
函數名    功能描述
Clnt_dg_create( )      採用無連接方式向遠程過程在客戶端建立客戶句柄
Svc_dg_create( )    採用無連接方式建立服務句柄
Clnt_vc_create( )    採用面向連接的方式建立客戶句柄
Svc_vc_create( )     採用面向連接的方式建立RPC服務句柄
Clnt_call( )             客戶端向服務器端發送調用請求

說明:其實這麼多接口不需要去記,我們平時常用的就那麼幾個,而其他的可以在用到時再去查詢下用法即可。

 

注:本文部分資料參考了網絡上相關資源,因數目較多,就不一一列舉,若涉及到您的版權問題,請與我聯繫,我會及時刪除,謝謝。同時,若你喜歡這篇文章,請在轉載時註明出處,謝謝合作!

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