一、RPC

在介绍RPC之前,我们有必要先介绍一下IPC

进程间通信(IPC,Inter-Process Communication),指至少两个进程或线程间传送数据或信号的一些技术或方法。进程是计算机系统分配资源的最小单位。每个进程都有自己的一部分独立的系统资源,彼此是隔离的。为了能使不同的进程互相访问资源并进行协调工作,才有了进程间通信。这些进程可以运行在同一计算机上或网络连接的不同计算机上。 进程间通信技术包括消息传递、同步、共享内存和远程过程调用。 IPC是一种标准的Unix通信机制。

有两种类型的进程间通信(IPC):

本地过程调用(LPC)LPC用在多任务操作系统中,使得同时运行的任务能互相会话。这些任务共享内存空间使任务同步和互相发送信息。
远程过程调用(RPC)RPC类似于LPC,只是在网上工作。RPC开始是出现在Sun微系统公司和HP公司的运行UNIX操作系统的计算机中。
为什么要用RPC呢?

我们知道,同一主机的同一程序之间因为有函数栈的存在,使得函数的相互调用很简单。但是两个程序(程序A和程序B)分别运行在不同的主机上,A想要调用B的函数来实现某一功能,那么使用常规的方法就是不可实现的,RPC就是干这个活的

RPC的核心并不在于使用什么协议。RPC的目的是让你在本地调用远程的方法,而对你来说这个调用是透明的,你并不知道这个调用的方法是部署哪里。通过RPC能解耦服务,这才是使用RPC的真正目的。RPC的原理主要用到了动态代理模式,至于http协议,只是传输协议而已。简单的实现可以参考spring remoting,复杂的实现可以参考dubbo。

总结一下就是:
RPC就是从一台机器(客户端)上通过参数传递的方式调用另一台机器(服务器)上的一个函数或方法(可以统称为服务)并得到返回的结果。
RPC 会隐藏底层的通讯细节(不需要直接处理Socket通讯或Http通讯) RPC 是一个请求响应模型。
客户端发起请求,服务器返回响应(类似于Http的工作方式) RPC 在使用形式上像调用本地函数(或方法)一样去调用远程的函数(或方法)。

二、rpcgen

rpcgen是一种工具,它可以生成实现RPC的C语言代码。使用rpcgen时,你需要提供一个与C语言类似的RPC语言源文件。
rpcgen通常通过一个源文件生成四个输出文件。如果输入文件是proto.x,rpcgen将生成一个头文件proto.h,XDR规则proto_xdr.c,服务端存根proto_svc.c,客户端存根proto_clt.c 若使用-T选项,还会生成一个proto_tbl.i,使用-Sc选项,将会生成一个客户端的rpc样例,使用-Ss选项将会生成一个服务端的rpc样例。

部分options解释:

-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),生成后还需要手动添加代码。

三、Linux上编写简易rpc-demo

(一)获取服务器时间

1、编写.x文件

program TESTPROG {
version VERSION {
string TEST(string) = 1;
} = 1;
} = 87654321;

这里数字87654321是RPC程序编号,还有VERSION版本号为1,都是给RPC服务程序用的。同时指定程序接受一个字符串参数。

2、运行命令

rpcgen test.x

将生成三个源文件:

test_clnt.c test.h test_svc.c

源文件test_clnt.c 内容如下:

/*
* Please do not edit this file.
* It was generated using rpcgen.
*/

#include <memory.h>  /* for memset */
#include "test.h"

/* Default timeout can be changed using clnt_control() */
static struct timeval TIMEOUT = { 25, 0 };

char **
test_1(char **argp, CLIENT *clnt)
{
static char *clnt_res;

memset((char *)&clnt_res, 0, sizeof(clnt_res));
if (clnt_call (clnt, TEST,
(xdrproc_t) xdr_wrapstring, (caddr_t) argp,
(xdrproc_t) xdr_wrapstring, (caddr_t) &clnt_res,
TIMEOUT) != RPC_SUCCESS) {
return (NULL);
}
return (&clnt_res);
}

说明:这是一个客户端调用函数,即客户端代码需要用到此函数。

源文件test.h内容如下:

/*
* Please do not edit this file.
* It was generated using rpcgen.
*/

#ifndef _TEST_H_RPCGEN
#define _TEST_H_RPCGEN

#include <rpc/rpc.h>


#ifdef __cplusplus
extern "C" {
#endif


#define TESTPROG 87654321
#define VERSION 1

#if defined(__STDC__) || defined(__cplusplus)
#define TEST 1
extern char ** test_1(char **, CLIENT *);
extern char ** test_1_svc(char **, struct svc_req *);
extern int testprog_1_freeresult (SVCXPRT *, xdrproc_t, caddr_t);

#else /* K&R C */
#define TEST 1
extern char ** test_1();
extern char ** test_1_svc();
extern int testprog_1_freeresult ();
#endif /* K&R C */

#ifdef __cplusplus
}
#endif

#endif /* !_TEST_H_RPCGEN */

说明:这里定义了一些公用头文件。

源文件test_svc.c内容如下:

/*
* Please do not edit this file.
* It was generated using rpcgen.
*/

