遠端程序呼叫(RPC)

來源:互聯網
上載者:User
 

一、概述

在傳統的編程概念中,過程是由程式員在本地編譯完成,並只能局限在本地啟動並執行一段代碼,也即其主程式和過程之間的運行關係是本地調用關係。因此這種結構在網路日益發展的今天已無法適應實際需求。總而言之,傳統程序呼叫模式無法充分利用網路上其他主機的資源(如CPU、 Memory等),也無法提高代碼在實體間的共用程度,使得主機資源大量浪費。
而本文要介紹的RPC編程,正是很好地解決了傳統過程所存在的一系列弊端。通過RPC我們可以充分利用非共用記憶體的多處理器環境(例如通過區域網路串連的多台工作站),這樣可以簡便地將你的應用分布在多台工作站上,應用程式就像運行在一個多處理器的電腦上一樣。你可以方便的實現過程代碼共用,提高系統資源的利用率,也可以將以大量數值處理的操作放在處理能力較強的系統上運行,從而減輕前端機的負擔。
二、RPC的結構原理及其調用機制

如前所述RPC其實也是一種C/S的編程模式,有點類似C/S Socket 編程模式,但要比它更高一層。當我們在建立RPC服務以後,用戶端的調用參數通過底層的RPC傳輸通道,可以是UDP,也可以是TCP(也即TI-RPC —無關性傳輸),並根據傳輸前所提供的目的地址及RPC上層應用程式號轉至相應的RPC Application Porgramme Server ,且此時的用戶端處於等待狀態,直至收到應答或Time Out逾時訊號。具體的流程圖如圖1。當伺服器端獲得了請求訊息,則會根據註冊RPC時告訴RPC系統的常式入口地址,執行相應的操作,並將結果返回至用戶端。
當一次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, "%s ", *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 message ", 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 time ",argv[0]);
exit(1);
}
printf("From the Time Server ...%s ",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 //

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.