我一畢業進公司就接觸到了RPC,主要是使用前輩們搭建好的RPC框架以及封裝好的RPC函數進行業務開發,雖說使用RPC框架開發已經近半年了,但一直想知道如何從零開始搭建起這么一個好用的分布式通信系統框架,近日心血來潮,雖說沒人教怎么搭建,但自己在網上查閱了大量資料后,開始自己一手一腳從零搭建這么一個RPC框架,所以就有了以下這篇文章,以記錄我的搭建過程。
首先對RPC做一個簡要介紹。
RPC的全稱是Remote Procedure Call,它能夠在本地以函數調用的形式來實現網絡操作,讓程序員集中關注於業務邏輯,不用關心底層的數據通信。
網絡通信的應用程序大多數是使用顯式網絡編程(explicit network programming)的方式編寫的,比如我們所熟悉的socket編程。客戶端調用socket、connect、read和write,服務器則調用socket、bind、listen等函數。我們熟悉的大多數應用程序(Web瀏覽器、Web服務器、Telnet客戶、Telnet服務器等程序)就是以這種方式編寫的。
編寫分布式應用程序的另一種方法就是使用隱式網絡編程(implicit network programming)。遠程過程調用(RPC)提供了這么一個工具。使用隱式網絡編程的好處就是,程序員不需要把把大量精力放在網絡通信程序的編寫上,因為這一塊工作已經有RPC框架幫你實現了,所以程序員可以把更多精力放在業務邏輯的開發上去。
這里就不對RPC做進一步詳細的理論性解析,這篇文章主要講述RPC的實踐,我們將一步一步搭建起一個基於RPC的完整的分布式通信系統框架。本文分為兩個部分,第一部分講述如何利用rpcgen工具搭建起來RPC通用骨架,第二部分我們就使用該骨架進行進一步完善,增加相應的處理函數,把血肉補充完全,做一個簡單的分布式計算系統demo。
一、使用rpcgen工具生成RPC底層骨架
1.生成my.x文件,然后在該文件編寫以下程序
首先創建文件夾rpc(mkdir rpc),以后的所有文件都放在這個文件夾下。
創建my.x文件(my為文件名,.x為后綴):vi my.x
在my.x填入下面代碼:
#define MY_RPC_PROG_NUM 0x38000010 //程序號 struct my_io_data_s //定義消息結構 { int mtype; int len; char data[1024]; }; typedef struct my_io_data_s my_io_data_t; program MY_RPC_PROG { version MY_RPC_VERS1 { int MY_RPCC(my_io_data_t) = 1; /* 過程號 = 1 */ } = 1; /* Version number = 1 */ version MY_RPC_VERS2 { my_io_data_t MY_RPCC(my_io_data_t) = 1; /* 過稱號 = 1 */ } = 2; /* Version number = 2 */ } = MY_RPC_PROG_NUM; /* Program number */
這里我創建了兩個版本,version1和version2,版本的數量是可以自己定制的,如果你需要一個的話定義一個即可。因為我打算定義一個版本用於SET的操作,一個用於GET操作,所以定義了兩個版本。
上面使用了RPC語言,我對以上幾個特殊名詞做一下解釋。
每個RPC過程由程序號、版本號和過程號來唯一確定。
RPC版本號:程序號標志一組相關的遠程過程,程序號的是有范圍的,我們需要在范圍內填寫程序號。
程序號范圍
|
簡述
|
0x00000000 - 0x1FFFFFFF
|
由Sun公司定義,提供特定服務
|
0x20000000 - 0x3FFFFFFF
|
由程序員自己定義,提供本地服務或用於調試
|
0x40000000 - 0x5FFFFFFF
|
用於短時間使用的程序,例如回調程序
|
0x60000000 - 0xFFFFFFFF
|
保留程序號
|
這里我們使用的范圍當然是0x20000000 - 0x3FFFFFFF,我填的是0x38000010。
版本號:在version的大括號里我們定義兩個我們將要使用的RPC調用函數的類型,比如:
version 1:我們定義了int MY_RPCC(my_io_data_t),這表明我們以后PRC框架使用的RPC調用函數的函數類型那個將會是:int * my_rpcc_1(my_io_data_t *argp, CLIENT *clnt)
version 2:my_io_data_t MY_RPCC(my_io_data_t) 則會變成 my_io_data_t * my_rpcc_2(my_io_data_t *argp, CLIENT *clnt)
所以我們可以根據我們需要的類型模仿編寫即可。
2.使用rpcgen指令生成以下幾個文件
使用rpcgen my.x生成系列文件(可以加參數-C,表示使用ANSI C)

