rpc簡介、原理、實例-緣於difx

簡介

RPC(Remote Procedure Call,遠程過程調用)是建立在Socket之上的,出於一種類比的願望,在一臺機器上運行的主程序,可以調用另一臺機器上準備好的子程序,就像LPC(本地過程調用).越底層,代碼越複雜、靈活性越高、效率越高;越上層,抽象封裝的越好、代碼越簡單、效率越差。Socket和RPC的區別再次說明了這點。在傳統的編程概念中,過程是由程序員在本地編譯完成,並只能侷限在本地運行的一段代碼,也即其主程序和過程之間的運行關係是本地調用關係。因此這種結構在網絡日益發展的今天已無法適應實際需求。衆所周知,傳統過程調用模式無法充分利用網絡上其他主機的資源(如CPU、Memory等),也無法提高代碼在實體間的共享程度,使得主機資源大量浪費。

      通過RPC我們可以充分利用非共享內存的多處理器環境(例如通過局域網連接得多臺工作站),這樣可以簡便地將你的應用分佈在多臺工作站上,應用程序就像運行在一個多處理器的計算機上一樣。你可以方便的實現過程代碼共享,提高系統資源的利用率,也可以將以大量數值處理的操作放在處理能力較強的系統上運行,從而減輕前端機的負擔。

    RPC作爲普遍的C/S開發方法,開發效率高效,可靠.但RPC方法的基本原則是--以模塊調用的簡單性忽略通訊的具體細節,以便程序員不用關心C/S之間的通訊協議,集中精力對付實現過程.這就決定了 RPC生成的通訊包不可能對每種應用都有最恰當的處理辦法,與Socket方法相比,傳輸相同的有效數據,RPC佔用更多的網絡帶寬.
  RPC是在Socket的基礎上實現的,它比socket需要更多的網絡和系統資源.另外,在對程序優化時,程序員雖然可以直接修改由rpcgen產生的令人費解的源程序,但對於追求程序設計高效率的RPC而言,獲得的簡單性則被大大削弱.

RPC的結構原理及其調用機制

如前所述RPC其實也是種C/S的編程模式,有點類似C/S Socket 編程模式,但要比它更高一層。當我們在建立RPC服務以後,客戶端的調用參數通過底層的RPC傳輸通道,可以是UDP,也可以是TCP(也即TI-RPC—無關性傳輸),並根據傳輸前所提供的目的地址及RPC上層應用程序號轉至相應的RPC應用程序服務端,且此時的客戶端處於等待狀態,直至收到應答或Time Out超時信號。當服務器端獲得請求消息,則會根據註冊RPC時告訴RPC系統的例程入口地址,執行相應的操作,並將結果返回至客戶端。

當一次RPC調用結束後,相應線程發送相應的信號,客戶端程序纔會繼續運行。

在這個過程中,一個遠程過程是有三個要素來唯一確定的:程序號、版本號和過程號。

程序號是用來區別一組相關的並且具有唯一過程好的遠程過程。一個程序可以有一個或幾個不同的版本,而每個版本的程序都包含一系列能被遠程調用的過程,通過版本的引入,使得不同版本下的RPC能同時提供服務。每個版本都包含有許多可供遠程調用的過程,每個過程則有其唯一標示的過程號。

基於RPC的應用系統開發

通過以上對RPC原理的簡介後,我們再來繼續討論如何來開發基於RPC的應用系統。

一般而言在開發RPC時,我們通常分爲三個步驟:

a、 定義說明客戶/服務器的通信協議:這裏所說的通信協議是指定義服務過程的名稱、調用參數的數據類型和返回參數的數據類型,還包括底層傳輸類型(可以是UDP或TCP),當然也可以由RPC底層函數自動選擇連接類型建立TI-RPC。最簡單的協議生成的方法是採用協議編譯工具,常用的有Rpcgen,我會在後面實例中詳細描述其使用方法。

b、  開發客戶端程序。

c、  開發服務器端程序。

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

簡單層例程

簡單層是面向普通RPC應用,爲了快速開發RPC應用服務而設計的,他提供瞭如下功能函數。

  函數名

             功能描述

Rpc_reg( )

在一特定類型的傳輸層上註冊某個過程,來作爲提供服務的RPC程序

Rpc_call( )

遠程調用在指定主機上指定的過程

Rpc_Broadcast( )

向指定類型的所有傳輸端口上廣播一個遠程過程調用請求

                                

高層例程

在這一層,程序需要在發出調用請求前先創建一個客戶端句柄,或是在偵聽請求前先建立一個服務器端句柄。程序在該層可以自由的將自己的應用綁在所有的傳輸端口上,它提供瞭如下功能函數。

          

  函數名

             功能描述

Clnt_create( )

程序通過這個功能調用,告訴底層RPC服務器的位置及其傳輸類型

Clnt_create_timed( )

定義每次嘗試連接的超時最大時間

Svc_create( )

在指定類型的傳輸端口上建立服務器句柄,告訴底層RPC事件過程的相應入口地址

Clnt_call()

向服務器端發出一個RPC調用請求

中間層例程

中間層向程序提供更爲詳細的RPC控制接口,而這一層的代碼變得更爲複雜,但運行也更爲有效,它提供瞭如下功能函數。

  函數名

             功能描述

Clnt_tp_create( )

在指定的傳輸端口上建立客戶端句柄

Clnt_tp_create_timed( )

定義最大傳輸時延

Svc_tp_creaet( )

