本文簡單介紹了RPC(Remote Procedure Call 遠端程序呼叫)的原理結構、特點,及其開放給編程人員不同層次的編程介面。並且例舉執行個體示範如何通過Rpcgen 編譯工具來快速開發RPC應用。
摘要:
本文簡單介紹了RPC(Remote Procedure Call 遠端程序呼叫)的原理結構、特點,
及其開放給編程人員不同層次的編程介面。並且例舉執行個體示範如何通過Rpcgen 編譯工
具來快速開發RPC應用。
一、 概述
在傳統的編程概念中,過程是由程式員在本地編譯完成,並只能局限在本地啟動並執行一段
代碼,也即其主程式和過程之間的運行關係是本地調用關係。因此這種結構在網路日益
發展的今天已無法適應實際需求。總所周知,傳統程序呼叫模式無法充分利用網路上其
他主機的資源(如CPU、Memory等),也無法提高代碼在實體間的共用程度,使得主機資
源大量浪費。
而本文要介紹的RPC編程,正是很好地解決了傳統過程所存在的一系列弊端。通過RPC我
們可以充分利用非共用記憶體的多處理器環境(例如通過局域汪串連得多台工作站),這樣
可以簡便地將你的應用分布在多台工作站上,應用程式就像運行在一個多處理器的電腦
上一樣。你可以方便的實現過程代碼共用,提高系統資源的利用率,也可以將以大量數值
處理的操作放在處理能力較強的系統上運行,從而減輕前端機的負擔。
二、 RPC的結構原理及其調用機制
如前所述RPC其實也是種C/S的編程模式,有點類似C/S Socket 編程模式,但要比它
更高一層。當我們在建立RPC服務以後,用戶端的調用參數通過底層的RPC傳輸通道,可以
是UDP,也可以是TCP(也即TI-RPC-無關性傳輸),並根據傳輸前所提供的目的地址及RPC
上層應用程式號轉至相應的RPC Application Porgramme Server ,且此時的用戶端處於等
待狀態,直至收到應答或Time Out逾時訊號。具體的流程圖如F1。當伺服器端獲得了請求
訊息,則會根據註冊RPC時告訴RPC系統的常式入口地址,執行相應的操作,並將結果返回
至用戶端。
F1
當一次RPC調用結束後,相應線程發送相應的訊號,用戶端程式才會繼續運行。
當然,一台服務主機上可以有多個遠程過程提供服務,那麼如何來表示一個唯一存
在的遠程過程呢。一個遠程過程是有三個要素來唯一確定的:程式號、版本號碼和過程號。
程式號是用來區別一組相關的並且具有唯一過程好的遠程過程。一個程式可以有一個或幾
個不同的版本,而每個版本的程式都包含一系列能被遠程調用的過程,通過版本的引入,
使得不同版本下的RPC能同時提供服務。每個版本都包含有許多可供遠程調用的過程,每個
過程則有其唯一標示的過程號。
三、 基於RPC的應用系統開發
通過以上對RPC原理的簡介後,我們再來繼續討論如何來開發基於RPC的應用系統。
一般而言在開發RPC時,我們通常分為三個步驟:
a、 定義說明客戶/伺服器的通訊協定。
這裡所說的通訊協定是指定義服務過程的名稱、調用參數的資料類型和返回參數的資料
類型,還包括底層傳輸類型(可以是UDP或TCP),當然也可以由RPC底層函數自動選擇
連線類型建立TI-RPC。最簡單的協議產生的方法是採用協議編譯工具,常用的有Rpcgen,
我會在後面執行個體中詳細描述其使用方法。
b、 開發用戶端程式。
c、 程式開發伺服器端程式。
開發用戶端和伺服器端的程式時,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( ) 用戶端向伺服器端發送調用請求
四、 執行個體介紹
以下我將通過執行個體向讀者介紹通過簡單層RPC的實現方法。通常在此過程中我們
將使用RPC協議編譯工具-Rpcgen。Rpcgen 工具用來產生遠程程式介面模組,它將以RPC
語言書寫的原始碼進行編譯,Rpc 語言在結構和文法上同C語言相似。由Rpcgen 編譯生
成的C來源程式可以直接用C編譯器進行編譯,因此整個編譯工作將分為兩個部分。Rpcgen
的來源程式以.x結尾,通過其編譯將產生如下檔案:
a) 一個標頭檔(.h)包括伺服器和用戶端程式變數、常量、類型等說明。
b) 一系列的XDR常式,它可以對標頭檔中定義的資料類型進行處理。
c) 一個Server 端的標準程式架構。
d) 一個Client 端的標準程式架構。
當然,這些輸出可以是選擇性的,Rpcgen 的編譯選項說明如下:
選項 功能
'-' a 產生所有的模板檔案
'-' Sc 產生用戶端的模板檔案
'-' Ss 產生伺服器端的模板檔案
'-' Sm 產生Makefile 檔案
(詳見Solaris Rpcgen Manaul)
Rpcgen 來源程式 time.x:
/* time.x: Remote time printing protocol */
program TIMEPROG {
version PRINTIMEVERS {
string PRINTIME(string) = 1;
} = 1;
} = 0x20000001;
time_proc.c來源程式:
/* time_proc.c: implementation of the remote procedure "printime" */
#include <stdio.h>
#include <rpc/rpc.h> /* always needed */
#include "time.h" /* time.h will be generated by rpcgen */
#include <time.h>
/* Remote version of "printime" */
char ** printime_1(char **msg,struct svc_req *req)
{
static char * result; /* must be static! */
static char tmp_char[100];
time_t rawtime;
FILE *f;
f = fopen("/tmp/rpc_result", "a+");
if (f == (FILE *)NULL) {
strcpy(tmp_char,"Error");
result = tmp_char;;
return (&result);
}
fprintf(f, "%sn", *msg); //used for debugging
fclose(f);
time(&rawtime);
sprintf(tmp_char,"Current time is :%s",ctime(&rawtime));
result =tmp_char;
return (&result);
}
rtime.c原始碼
/*
* rtime.c: remote version
* of "printime.c"
*/
#include <stdio.h>
#include "time.h" /* time.h generated by rpcgen */
main(int argc, char **argv)
{
CLIENT *clnt;
char *result;
char *server;
char *message;
if (argc != 3) {
fprintf(stderr, "usage: %s host messagen", argv[0]);
exit(1);
}
server = argv[1];
message = argv[2];
/*
* Create client "handle" used for
* calling TIMEPROG on the server
* designated on the command line.
*/
clnt = clnt_create(server, TIMEPROG, PRINTIMEVERS, "visible");
if (clnt == (CLIENT *)NULL) {
/*
* Couldn't establish connection
* with server.
* Print error message and die.
*/
clnt_pcreateerror(server);
exit(1);
}
/*
* Call the remote procedure
* "printime" on the server
*/
result =*printime_1(&message,clnt);
if (result== (char *)NULL) {
/*
* An error occurred while calling
* the server.
* Print error message and die.
*/
clnt_perror(clnt, server);
exit(1);
}
/* Okay, we successfully called
* the remote procedure.
*/
if (strcmp(result,"Error") == 0) {
/*
* Server was unable to print
* the time.
* Print error message and die.
*/
fprintf(stderr, "%s: could not get the timen",argv[0]);
exit(1);
}
printf("From the Time Server ...%sn",result);
clnt_destroy( clnt );
exit(0);
}
有了以上的三段代碼後,就可用rpcgen 編譯工具進行RPC協議編譯,命令如下:
$rpcgen time.x
rpcgen 會自動產生time.h、time_svc.c、time_clnt.c
再用系統提供的gcc進行C的編譯,命令如下:
$gcc rtime.c time_clnt.c -o rtime -lnsl //用戶端編譯
$gcc time_proc.c time_svc.c -o time_server -lnsl //伺服器端編譯
編譯成功後即可在Server端運行time_server,立即將該服務綁定在rpc服務連接埠上提供
服務。在用戶端運行./rdate hostname msg (msg 是一字串,筆者用來測試時建立的),
立即會返回hostname 端的時間。
由於,在Sun Solaris 中無法擷取遠端Server 上時鐘資訊的功能(不改變本
地Server時鐘),筆者曾將此程式應用於計費伺服器同時鐘伺服器同步監測的網管
系統中,運行穩定,獲得了較好的效果。應該說RPC的應用是十分廣泛的,特別是
在分散式運算領域中尤為顯得重要。當然,筆者也是剛接觸RPC,還有很多地方了
解的不夠深刻,望廣大讀者多指教。
參考文獻:
《SUN Solaris8 ONC+ Dev》