執行該指令后,文件夾下將出現以下幾個文件。
my.h:

/* * Please do not edit this file. * It was generated using rpcgen. */ #ifndef _MY_H_RPCGEN #define _MY_H_RPCGEN #include <rpc/rpc.h> #ifdef __cplusplus extern "C" { #endif struct my_io_data_s { int mtype; int len; char data[1024]; }; typedef struct my_io_data_s my_io_data_s; typedef my_io_data_s my_io_data_t; #define MY_RPC_PROG 666 #define MY_RPC_VERS1 1 #if defined(__STDC__) || defined(__cplusplus) #define MY_RPCC 1 extern int * my_rpcc_1(my_io_data_t *, CLIENT *); extern int * my_rpcc_1_svc(my_io_data_t *, struct svc_req *); extern int my_rpc_prog_1_freeresult (SVCXPRT *, xdrproc_t, caddr_t); #else /* K&R C */ #define MY_RPCC 1 extern int * my_rpcc_1(); extern int * my_rpcc_1_svc(); extern int my_rpc_prog_1_freeresult (); #endif /* K&R C */ #define MY_RPC_VERS2 2 #if defined(__STDC__) || defined(__cplusplus) extern my_io_data_t * my_rpcc_2(my_io_data_t *, CLIENT *); extern my_io_data_t * my_rpcc_2_svc(my_io_data_t *, struct svc_req *); extern int my_rpc_prog_2_freeresult (SVCXPRT *, xdrproc_t, caddr_t); #else /* K&R C */ extern my_io_data_t * my_rpcc_2(); extern my_io_data_t * my_rpcc_2_svc(); extern int my_rpc_prog_2_freeresult (); #endif /* K&R C */ /* the xdr functions */ #if defined(__STDC__) || defined(__cplusplus) extern bool_t xdr_my_io_data_s (XDR *, my_io_data_s*); extern bool_t xdr_my_io_data_t (XDR *, my_io_data_t*); #else /* K&R C */ extern bool_t xdr_my_io_data_s (); extern bool_t xdr_my_io_data_t (); #endif /* K&R C */ #ifdef __cplusplus } #endif #endif /* !_MY_H_RPCGEN */
my_clnt.c:

/* * Please do not edit this file. * It was generated using rpcgen. */ #include <memory.h> /* for memset */ #include "my.h" /* Default timeout can be changed using clnt_control() */ static struct timeval TIMEOUT = { 25, 0 }; int * my_rpcc_1(my_io_data_t *argp, CLIENT *clnt) { static int clnt_res; memset((char *)&clnt_res, 0, sizeof(clnt_res)); if (clnt_call (clnt, MY_RPCC, (xdrproc_t) xdr_my_io_data_t, (caddr_t) argp, (xdrproc_t) xdr_int, (caddr_t) &clnt_res, TIMEOUT) != RPC_SUCCESS) { return (NULL); } return (&clnt_res); } my_io_data_t * my_rpcc_2(my_io_data_t *argp, CLIENT *clnt) { static my_io_data_t clnt_res; memset((char *)&clnt_res, 0, sizeof(clnt_res)); if (clnt_call (clnt, MY_RPCC, (xdrproc_t) xdr_my_io_data_t, (caddr_t) argp, (xdrproc_t) xdr_my_io_data_t, (caddr_t) &clnt_res, TIMEOUT) != RPC_SUCCESS) { return (NULL); } return (&clnt_res); }
my_svc.c