在指定的傳輸端口上建立服務句柄

Clnt_call( )

向服務器端發出RPC調用請求

                

專家層例程

這層提供了更多的一系列與傳輸相關的功能調用,它提供瞭如下功能函數。

 

  函數名

             功能描述

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請求

底層例程

該層提供了所有對傳輸選項進行控制的調用接口,它提供瞭如下功能函數。

  函數名

             功能描述

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:

[plain] view plain copy
在CODE上查看代碼片派生到我的代碼片
  1. program TIMEPROG {  
  2.    version PRINTIMEVERS {  
  3.      string PRINTIME(string) = 1; /* 過程號 */  
  4.    } = 1; /* 版本號 */  
  5. } = 0x20000001; /* 程序號 */  


time_proc.c源程序:

  1. /* time_proc.c: implementation of the remote procedure "printime" */   
  2.   
  3. #include <stdio.h>   
  4. #include <rpc/rpc.h>           /* always needed */   
  5. #include "time.h"                  /* time.h will be generated by rpcgen */   
  6. #include <time.h>           
  7.   
  8. /* Remote version of "printime" */   
  9. char ** printime_1_svc(char **msg,struct svc_req *req)  
  10. {  
  11.    static char * result; /* must be static! */  
  12.    static char tmp_char[100];  
  13.    time_t rawtime;     
  14.   
  15.    FILE *f;   
  16.   
  17.    f = fopen("/tmp/rpc_result""a+");  
  18.    if (f == (FILE *)NULL) {  
  19.      strcpy(tmp_char,"Error");  
  20.      result = tmp_char;;  
  21.      return (&result);  
  22.     }  
  23.   
  24.    fprintf(f, "%s", *msg);                      //used for debugging   
  25.    fclose(f);  
  26.    time(&rawtime);  
  27.    sprintf(tmp_char,"Current time is :%s",ctime(&rawtime));  
  28.    result =tmp_char;  
  29.    return (&result);  
  30. }  


rtime.c源代碼

  1. /* 
  2. * rtime.c: remote version 
  3. * of "printime.c" 
  4. */  
  5.    
  6. #include <stdio.h>  
  7. #include "time.h" /* time.h generated by rpcgen */   
  8. main(int argc, char **argv)  
  9. {  
  10.   CLIENT *clnt;  
  11.   char *result;  
  12.   char *server;  
  13.   char *message;   
  14.   
  15.  if (argc != 3) {  
  16.      fprintf(stderr, "usage: %s host messagen", argv[0]);  
  17.      exit(1);  
  18.     }  
  19.   
  20.   server = argv[1];  
  21.   message = argv[2];  
  22.   
  23.   /* 
  24.    * Create client "handle" used for 
  25.    * calling TIMEPROG on the server 
  26.    * designated on the command line. 
  27.    */  
  28.   //clnt = clnt_create(server, TIMEPROG, PRINTIMEVERS, "visible");  
  29. clnt = clnt_create(server, TIMEPROG, PRINTIMEVERS, "TCP");   
  30.   
  31.   if (clnt == (CLIENT *)NULL) {  
  32.    /* 
  33.     * Couldn't establish connection 
  34.     * with server. 
  35.     * Print error message and die. 
  36.     */  
  37.    clnt_pcreateerror(server);  
  38.    exit(1);  
  39.    }  
  40.   
  41.   /* 
  42. * Call the remote procedure 
  43.    * "printime" on the server 
  44.    */  
  45.    result =*printime_1(&message,clnt);  
  46.    if (result== (char *)NULL) {  
  47.      /* 
  48.       * An error occurred while calling 
  49.       * the server. 
  50.       * Print error message and die. 
  51.       */   
  52.      clnt_perror(clnt, server);  
  53.      exit(1);  
  54.     }  
  55.   
  56.    /* Okay, we successfully called 
  57.     * the remote procedure. 
  58.     */  
  59.     if (strcmp(result,"Error")==0){  
  60.    /* 
  61.     * Server was unable to print 
  62.     * the time. 
  63.     * Print error message and die. 
  64.     */  
  65.   
  66.    
  67.     fprintf(stderr, "%s: could not get the timen",argv[0]);  
  68.     exit(1);  
  69.     }  
  70.   
  71.     printf("From the Time Server ...%s",result);    
  72.     clnt_destroy( clnt );  
  73.     exit(0);  
  74. }  

 

編譯方法

   有了以上的三段代碼後,就可用rpcgen 編譯工具進行RPC協議編譯,命令如下:

   $rpcgen time.x(rpcgen -C -a -M -N 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 端的時間。

出錯問題

Cannot register service

RPC: Authentication error; why = Client credential too weak unable to register (TIMEPROG, PRINTIMEVERS, udp)

解決方法:使用root賬戶即可(同時檢查portmap是否安裝了)

time_proc.c:7: error: conflicting types for printime_1?

源程序time_proc.c中報錯

解決方法,改爲char ** printime_1_svc(char **msg,struct svc_req *req)即可。

RPC: Unknown protocol

      這個原因可能是rpc依賴包沒有安裝,或者使用函數clnt_create的時候,最後一個參數應該不使用visible,而是tcp。


參考文檔:http://docs.freebsd.org/44doc/psd/22.rpcgen/paper.pdf

Linux.C 高級程序員指南

http://www.cs.rutgers.edu/~pxk/rutgers/notes/rpc/index.html

http://fossies.org/linux/misc/old/portmap-6.0.tgz/

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