#include "test.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
testprog_1(struct svc_req *rqstp, register SVCXPRT *transp)
{
union {
char *test_1_arg;
} argument;
char *result;
xdrproc_t _xdr_argument, _xdr_result;
char *(*local)(char *, struct svc_req *);

switch (rqstp- case NULLPROC:
(void) svc_sendreply (transp, (xdrproc_t) xdr_void, (char *)NULL);
return;

case TEST:
_xdr_argument = (xdrproc_t) xdr_wrapstring;
_xdr_result = (xdrproc_t) xdr_wrapstring;
local = (char *(*)(char *, struct svc_req *)) test_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;
}

int
main (int argc, char **argv)
{
register SVCXPRT *transp;

pmap_unset (TESTPROG, VERSION);

transp = svcudp_create(RPC_ANYSOCK);
if (transp == NULL) {
fprintf (stderr, "%s", "cannot create udp service.");
exit(1);
}
if (!svc_register(transp, TESTPROG, VERSION, testprog_1, IPPROTO_UDP)) {
fprintf (stderr, "%s", "unable to register (TESTPROG, VERSION, 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, TESTPROG, VERSION, testprog_1, IPPROTO_TCP)) {
fprintf (stderr, "%s", "unable to register (TESTPROG, VERSION, tcp).");
exit(1);
}

svc_run ();
fprintf (stderr, "%s", "svc_run returned");
exit (1);
/* NOTREACHED */
}

说明:这是一个标准的服务器端代码。

3、运行下列命令生成一个客户端源文件test_client.c:

rpcgen -Sc -o test_client.c test.x

源代码test_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 "test.h"


void
testprog_1(char *host)
{
CLIENT *clnt;
char * *result_1;
char * test_1_arg;

#ifndef DEBUG
clnt = clnt_create (host, TESTPROG, VERSION, "udp");
if (clnt == NULL) {
clnt_pcreateerror (host);
exit (1);
}
#endif /* DEBUG */

result_1 = test_1(&test_1_arg, clnt);
if (result_1 == (char **) 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];
testprog_1 (host);
exit (0);
}

4、运行这个命令生成服务端源文件test_srv_func.c:

rpcgen -Ss -o test_srv_func.c test.x

源文件test_srv_func.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 "test.h"

char **
test_1_svc(char **argp, struct svc_req *rqstp)
{
static char * result;

/*
* insert server code here
*/

return &result;
}

说明:这是一个服务器端调用的函数。


至此,我们就可以编译生成程序来运行了。
用下面的命令编译生成服务端程序test_server:


gcc -Wall -o test_server test_clnt.c test_srv_func.c test_svc.c

用下面的命令编译生成客户端程序test_client:


gcc -Wall -o test_client test_client.c test_clnt.c

运行下列命令启动服务端:


./test_server

运行下列命令可以进行客户端测试:


./test_client 127.0.0.1

但是由于现的的服务端没有处理客户端请求,所以这样的程序还不能完成任何工作。


下面我们先给服务端程序加上代码,使这个服务器能完成一定的工作。即修改 test_srv_func.c ,在 “ * insert server code here ” 后面加上取时间的代码,即修改后的 test_srv_func.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 <time.h>
#include "test.h"

char **
test_1_svc(char **argp, struct svc_req *rqstp)
{
static char * result;
static char tmp_char[128];
time_t rawtime;

/*
* insert server code here
*/
if( time(&rawtime) == ((time_t)-1) ) {
strcpy(tmp_char, "Error");
result = tmp_char;
return &result;
}
sprintf(tmp_char, "服务器当前时间是 :%s", ctime(&rawtime));
result = tmp_char;

return &result;
}

5、修改代码内容

再修改客户端代码以显示服务器端返回的内容,即修改test_client.c源文件,只需要修改其中的函数testprog_1,修改后如下:

void
testprog_1(char *host)
{
CLIENT *clnt;
char * *result_1;
char * test_1_arg;

test_1_arg = (char *)malloc(128);
#ifndef DEBUG
clnt = clnt_create (host, TESTPROG, VERSION, "udp");
if (clnt == NULL) {
clnt_pcreateerror (host);
exit (1);
}
#endif /* DEBUG */

result_1 = test_1(&test_1_arg, clnt);
if (result_1 == (char **) NULL) {
clnt_perror (clnt, "call failed");
}
if (strcmp(*result_1, "Error") == 0) {
fprintf(stderr, "%s: could not get the time/n", host);
exit(1);
}
printf("收到消息 ... %s/n", *result_1);
#ifndef DEBUG
clnt_destroy (clnt);
#endif /* DEBUG */
}

重新运行上述编译命令编译生成程序:

gcc -Wall -o test_server test_clnt.c test_srv_func.c test_svc.c
gcc -Wall -o test_client test_client.c test_clnt.c

6、再次运行

启动服务端程序

./test_server

运行客户端程序如下:

./test_client 127.0.0.1

rpcgen帮我们生成了makefile,也可以使用make -f命令调用

$ make -f Makefile.calculator 

​结果是:

收到消息 ... 服务器当前时间是 :Tue Feb 27 11:45:21 2021

(二)实现计算器功能

参考:

https://www.cnblogs.com/bluettt/p/12887671.html
Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