/* * Please do not edit this file. * It was generated using rpcgen. */ #include "my.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 my_rpc_prog_1(struct svc_req *rqstp, register SVCXPRT *transp) { union { my_io_data_t my_rpcc_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 MY_RPCC: _xdr_argument = (xdrproc_t) xdr_my_io_data_t; _xdr_result = (xdrproc_t) xdr_int; local = (char *(*)(char *, struct svc_req *)) my_rpcc_1_svc; 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; } static void my_rpc_prog_2(struct svc_req *rqstp, register SVCXPRT *transp) { union { my_io_data_t my_rpcc_2_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 MY_RPCC: _xdr_argument = (xdrproc_t) xdr_my_io_data_t; _xdr_result = (xdrproc_t) xdr_my_io_data_t; local = (char *(*)(char *, struct svc_req *)) my_rpcc_2_svc; 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 (MY_RPC_PROG, MY_RPC_VERS1); pmap_unset (MY_RPC_PROG, MY_RPC_VERS2); transp = svcudp_create(RPC_ANYSOCK); if (transp == NULL) { fprintf (stderr, "%s", "cannot create udp service."); exit(1); } if (!svc_register(transp, MY_RPC_PROG, MY_RPC_VERS1, my_rpc_prog_1, IPPROTO_UDP)) { fprintf (stderr, "%s", "unable to register (MY_RPC_PROG, MY_RPC_VERS1, udp)."); exit(1); } if (!svc_register(transp, MY_RPC_PROG, MY_RPC_VERS2, my_rpc_prog_2, IPPROTO_UDP)) { fprintf (stderr, "%s", "unable to register (MY_RPC_PROG, MY_RPC_VERS2, 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, MY_RPC_PROG, MY_RPC_VERS1, my_rpc_prog_1, IPPROTO_TCP)) { fprintf (stderr, "%s", "unable to register (MY_RPC_PROG, MY_RPC_VERS1, tcp)."); exit(1); } if (!svc_register(transp, MY_RPC_PROG, MY_RPC_VERS2, my_rpc_prog_2, IPPROTO_TCP)) { fprintf (stderr, "%s", "unable to register (MY_RPC_PROG, MY_RPC_VERS2, tcp)."); exit(1); } svc_run (); fprintf (stderr, "%s", "svc_run returned"); exit (1); /* NOTREACHED */ }
my_xdr.c

/* * Please do not edit this file. * It was generated using rpcgen. */ #include "my.h" bool_t xdr_my_io_data_s (XDR *xdrs, my_io_data_s *objp) { register int32_t *buf; int i; if (!xdr_int (xdrs, &objp->mtype)) return FALSE; if (!xdr_int (xdrs, &objp->len)) return FALSE; if (!xdr_vector (xdrs, (char *)objp->data, 1024, sizeof (char), (xdrproc_t) xdr_char)) return FALSE; return TRUE; } bool_t xdr_my_io_data_t (XDR *xdrs, my_io_data_t *objp) { register int32_t *buf; if (!xdr_my_io_data_s (xdrs, objp)) return FALSE; return TRUE; }
3.rpcgen -Sc -o my_client.c my.x 生成my_client.c
使用該指令后我們就生成了客戶端.c文件,這個文件很重要,因為以后我們做業務開發就在這里做,我們的調用都會從這里開始。
my_client.c:

