一、概述
在傳統的編程概念中,過程是由程序員在本地編譯完成,并只能局限在本地運行的一段代碼,也即其主程序和過程之間的運行關系是本地調用關系。因此這種結構在網絡日益發展的今天已無法適應實際需求。總而言之,傳統過程調用模式無法充分利用網絡上其他主機的資源(如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應用服務而設計的,他提供了如下功能函數。 |
|
2、高層例程 |
在這一層,程序需要在發出調用請求前先創建一個客戶端句柄,或是在偵聽請求前先建立一個服務器端句柄。程序在該層可以自由的將自己的應用綁在所有的傳輸端口上,它提供了如下功能函數。 |
|
3、中間層例程 |
中間層向程序提供更為詳細的RPC控制接口,而這一層的代碼變得更為復雜,但運行也更為有效,它提供了如下功能函數。 |
|
4、專家層例程 |
這層提供了更多的一系列與傳輸相關的功能調用,它提供了如下功能函數。 |
|
5、底層例程 |
該層提供了所有對傳輸選項進行控制的調用接口,它提供了如下功能函數。 |
函數名 | 功能描述 |
Clnt_dg_create( ) | 采用無連接方式向遠程過程在客戶端建立客戶句柄 |
Svc_dg_create( ) | 采用無連接方式建立服務句柄 |
Clnt_vc_create( ) | 采用面向連接的方式建立客戶句柄 |
Svc_vc_create( ) | 采用面向連接的方式建立?RPC?服務句柄 |
Clnt_call( ) | 客戶端向服務器端發送調用請求 |
?
1.????????根據rpc調用的功能,先不考慮rpc調用,編寫一個平常的實現相應功能的程序。
如一個遠程的文件傳輸的rpc調用,平常程序便是考慮文件存儲在本地,直接打開讀便可,如下:
#include <stdio.h>
#include <stdlib.h>
?
#define MAXNAME 20
#define MAXLENGTH 1024
char * readfile(char *);
?
int main()
{
?? ?char name[MAXNAME];
?
?? ?printf("Enter File Name: ");
?? ?scanf("%s", name);
?? ?printf("%s", readfile(name));
}
?
char * readfile(char * name)
{
?? ?FILE *file = fopen(name, "r");
?? ?char * buf = (char *)malloc(sizeof(char)*MAXLENGTH);
?? ?if (file == NULL)
?? ?{
?? ? ? ?printf("File Cann't Be Open!");
?? ? ? ?return 0;
?? ?}
?? ?printf("The File Content is : /n");
?? ?while (fgets(buf, MAXLENGTH-1, file) != NULL)
?? ?{
?? ? ? ?return buf;
?? ?}
?? ?return NULL;
}
2.????????把程序拆分為兩部分,main函數和readfile函數,帶有main的一部分是主動發起調用的,在rpc中相當于客戶端,帶有readfile函數的部分是提供相應的功能的,相當于服務器端。將代碼拆分后要在客戶端添加相應的rpc調用函數,clnt_create(RMACHINE, FILETRANSPROG, FILETRANSVERS,"tcp");
FILETRANSPROG便是trans.x中的程序名,FILETRANSVERS是版本名,使用tcp進行rpc調用。
?
拆分后代碼如下:
客戶端client.c:
#include <rpc/rpc.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <unistd.h>
#include "trans.h"
?
#define WSVERS MAKEWORD(0, 2)
#define RMACHINE "localhost"
CLIENT *handle;
?
#define MAXNAME 20
#define MAXLENGTH 1024
?
char * readfile(char *);
?
int main()
{
????char name[MAXNAME];
????char * buf;
????printf("Enter File Name: ");
????scanf("%s", name);
???handle = clnt_create(RMACHINE, FILETRANSPROG, FILETRANSVERS,"tcp");
???if (handle == 0) {
?????????printf("Could Not Connect To Remote Server./n");
?????????exit(1);
???}
????buf = readfile(name);
????printf("%s", buf);
????return 0;
}
服務器端server.c:
#include <rpc/rpc.h>
#include <stdio.h>
#include <stdlib.h>
#include "trans.h"
?
#define MAXNAME 20
#define MAXLENGTH 1024
?
char * readfile(char * name)
{
????FILE *file = fopen(name, "r");
????char * buf = (char *)malloc(sizeof(char)*MAXLENGTH);
????if (file == NULL)
????{
????????printf("File Cann't Be Open!");
????????return 0;
????}
????printf("The File Content is : /n");
????while (fgets(buf, MAXLENGTH-1, file) != NULL)
????{
????????return buf;
????}
????return NULL;
}
?
3.????????編寫***.x文件。具體步驟可以參考Douglas的那本Internetworking With TCP/IP?的第三卷,客戶端-服務器端編程與應用。
本程序的.x文件命名為trans.x內容如下:
const MAXLENGTH = 1024;
const MAXNAME = 20;
?
program FILETRANSPROG??//程序名
{?
????version FILETRANSVERS??//版本名
????{?
????????string READFILE(string) = 1;??//調用的方法名
????} = 1;?
} = 99;
?
4.????????使用rpcgen編輯.x文件,在linux下輸入命令
rpcgen???trans.x
若格式正確,編譯無錯誤則產生三個文件trans.h,trans_svc.c(服務器端),trans_clnt.c(客戶端)。因為上述trans.x中無自定義數據結構,所以沒有xdr文件產生。
trans.h代碼:
/*
?* Please do not edit this file.
?* It was generated using rpcgen.
?*/
?
#ifndef _TRANS_H_RPCGEN
#define _TRANS_H_RPCGEN
?
#include <rpc/rpc.h>
?
?
#ifdef __cplusplus
extern "C" {
#endif
?
#define MAXLENGTH 1024
#define MAXNAME 20
?
#define FILETRANSPROG 99
#define FILETRANSVERS 1
?
#if defined(__STDC__) || defined(__cplusplus)
#define READFILE 1
extern??char ** readfile_1(char **, CLIENT *);
extern??char ** readfile_1_svc(char **, struct svc_req *);
extern int filetransprog_1_freeresult (SVCXPRT *, xdrproc_t, caddr_t);
?
#else /* K&R C */
#define READFILE 1
extern??char ** readfile_1();
extern??char ** readfile_1_svc();
extern int filetransprog_1_freeresult ();
#endif /* K&R C */
?
#ifdef __cplusplus
}
#endif
?
#endif /* !_TRANS_H_RPCGEN */
?
trans_svc.c代碼:
/*
?* Please do not edit this file.
?* It was generated using rpcgen.
?*/
?
#include "trans.h"
#include <stdio.h>
#include <stdlib.h>
#include <rpc/pmap_clnt.h>
#include <string.h>
#include <memory.h>
#include <sys/socket.h>
#include <netinet/in.h>
?
#ifndef SIG_PF
#define SIG_PF void(*)(int)
#endif
?
static void
filetransprog_1(struct svc_req *rqstp, register SVCXPRT *transp)
{
???union {
????????????char *readfile_1_arg;
???} argument;
???char *result;
???xdrproc_t _xdr_argument, _xdr_result;
???char *(*local)(char *, struct svc_req *);
?
???switch (rqstp->rq_proc) {
???case NULLPROC:
????????????(void) svc_sendreply (transp, (xdrproc_t) xdr_void, (char *)NULL);
????????????return;
?
???case READFILE:
????????????_xdr_argument = (xdrproc_t) xdr_wrapstring;
????????????_xdr_result = (xdrproc_t) xdr_wrapstring;
????????????local = (char *(*)(char *, struct svc_req *)) readfile_1;
????????????break;
?
???default:
????????????svcerr_noproc (transp);
????????????return;
???}
???memset ((char *)&argument, 0, sizeof (argument));
???if (!svc_getargs (transp, (xdrproc_t) _xdr_argument, (caddr_t) &argument)) {
????????????svcerr_decode (transp);
????????????return;
???}
???result = (*local)((char *)&argument, rqstp);
???if (result != NULL && !svc_sendreply(transp, (xdrproc_t) _xdr_result, result)) {
????????????svcerr_systemerr (transp);
???}
???if (!svc_freeargs (transp, (xdrproc_t) _xdr_argument, (caddr_t) &argument)) {
????????????fprintf (stderr, "%s", "unable to free arguments");
????????????exit (1);
???}
???return;
}
?
int
main (int argc, char **argv)
{
???register SVCXPRT *transp;
?
???pmap_unset (FILETRANSPROG, FILETRANSVERS);
?
???transp = svcudp_create(RPC_ANYSOCK);
???if (transp == NULL) {
????????????fprintf (stderr, "%s", "cannot create udp service.");
????????????exit(1);
???}
???if (!svc_register(transp, FILETRANSPROG, FILETRANSVERS, filetransprog_1, IPPROTO_UDP)) {
????????????fprintf (stderr, "%s", "unable to register (FILETRANSPROG, FILETRANSVERS, udp).");
????????????exit(1);
???}
?
???transp = svctcp_create(RPC_ANYSOCK, 0, 0);
???if (transp == NULL) {
????????????fprintf (stderr, "%s", "cannot create tcp service.");
????????????exit(1);
???}
???if (!svc_register(transp, FILETRANSPROG, FILETRANSVERS, filetransprog_1, IPPROTO_TCP)) {
????????????fprintf (stderr, "%s", "unable to register (FILETRANSPROG, FILETRANSVERS, tcp).");
????????????exit(1);
???}
?
???svc_run ();
???fprintf (stderr, "%s", "svc_run returned");
???exit (1);
???/* NOTREACHED */
}
?
trans_clnt.c代碼:
/*
?* Please do not edit this file.
?* It was generated using rpcgen.
?*/
?
#include <memory.h> /* for memset */
#include "trans.h"
?
/* Default timeout can be changed using clnt_control() */
static struct timeval TIMEOUT = { 25, 0 };
?
char **
readfile_1(char **argp, CLIENT *clnt)
{
???static char *clnt_res;
?
???memset((char *)&clnt_res, 0, sizeof(clnt_res));
???if (clnt_call (clnt, READFILE,
????????????(xdrproc_t) xdr_wrapstring, (caddr_t) argp,
????????????(xdrproc_t) xdr_wrapstring, (caddr_t) &clnt_res,
????????????TIMEOUT) != RPC_SUCCESS) {
????????????return (NULL);
???}
???return (&clnt_res);
}
5.????????編寫客戶端和服務器端接口。此部分可以說是最麻煩的部分,稍不注意便會出錯,同樣可以參考Douglas的那本書,但要注意的是他的服務器接口例程代碼中的每個函數的第二個參數應該是CLIENT *clnt,而非struct svc_req * rqstp
為本程序編寫的代碼如下:
客戶端接口文件trans_cif.c:
#include <rpc/rpc.h>
#include <stdio.h>
?
#include "trans.h"/* Client-side stub interface routines written by programmer */
extern CLIENT * handle;
static char **ret;
?
char * readfile(char * name)
{
???char ** arg;
???arg = &name;
???ret = readfile_1(arg, handle);
?
???return ret==NULL ? NULL : *ret;
}
服務器端接口文件trans_sif.c:
#include <rpc/rpc.h>
#include <stdio.h>
#include "trans.h"
?
char * readfile(char *);
static char * retcode;
?
char ** readfile_1(char ** w, CLIENT *clnt)
{
???retcode = readfile(*(char**)w);
???return &retcode;
}
?
6.????????編譯鏈接客戶端和服務器端程序
不管是客戶端還是服務器端,都要鏈接三個文件,
客戶端:程序文件+*** _clnt.c+客戶端接口文件。
服務器端:程序文件+*** _svc.c+服務器端接口文件
同時每一段的三個文件都是互相關聯的,編譯出現錯誤時,可以根據提示查看三個文件進行debug
命令如下:
gcc -Wall -o trans_client client.c trans_clnt.c trans_cif.c
gcc -Wall -o trans_server server.c trans_svc.c trans_sif.c
?
7.????????啟動服務器端和客戶端,大功告成。要先運行服務器端程序,再運行客戶端程序。命令如下:
./trans_server
./trans_client
client啟動后,提示輸入要傳輸的文件名,輸入后,server將文件的第一行傳回,大功告成!
相關:
http://hi.baidu.com/%E2%C8%C2%F8%CD%B7%C9%E7%C7%F8/blog/item/6d50b86752957dfef63654cc.html