RPC(Remote Procedure Call)——遠程過程調用,它是一種經過網絡從遠程計算機程序上請求服務,而不須要了解底層網絡技術的細節的技術。前端
經過RPC咱們能夠充分利用非共享內存的多處理器環境(例如經過局域網鏈接得多臺工做站),這樣能夠簡便地將你的應用分佈在多臺工做站上,應用程序就像運行在一個多處理器的計算機上同樣。你能夠方便的實現過程代碼共享,提升系統資源的利用率,也能夠將以大量數值處理的操做放在處理能力較強的系統上運行,從而減輕前端機的負擔。程序員
在OSI網絡通訊模型中,RPC跨越了傳輸層和應用層。RPC使得開發包括網絡分佈式多程序在內的應用程序更加容易。數據庫
RPC 的主要目的是爲組件提供一種相互通訊的方式,使這些組件之間可以相互發出請求並傳遞這些請求的結果。數組
客戶機對服務器的一次RPC調用,其內部操做大體有以下十步:服務器
1.調用客戶端句柄;執行傳送參數微信
2.調用本地系統內核發送網絡消息網絡
3.消息傳送到遠程主機tcp
4.服務器句柄獲得消息並取得參數分佈式
5.執行遠程過程函數
6.執行的過程將結果返回服務器句柄
7.服務器句柄返回結果,調用遠程系統內核
8.消息傳回本地主機
9.客戶句柄由內核接收消息
10.客戶接收句柄返回的數據
RPC在分佈式系統中的系統環境建設和應用程序設計中有着普遍韻應用,應用包括以下方面:
1. 分佈式操做系統的進程間通信
進程間通信是操做系統必須提供的基本設施之一,分佈式操做系統必須提供分佈於異構的結點機上進程間的通信機制,RPC是實現消息傳送模式的分佈式進程間通信的手段之一。
2. 構造分佈式計算的軟件環境
因爲分佈式軟件環境自己地理上的分佈性,它的各個組成成份之間存在大量的交互和通信,RPC是其基本的實現方法之一。ONC+和DCE兩個流行的分式布式計算軟件環境都是使用RPC構造的,其它一些分佈式軟件環境也採用了RPC方式。
3. 遠程數據庫服務
在分佈式數據庫系統中,數據庫通常在服務器上,客戶機經過遠程數據庫服務功能訪問數據庫服務器,現有的遠程數據庫服務是使用RPC模式的。鍀如,Sybase和Oracle都提供了存儲過程機制,系統與用戶定義的存儲過程存儲在數據庫服務器上,用戶在客戶端使用RPC模式調用存儲過程。
4. 分佈式應用程序設計
RPC機制與RPC工具爲分佈式應用程序設計提供了手段和方便,用戶能夠無需知道網絡結構和協議細節而直接使用RPC工具設計分佈式應用程序。
5. 分佈式程序的調試
RPC可用於分佈式程序的調試。使用反RPC使服務器成爲客戶並向它的客戶進程發出RPC,能夠調試分佈式程序。例如.在服務器上運行一個遠端調試程序,它不斷接收客戶端的RPC,當遇到一個調試程序斷點時,它向客戶機發回一個RPC,通知斷點已經到達,這也是RPC用於進程通信的例子。
Rpcgen是一個編譯器,她能夠自動生成RPC服務器程序的大多數代碼,它的輸入爲一個規格說明文件(*.x),它的輸出爲一個C語言的源程序。
規格文件(*.x)包含常量、全局數據類型以及遠程過程的聲明。rpcgen產生的代碼包含了實現客戶機和服務器程序所須要的大部分源代碼,包括參數整理、發送RPC報文、參數和結果的外部數據表示以及本地數據表示的轉換等。不過在由rpcgen生成的源文件中,沒有過程的具體實現,因此程序員必需要手工編輯這些文件,實現這些過程。
規格文件是使用RPC語言編寫的,RPC語言是XDR語言的擴展,RPC對XDR的惟一擴展,就是增長了program類型。下面來介紹一下XDR語言的語法。
XDR(External Data Representation)是SunSoft的開放網絡計算環境的一種功能。XDR提供了一種與體系結構無關的表示數據,解決了數據字節排序的差別、數據字節大小、數據表示和數據對準的方式。使用XDR的應用程序,能夠在異構硬件系統上交換數據。
外部數據表示法(XDR) 是在OSI模型的表示層presentation layer中實現。XDR容許把數據包裝在獨立於介質的結構中使得數據能夠在異構的計算機系統中傳輸。從局部表示轉換到XDR稱爲編碼,從XDR轉換到局部表示稱爲譯碼。XDR使用軟件來完成變換,因此在不一樣的操做系統中能夠靈活的運用。另外,XDR仍是獨立於傳輸層的transport layer。Sun的遠端程序呼叫RPC就是使用XDR。
XDR語言跟C語言很是接近。
下面左邊是XDR語言定義的一個二維座標的結構體,右邊是左邊結構體編譯後生成的C語言結構體:
struct coord{ struct coord{ int x; --> int x; int y; int y; }; }; typedef struct coord coord;
XDR的聯合類型跟C語言的聯合類型很不同,它更像Pascal的records變量類型。
union read_result switch (int errno) { case 0: opaque data[1024]; default: void; };
上面的聯合類型編譯後生成的C語言以下:
struct read_result { int errno; union { char data[1024]; } read_result_u; };
下面左邊是XDR語言定義的一個顏色枚舉類型,右邊是左邊枚舉類型編譯後生成的C語言枚舉類型:
enum colortype { enum colortype { RED = 0, RED = 0, GREEN = 1, --> GREEN = 1, BLUE = 2, BLUE = 2, }; }; typedef enum colortype colortype;
下面左邊是XDR語言定義的typedef,右邊是左邊的typedef編譯後生成的C語言typedef:
typedef string fname_type<255>; --> typedef char* fname_type;
const DOZEN = 12; --> #define DOZEN 12
XDR裏面的常量都是整型常量,下面定義了一個常量DOZEN,他的值爲整形12
Program定義的其實就是RPC的協議
program TIMEPROG { version TIMEVERS { unsigned int TIMEGET(void) = 1; void TIMESET(unsigned) = 2; } = 1; } = 44;
上面的代碼編譯後會在頭文件裏面生成以下定義:
#define TIMEPROG 44 #define TIMEVERS 1 #define TIMEGET 1 #define TIMESET 2
XDR語言裏面只有四種聲明類型:普通類型聲明,固定數組聲明,可變數組聲明,指針聲明。
colortype color; --> colortype color;
colortype palette[8]; --> colortype palette[8];
可變數組聲明在C語言中,沒有相似的語法,因此XDR自創了這種聲明。它使用尖括號來聲明數組,數組裏面的數字表明這個數組的最大size,尖括號裏面也能夠沒有數字,表明這個數組能夠是任意大小:
int heights<12>; /*at most 12 items*/ int widths<>; /*any number of items*/
由於可變數組在C語言裏面沒有相似的語法,因此這種XDR聲明在編譯成C語言時,會被轉換爲結構體類型(struct),好比說上面對heights的可變數組聲明,編譯後生成的C代碼以下:
struct { u_int heights_len; /*number of items in array*/ int *heights_val; /*pointer array*/ }heights;
數組的個數存放在heights_len字段裏面,數組的指針存放在heights_val字段裏面。
XDR的指針語法跟C語言指針的語法基本上是同樣的,但,這並非說你能夠在網絡上傳輸指針類型數據。你可使用XDR裏面的指針類型傳輸列表或者樹類型的數據。
listitem *next; --> listitem *next;
C沒有內置的布爾類型,可是rpc庫裏面定義了布爾類型,類型名是bool_t,值爲TRUE或者FALSE。使用XDR bool類型定義的數據,編譯後會被轉換成bool_t類型:
bool married; --> bool_t married;
C沒有內置的string類型,只有‘\0’結尾的char*類型。XDR語言裏面使用關鍵字string來定義字符串類型數據,編譯後會轉換爲char*類型的數據。尖括號裏面的數字代碼字符串的最大長度(不包括’\0’結束符),尖括號裏面若是沒有數字,表明能夠是任意長度的字符串:
string name<32>; --> char *name; string longname<>; --> char *longname;
Opaque data在XDR和RPC語言裏面用來定義沒有類型的數據,表明任意類型的序列,它能夠被定義成固定的或者非固定的數組:
opaque diskblock[512]; --> char diskblock[512]; opaque filedata<1024>; --> struct { u_int filedata_len; char *filedata_val; }filedata;
Void 類型在XDR語言裏面只會出如今兩個地方:union和program的定義裏面。
在program定義裏面表明沒有返回值或者沒有參數。
文件名 | 做用 |
Makefile.file | 該文件用於編譯全部客戶機,服務器代碼 |
File_clnt.c | 該文件包含client_stub,程序員通常不用修改 |
File_svc.c | 該文件包含server_stub,程序員通常不用修改 |
File.h | 該文件包含了從說明中產生的全部XDR類型 |
File_xdr.c | 該文件包含了客戶機和服務器stub所需的XDR過濾器,程序員通常不用修改 |
File_server.c | 若是生成此文件,則該文件包含遠程服務的stub |
File_client.c | 若是生成此文件,則該文件包含了骨架客戶機程序 |
選項 | 做用 |
-a | 生成全部源程序,包括客戶機和服務器源程序 |
-C | 使用ANSI C標準生成編碼 |
-c | 生成xdr轉碼C程序。(file_xdr.c) |
-l | 生成客戶機stubs。(file_clnt.c) |
-m | 生成服務器stubs,可是不生成main函數。(file_svc.c) |
-s | rpcgen –C –s tcp file.x,生成服務器stubs,用tcp協議,同時生成了main函數。(file_svc.c) |
-h | 生成頭文件 |
-Sc | 生成骨架客戶機程序,(file_client.c),生成後還須要手動添加代碼 |
-Ss | 生成服務器程序,(file_server.c),生成後還須要手動添加代碼 |
實例:
Rpcgen -C file.x 生成file_xdr.c, file.h, Makefile.file, file_svc.c 和 file_clnt.c
Rpcgen -C -a file.x 比上面多生成了2個文件,file_server.c 和 file_client.c
通常而言在開發RPC時,咱們一般分爲三個步驟:
1:定義說明客戶/服務器的通訊協議。這裏所說的通訊協議是指定義服務過程的名稱、調用參數的數據類型和返回參數的數據類型,還包括底層傳輸類型(能夠是UDP或TCP),固然也能夠由RPC底層函數自動選擇鏈接類型創建TI-RPC。最簡單的協議生成的方法是採用協議編譯工具,經常使用的有rpcgen。
2:開發客戶端程序。
3:開發服務器端程序。
下面是一個rpc實例,咱們使用rpcgen 工具來生成遠程程序接口模塊,它將以RPC語言書寫的源代碼進行編譯,rpc語言在結構和語法上同C語言類似。由rpcgen 編譯生成的C源程序能夠直接用C編譯器進行編譯,rpcgen的源程序以.x結尾。
rpcgen 源程序 time.x:
program TIMEPROG { version PRINTTIMEVERS { string PRINTTIME(string) = 1; /* 過程號 */ } = 1; /* 版本號 */ } = 99; /* 程序號 */
time_proc.c源程序:
/* time_proc.c: implementation of the remote procedure "printime" */ #include <stdio.h> #include <time.h> #include "time.h" /* Remote version of "printime" */ char ** printime_1_svc(char **msg,struct svc_req *req) { static char * result; static char tmp_char[100]; time_t rawtime; FILE *f = fopen("/tmp/rpc_result", "a+"); if (f ==NULL) { strcpy(tmp_char,"Error"); result = tmp_char;; return (&result); } fprintf(f, "%s", *msg); fclose(f); time(&rawtime); sprintf(tmp_char,"Current time is :%s",ctime(&rawtime)); result =tmp_char; return (&result); }
rtime.c源代碼
#include <stdio.h> #include "time.h" /* time.h generated by rpcgen */ int main(int argc, char **argv) { CLIENT *clnt; char *result; char *server; char *message; if (argc != 3) { fprintf(stderr, "usage: %s host message", argv[0]); return -1; } server = argv[1]; message = argv[2]; clnt = clnt_create(server, TIMEPROG, PRINTTIMEVERS, "TCP"); if (clnt == NULL) { clnt_pcreateerror(server); return -1; } result =*printime_1(&message, clnt); if (result== (char *)NULL) { clnt_perror(clnt, server); return -1; } if (strcmp(result,"Error")==0){ fprintf(stderr, "%s: could not get the time",argv[0]); return -1; } printf("From the Time Server : %s",result); clnt_destroy( clnt ); return 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 //客戶端編譯
$gcc time_proc.c time_svc.c -o time_server //服務器端編譯
編譯成功後便可在Server端運行time_server,當即將該服務綁定在rpc服務端口上提供服務。在客戶端運行./rtime hostname msg (msg 是一字符串,筆者用來測試時創建的),當即會返回hostname 端的時間。
------------------------------------------------------------------------
歡迎關注個人微信公衆號 ^_^