/* * This is sample code generated by rpcgen. * These are only templates and you can use them * as a guideline for developing your own functions. */ #include "my.h" void my_rpc_prog_1(char *host) { CLIENT *clnt; int *result_1; my_io_data_t my_rpcc_1_arg; #ifndef DEBUG clnt = clnt_create (host, MY_RPC_PROG, MY_RPC_VERS1, "udp"); if (clnt == NULL) { clnt_pcreateerror (host); exit (1); } #endif /* DEBUG */ result_1 = my_rpcc_1(&my_rpcc_1_arg, clnt); if (result_1 == (int *) NULL) { clnt_perror (clnt, "call failed"); } #ifndef DEBUG clnt_destroy (clnt); #endif /* DEBUG */ } void my_rpc_prog_2(char *host) { CLIENT *clnt; my_io_data_t *result_1; my_io_data_t my_rpcc_2_arg; #ifndef DEBUG clnt = clnt_create (host, MY_RPC_PROG, MY_RPC_VERS2, "udp"); if (clnt == NULL) { clnt_pcreateerror (host); exit (1); } #endif /* DEBUG */ result_1 = my_rpcc_2(&my_rpcc_2_arg, clnt); if (result_1 == (my_io_data_t *) NULL) { clnt_perror (clnt, "call failed"); } #ifndef DEBUG clnt_destroy (clnt); #endif /* DEBUG */ } int main (int argc, char *argv[]) { char *host; if (argc < 2) { printf ("usage: %s server_host\n", argv[0]); exit (1); } host = argv[1]; my_rpc_prog_1 (host); my_rpc_prog_2 (host); exit (0); }
現在我們就可以在該文件編寫客戶端的代碼了。
5.rpcgen -Ss -o my_server.c my.x生成文件my_server.c
使用該指令后我們就生成了服務器.c文件,這個文件很重要,因為以后我們做業務開發就在這里做,我們將在這里編寫處理客戶端請求的代碼。
my_server.c:

/* * This is sample code generated by rpcgen. * These are only templates and you can use them * as a guideline for developing your own functions. */ #include "my.h" int * my_rpcc_1_svc(my_io_data_t *argp, struct svc_req *rqstp) { static int result; /* * insert server code here */ return &result; } my_io_data_t * my_rpcc_2_svc(my_io_data_t *argp, struct svc_req *rqstp) { static my_io_data_t result; /* * insert server code here */ return &result; }
6.在my_server.c和my_client.c添加測試代碼
所有利用rpcgen生成的文件都已經生成完畢,接下來我們需要添加測試代碼來驗證該RPC骨架是否正常工作。
my_client.c:

/* * This is sample code generated by rpcgen. * These are only templates and you can use them * as a guideline for developing your own functions. */ #include "my.h" void my_rpc_prog_1(char *host) { CLIENT *clnt; int *result_1; my_io_data_t my_rpcc_1_arg; #ifndef DEBUG clnt = clnt_create (host, MY_RPC_PROG, MY_RPC_VERS1, "udp"); if (clnt == NULL) { clnt_pcreateerror (host); exit (1); } #endif /* DEBUG */ result_1 = my_rpcc_1(&my_rpcc_1_arg, clnt); if (result_1 == (int *) NULL) { clnt_perror (clnt, "call failed"); } #ifndef DEBUG clnt_destroy (clnt); #endif /* DEBUG */ } void my_rpc_prog_2(char *host) { CLIENT *clnt; my_io_data_t *result_1; my_io_data_t my_rpcc_2_arg; #ifndef DEBUG clnt = clnt_create (host, MY_RPC_PROG, MY_RPC_VERS2, "udp"); if (clnt == NULL) { clnt_pcreateerror (host); exit (1); } #endif /* DEBUG */ my_rpcc_2_arg.mtype = 3; my_rpcc_2_arg.len = 18; result_1 = my_rpcc_2(&my_rpcc_2_arg, clnt); if (result_1 == (my_io_data_t *) NULL) { clnt_perror (clnt, "call failed"); } fprintf(stderr,"recv msg from server! mtype:%d len:%d \n",result_1->mtype,result_1->len); #ifndef DEBUG clnt_destroy (clnt); #endif /* DEBUG */ } int main (int argc, char *argv[]) { char *host; if (argc < 2) { printf ("usage: %s server_host\n", argv[0]); exit (1); } host = argv[1]; //my_rpc_prog_1 (host); my_rpc_prog_2 (host); exit (0); }
值得注意的是,我們client使用的是UDP協議,當然我們用戶也可以根據自己需要選用TCP協議進行開發。
my_server.c

