Linux下C实现RPC
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 在使用形式上像调用本地函数(或方法)一样去调用远程的函数(或方法)。
XDR
XDR是一个数据描述和数据编码的标准,XDR的主要作用就是在不同进程间传递消息参数时,避免因为计算机平台的不一致而导致数据传送接收异常。它可以对消息参数按照一定的顺序编码,放在一个数据包里(通常是在内存中申请一个一定大小的字符串缓冲区),然后把这个数据包发送给其他平台,然后在按照之前编码的顺序依次解码,并可以获得原来的消息参数。
rpcgen
rpcgen介绍
可以看执行man rpcgen
在man page中查看rpcgen的介绍,不得不说man page真的是很强大的工具,里面的介绍说明言简意赅,还能练英语 😄
原文:
rpcgen is a tool that generates C code to implement an RPC protocol. The input to rpcgen is a language similar to C known as RPC Language (Remote Procedure Call Language).
rpcgen is normally used as in the first synopsis where it takes an input file and generates up to four output files. If the infile is named proto.x, then rpcgen will generate a header file in proto.h, XDR routines in proto_xdr.c, server-side stubs in proto_svc.c, and client-side stubs in proto_clnt.c. With the -T option, it will alsogenerate the RPC dispatch table in proto_tbl.i. With the -Sc option, it will also generate sample code which would illustrate how to use the remote procedures on the client side. This code would be created in proto_client.c. With the -Ss option, it will also generate a sample server code which would illustrate how to write the remote procedures. This code would be created in proto_server.c..
翻译:
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样例。
rpcgen命令选项
usage: rpcgen infile
rpcgen [-abkCLNTM][-Dname[=value]] [-i size] [-I [-K seconds]] [-Y path] infile
rpcgen [-c | -h | -l | -m | -t | -Sc | -Ss | -Sm] [-o outfile] [infile]
rpcgen [-s nettype]* [-o outfile] [infile]
rpcgen [-n netid]* [-o outfile] [infile]
options:
-a generate all files, including samples
-b backward compatibility mode (generates code for SunOS 4.1)
-c generate XDR routines
-C ANSI C mode
-Dname[=value] define a symbol (same as #define)
-h generate header file
-i size size at which to start generating inline code
-I generate code for inetd support in server (for SunOS 4.1)
-K seconds server exits after K seconds of inactivity
-l generate client side stubs
-L server errors will be printed to syslog
-m generate server side stubs
-M generate MT-safe code
-n netid generate server code that supports named netid
-N supports multiple arguments and call-by-value
-o outfile name of the output file
-s nettype generate server code that supports named nettype
-Sc generate sample client code that uses remote procedures
-Ss generate sample server code that defines remote procedures
-Sm generate makefile template
-t generate RPC dispatch table
-T generate code to support RPC dispatch tables
-Y path directory name to find C preprocessor (cpp)
-5 SysVr4 compatibility mode
--help give this help list
--version print program version
部分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
Linux下面的RPC模型是SUN RPC (ONC RPC),使用了XDR来编码/解码数据。gcc提供了一些标准数据类型的XDR filter(比如整型,浮点型,字符串等)。对于自定义数据类型,则需要自己编写XDR filter来处理。
你可以使用rpcgen来帮你自动生成xdr filter,但是,该工具需要你提供一个 .x 文件。
这里提供两个demo作为参考
- 简易计算器
(1)编写.x文件
/*
* filename: calculator.x
* function: 定义远程调用中常量、非标准数据类型以及调用过程
*/
const ADD = 0;
const SUB = 1;
const MUL = 2;
const DIV = 3;
struct CALCULATOR
{
int op; /* 0-ADD, 1-SUB, 2-MUL, 3-DIV */
float arg1;
float arg2;
float result;
};
program CALCULATOR_PROG
{
version CALCULATOR_VER
{
struct CALCULATOR CALCULATOR_PROC(struct CALCULATOR) = 1;
} = 1; /* 版本号=1 */
} = 0x20000001; /* RPC程序编号 */
(2)使用rpcgen生成文件
想一步到位,生成所有文件的话执行:
$ rpcgen -a calculator.x
(如果你需要分步执行也是可以的,根据上面写的rpcgen命令使用不同的参数)
生成以下文件:
文件名 | 作用 |
---|---|
Makefile.calculator | 该文件用于编译所有客户机,服务器代码 |
calculator.h | 声明用到的变量和函数 |
calculator_xdr.c | 非标准数据类型的编码 |
calculator_clnt.c | 将远程调用代理( proxying ) 为本地OS调用,并将调用参数打包成一个消息,然后将此消息发送给服务器。由rpcgen生成,程序员一般无需修改 |
calculator_svc.c | 将通过网络输入的请求转换为本地过程调用,即负责解压服务器上收到的消息,并调用实际的服务器端、应用程序级的实现,程序员一般不用修改 |
calculator_client.c | 骨架客户端程序,需要自行修改 |
calculator_server.c | 骨架服务端程序,需要自行修改 |
(3)修改骨架程序
这里需要我们自己修改calculator_client.c和calculator_server.c来实现功能
注:/* -<<< Add to test*/ 包含的为添加的代码
calculator_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 "calculator.h"
void
calculator_prog_1(char *host)
{
CLIENT *clnt;
struct CALCULATOR *result_1;
struct CALCULATOR calculator_proc_1_arg;
#ifndef DEBUG
clnt = clnt_create (host, CALCULATOR_PROG, CALCULATOR_VER, "udp");
if (clnt == NULL) {
clnt_pcreateerror (host);
exit (1);
}
#endif /* DEBUG */
/* -<<< Add to test*/
char c;
printf("choose the operation:\n\t0---ADD\n\t1---SUB\n\t2---MUL\n\t3---DIV\n");
c = getchar();
if(c>'3' && c<'0'){
printf("error:operate/n");
exit(1);
}
calculator_proc_1_arg.op = c-'0';
printf("input the first number:");
scanf("%f", &calculator_proc_1_arg.arg1);
printf("input the second number:");
scanf("%f", &calculator_proc_1_arg.arg2);
/* -<<< Add to test*/
result_1 = calculator_proc_1(&calculator_proc_1_arg, clnt);
if (result_1 == (struct CALCULATOR *) NULL) {
clnt_perror (clnt, "call failed");
}
#ifndef DEBUG
clnt_destroy (clnt);
#endif /* DEBUG */
/* -<<< Add to test*/
printf("The Result is %.3f \n", result_1->result);
/* -<<< Add to test*/
}
int
main (int argc, char *argv[])
{
char *host;
if (argc < 2) {
printf ("usage: %s server_host\n", argv[0]);
exit (1);
}
host = argv[1];
calculator_prog_1 (host);
exit (0);
}
calculator_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 "calculator.h"
struct CALCULATOR *
calculator_proc_1_svc(struct CALCULATOR *argp, struct svc_req *rqstp)
{
static struct CALCULATOR result;
/*
* insert server code here
*/
/* -<<< Add to test*/
switch(argp->op){
case ADD:
result.result = argp->arg1 + argp->arg2;
break;
case SUB:
result.result = argp->arg1 - argp->arg2;
break;
case MUL:
result.result = argp->arg1 * argp->arg2;
break;
case DIV:
result.result = argp->arg1 / argp->arg2;
break;
default:
break;
}
/* -<<< Add to test*/
return &result;
}
(4)编译
rpcgen帮我们生成了makefile,使用make -f命令调用即可
$ make -f Makefile.calculator
(5)执行
- 服务端时间获取
(1)编写.x文件
program DATE_PROG {
version DATE_VERS {
long GET_DATE(void) = 1;
} = 1;
} = 0x20000002;
(2)使用rpcgen生成文件
$ rpcgen -a date.x
(3)修改骨架程序
date_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 "date.h"
void
date_prog_1(char *host)
{
CLIENT *clnt;
long *result_1;
char *get_date_1_arg;
#ifndef DEBUG
clnt = clnt_create (host, DATE_PROG, DATE_VERS, "udp");
if (clnt == NULL) {
clnt_pcreateerror (host);
exit (1);
}
#endif /* DEBUG */
result_1 = get_date_1((void*)&get_date_1_arg, clnt);
if (result_1 == (long *) NULL) {
clnt_perror (clnt, "call failed");
}
/* -<<< Add to test*/
printf("%ld\n",*result_1);
/* -<<< Add to test*/
#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];
date_prog_1 (host);
exit (0);
}
date_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 "date.h"
#include <time.h>
long *
get_date_1_svc(void *argp, struct svc_req *rqstp)
{
static long result;
/*
* insert server code here
*/
/* -<<< Add to test*/
result = (long)time(0);
/* -<<< Add to test*/
return &result;
}
(4)编译(同上一个例子)
(5)执行
出现的问题
-
服务器无法启动,错误如下:Cannot register service: RPC: Unable to receive; errno = Connection refused
unable to register (TESTPROG, VERSION, udp).问题原因:系统没有安装 portmap 或者没有启动 portmap 端口映射。
解决方法: 在新版的 ubuntu 中 portmap 被 rpcbind 所代替, 所以需要启动 rpcbind 服务。
service rpcbind restart
参考资料
https://www.jianshu.com/p/b0343bfd216e
https://www.jianshu.com/p/b33fc01c1f59
http://blog.chinaunix.net/uid-20644632-id-2220585.html
https://blog.csdn.net/weixin_30538029/article/details/95014067