/* * This is sample code generated by rpcgen. * These are only templates and you can use them * as a guideline for developing your own functions. */ #include "my.h" int * my_rpcc_1_svc(my_io_data_t *argp, struct svc_req *rqstp) { static int result; /* * insert server code here */ return &result; } my_io_data_t * my_rpcc_2_svc(my_io_data_t *argp, struct svc_req *rqstp) { static my_io_data_t result; /* * insert server code here */ printf("recv msg from client! len:%d, mt:%d \n",argp->len,argp->mtype); result.mtype = 33; result.len = 12; return &result; }
7.測試現象
編譯這client和server
gcc -o client my_clnt.c my_client.c my_xdr.c
gcc -o server my_svc.c my_server.c my_xdr.c
我在主機172.0.5.183運行server程序,在172.0.5.183運行client程序,測試現象如下:
server端

client端
以上測試已經證明了我們創建的RPC是可以正常通信的,那我們繼續在此框架下完善代碼,構建出可供業務開發的分布式系統的系統框架。
二、完善分布式系統的系統框架
以上的通信骨架過於簡單了,那我們就動動手加點代碼,設計一個簡單的分布式計算系統。這個所謂的分布式計算系統很簡單,就是我們一個客戶端向一個服務器端提出計算請求,服務器端將計算得出的結果返回給客戶端。
本次程序是基於本文第一部分的骨架搭建的,大多數文件不需要修改,需要修改的文件如下:
my_client、my_server.c、my_clnt.c、my.h
my_client、my_server.c、my_clnt.c、my.h
新增加一個文件:rpc_msg.h
首先我們重新定義一下客戶端和服務器端通信消息的格式。
首先我們重新定義一下客戶端和服務器端通信消息的格式。
typedef struct my_msg_hdr_s { int mtype; int len; }my_msg_hdr_t; typedef struct my_msg_s { my_msg_hdr_t msg_hdr; int para1; int para2; int result; }my_msg_t;
然后我們先改變一下我們的客戶端程序,
my_rpc_prog_1主要負責服務器的設置,比如在該程序里,此函數就用於設置服務器的開關,所以這個函數只需要把消息發給服務器端就可以了,不需要服務器返回數據給它,所以使用了int的返回值。
而my_rpc_prog_2則需要給它返回數據結果的,所以該函數使用了my_io_data_t *作返回值。
my_client.c
/* * This is sample code generated by rpcgen. * These are only templates and you can use them * as a guideline for developing your own functions. */ #include "my.h" #include "rpc_msg.h" int my_rpc_prog_1(char *host, my_io_data_t* in_msg) { CLIENT *clnt; int *result_1; #ifndef DEBUG clnt = clnt_create (host, MY_RPC_PROG, MY_RPC_VERS1, "udp"); if (clnt == NULL) { printf("Fail to create rpc client1!\n"); return -1; } #endif /* DEBUG */ result_1 = my_rpcc_1(in_msg, clnt); if (result_1 == (int *) NULL) { clnt_perror (clnt, "call failed"); return -1; } return 0; } my_io_data_t * my_rpc_prog_2(char *host, my_io_data_t* in_msg) { CLIENT *clnt; my_io_data_t *result_1 = NULL; #ifndef DEBUG clnt = clnt_create (host, MY_RPC_PROG, MY_RPC_VERS2, "udp"); if (clnt == NULL) { printf("Fail to create rpc client1!\n"); return NULL; } #endif /* DEBUG */ result_1 = my_rpcc_2(in_msg, clnt); if (result_1 == (my_io_data_t *) NULL) { clnt_perror (clnt, "call failed"); return NULL; } return result_1; } void get_compute_result(char *host, int type, int para1, int para2) { my_io_data_t in_msg; my_msg_t* rsp; my_io_data_t* out_msg; my_msg_t* req = (my_msg_t*)&in_msg; memset(&in_msg, 0, sizeof(in_msg)); req->msg_hdr.mtype = type; req->msg_hdr.len = sizeof(in_msg) - sizeof(my_msg_hdr_t); req->para1 = para1; req->para2 = para2; out_msg = my_rpc_prog_2(host, &in_msg); rsp = (my_msg_t*)out_msg; if(rsp == NULL) { printf("RPC call fail!\n"); return; } printf("compute result is %d\n",rsp->result); } void server_switch(char *host, int type) { my_io_data_t msg; my_msg_t* in_msg = (my_msg_t*)&msg; memset(&msg, 0, sizeof(msg)); in_msg->msg_hdr.mtype = type; in_msg->msg_hdr.len = sizeof(msg) - sizeof(my_msg_hdr_t); if(my_rpc_prog_1(host, &msg)) { printf("enable server fail!\n"); } printf("Configure server successfully!\n"); } int main (int argc, char *argv[]) { server_switch(SERVER_IP, RPC_enable); //server start sleep(1); get_compute_result(SERVER_IP, RPC_ADD, 6, 3); sleep(1); get_compute_result(SERVER_IP, RPC_SUB, 6, 3); sleep(1); get_compute_result(SERVER_IP, RPC_MUL, 6, 3); sleep(1); get_compute_result(SERVER_IP, RPC_DIV, 6, 3); sleep(1); server_switch(SERVER_IP, RPC_disable); //server close return 0; }
當然改了以上的兩個函數,我們當然也需要在my_clnt.c和my.h作出函數聲明的修改。
現在看看server端怎么處理這些請求的。
my_server.c
/* * This is sample code generated by rpcgen. * These are only templates and you can use them * as a guideline for developing your own functions. */ #include "my.h" #include "rpc_msg.h" int* my_rpcc_1_svc(my_io_data_t *argp, struct svc_req *rqstp) { static int result; switch(argp->mtype) { case RPC_enable: printf("server start!\n"); break; case RPC_disable: printf("server close!\n"); break; default: break; } return &result; } my_io_data_t* my_rpcc_2_svc(my_io_data_t *argp, struct svc_req *rqstp) { static my_io_data_t result; my_msg_t* out = (my_msg_t*)&result; my_msg_t* in = (my_msg_t*)argp; switch(in->msg_hdr.mtype) { case RPC_ADD: out->result = in->para1 + in->para2; break; case RPC_SUB: out->result = in->para1 - in->para2; break; case RPC_MUL: out->result = in->para1 * in->para2; break; case RPC_DIV: out->result = in->para1/in->para2; break; default: break; } return &result; }
附上有修改的文件
rpc_msg.h

#define SERVER_IP "172.0.5.183" enum RPC_REQ_TYPE_1 { RPC_enable, RPC_disable, }; enum RPC_REQ_TYPE_2 { RPC_ADD, RPC_SUB, RPC_MUL, RPC_DIV, }; typedef struct my_msg_hdr_s { int mtype; int len; }my_msg_hdr_t; typedef struct my_msg_s { my_msg_hdr_t msg_hdr; int para1; int para2; int result; }my_msg_t;
my_clnt.c:

/* * Please do not edit this file. * It was generated using rpcgen. */ #include <memory.h> /* for memset */ #include "my.h" /* Default timeout can be changed using clnt_control() */ static struct timeval TIMEOUT = { 25, 0 }; int * my_rpcc_1(my_io_data_t *argp, CLIENT *clnt) { static int clnt_res; memset((char *)&clnt_res, 0, sizeof(clnt_res)); if (clnt_call (clnt, MY_RPCC, (xdrproc_t) xdr_my_io_data_t, (caddr_t) argp, (xdrproc_t) xdr_int, (caddr_t) &clnt_res, TIMEOUT) != RPC_SUCCESS) { return (NULL); } return (&clnt_res); } my_io_data_t * my_rpcc_2(my_io_data_t *argp, CLIENT *clnt) { static my_io_data_t clnt_res; memset((char *)&clnt_res, 0, sizeof(clnt_res)); if (clnt_call (clnt, MY_RPCC, (xdrproc_t) xdr_my_io_data_t, (caddr_t) argp, (xdrproc_t) xdr_my_io_data_t, (caddr_t) &clnt_res, TIMEOUT) != RPC_SUCCESS) { return (NULL); } return (&clnt_res); }
my.h

/* * Please do not edit this file. * It was generated using rpcgen. */ #ifndef _MY_H_RPCGEN #define _MY_H_RPCGEN #include <rpc/rpc.h> #ifdef __cplusplus extern "C" { #endif struct my_io_data_s { int mtype; int len; char data[1024]; }; typedef struct my_io_data_s my_io_data_s; typedef my_io_data_s my_io_data_t; #define MY_RPC_PROG 666 #define MY_RPC_VERS1 1 #if defined(__STDC__) || defined(__cplusplus) #define MY_RPCC 1 extern int * my_rpcc_1(my_io_data_t *, CLIENT *); extern int * my_rpcc_1_svc(my_io_data_t *, struct svc_req *); extern int my_rpc_prog_1_freeresult (SVCXPRT *, xdrproc_t, caddr_t); #else /* K&R C */ #define MY_RPCC 1 extern int * my_rpcc_1(); extern int * my_rpcc_1_svc(); extern int my_rpc_prog_1_freeresult (); #endif /* K&R C */ #define MY_RPC_VERS2 2 #if defined(__STDC__) || defined(__cplusplus) extern my_io_data_t * my_rpcc_2(my_io_data_t *, CLIENT *); extern my_io_data_t * my_rpcc_2_svc(my_io_data_t *, struct svc_req *); extern int my_rpc_prog_2_freeresult (SVCXPRT *, xdrproc_t, caddr_t); #else /* K&R C */ extern my_io_data_t * my_rpcc_2(); extern my_io_data_t * my_rpcc_2_svc(); extern int my_rpc_prog_2_freeresult (); #endif /* K&R C */ /* the xdr functions */ #if defined(__STDC__) || defined(__cplusplus) extern bool_t xdr_my_io_data_s (XDR *, my_io_data_s*); extern bool_t xdr_my_io_data_t (XDR *, my_io_data_t*); #else /* K&R C */ extern bool_t xdr_my_io_data_s (); extern bool_t xdr_my_io_data_t (); #endif /* K&R C */ #ifdef __cplusplus } #endif #endif /* !_MY_H_RPCGEN */
實驗結果:
編譯這client和server
gcc -o client my_clnt.c my_client.c my_xdr.c
gcc -o server my_svc.c my_server.c my_xdr.c
首先在主機172.0.5.183上運行server程序,然后在主機172.0.5.182運行client程序
同時觀察兩個主機的輸出情況:

實驗證明,client的RPC調用是成功的,計算結果跟我們預計的也一樣,我們的demo設計完成。
后記:
現在網上介紹RPC的文章很多,但教你如何在Linux下搭建RPC框架的技術文章是很少的,僅有的幾篇文章介紹得也是很簡單很模糊,我也因此走了很多彎路,自己探索很多花了很多時間才搭建起來這么一個RPC框架。希望本文幫助到那些想在Linux下搭建RPC框架卻不得入門的朋友。
參考資料:
《Unix網絡編程卷二》
《Linux C高級程序員指南》
《rpcgen_mannual》