RPC(Remote Procedure Call Protocol)——远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议.
最初由 Sun 公司提出 。
其不需要了解底层网络技术协议的原因在于:
我们只编写服务端需要被调用的函数声明,和定义,使用了一种IDL语言(还是别的哈?),没有main
函数。
使用rpcgen
工具自动生成服务器端的完整代码,带有main
。
爽一爽
我们先通过一个例子来演示RPC:
编写RPC说明文件
*.x
该文件相当于一个啥啊。对,相当于CMakeLists.txt
文件,执行cmake
会根据这个文件申城会生成一系列的文件。
而*.x
文件,执行rpcgen
也会生成对应的各种文件。
这是一种IDL语言
double_v.x文件
/*参数结构体*/
struct double_in
{
long key;
};
/*返回值结构体*/
struct dpuble_out
{
long value;
pid_t pid;
};
/*调用程序*/
program DOUBLE_PROG
{
/*版本号*/
version DOUBLE_VERS1
{
/*调用程序*/
double_out double_func(double_in)=1;/*这是个程序标号*/
} = 1;/*这个1就是版本号*/
version DOUBLE_VERS2
{
void double_func(double_in) = 1;
void double_func2(double_in) = 2;
} = 2;
version DOUBLE_VERS3
{
double_out double_func(string) = 1;
} =12;
}=0x12345678;
你没看错!!!这个古老的东西不支持//
注释
后面都有个标号=1
或者=2
,这些东西的存在是因为:
我们客户端通过网络传给程序只是一些数字啊,之类的。所以如何通过这些数字,字符串去调用一个函数?可以使用反射,而sun RPC使用的是switch
。具体看下面。
执行
rpcgen double_v.x
生成4个文件:double_v_clnt.c
,double_v_svc.c
,double_v_xdr.c
,double_v.h
。
rpc默认函数默认只能有一个参数,多个参数使用结构体。但是可以使用参数改变:
-N
double_v_xdr.c文件
而在分布式系统中,不同远程机器上可能有不同的字节顺序,不同大小的整数,以及不同的浮点表示。想与异构系统通信,我们就需要想出一个“标准”来对所有数据类型进行编码,并可以作为参数传递。隐式类型,是指只传递值,而不传递变量的名称或类型。常见的例子是 ONC RPC 的 XDR 和 DCE RPC 的 NDR。显式类型,指需要传递每个字段的类型以及值。常见的例子是 ISO 标准 ASN.1 (Abstract Syntax Notation)、JSON (JavaScript Object Notation)、Google Protocol Buffers、以及各种基于 XML 的数据表示格式。
XDR估计应该是一种古老的格式了。
而这个文件中存储的就是我们定义的两个结构体:
/*
* Please do not edit this file.
* It was generated using rpcgen.
*/
#include "double_v.h"
bool_t
xdr_double_in (XDR *xdrs, double_in *objp)
{
register int32_t *buf;
if (!xdr_long (xdrs, &objp->key))
return FALSE;
return TRUE;
}
bool_t
xdr_dpuble_out (XDR *xdrs, dpuble_out *objp)
{
register int32_t *buf;
if (!xdr_long (xdrs, &objp->value))
return FALSE;
if (!xdr_pid_t (xdrs, &objp->pid))
return FALSE;
return TRUE;
}
同时还提示我们不要去修改这个文件。
double_v_svc.c文件
称为服务器存根
包含服务器端的main
函数
没错,这个文件就是服务器端最主要的文件了。
当消息到达服务器时,服务器上的操作系统将它传递给服务器存根(server stub)。服务器存根是客户存根在服务器端的等价物,也是一段代码,用来将通过网络输入的请求转换为本地过程调用。
就是这个文件客户端传入的参数转化为实际的函数调用。
可以使用反射,但是RCP这种古老的东西使用的是switch
/*
* Please do not edit this file.
* It was generated using rpcgen.
*/
#include "double_v.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
/*省略 double_prog_1 部分
*/
static void
double_prog_2(struct svc_req *rqstp, register SVCXPRT *transp)
{
union {
double_in double_func_2_arg;
double_in double_func2_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 double_func:
_xdr_argument = (xdrproc_t) xdr_double_in;
_xdr_result = (xdrproc_t) xdr_void;
local = (char *(*)(char *, struct svc_req *)) double_func_2_svc;
break;
case double_func2:
_xdr_argument = (xdrproc_t) xdr_double_in;
_xdr_result = (xdrproc_t) xdr_void;
local = (char *(*)(char *, struct svc_req *)) double_func2_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;
}
/*省略 double_prog_12 部分
*/
int
main (int argc, char **argv)
{
register SVCXPRT *transp;
pmap_unset (DOUBLE_PROG, DOUBLE_VERS1);
pmap_unset (DOUBLE_PROG, DOUBLE_VERS2);
pmap_unset (DOUBLE_PROG, DOUBLE_VERS3);
transp = svcudp_create(RPC_ANYSOCK);
if (transp == NULL) {
fprintf (stderr, "%s", "cannot create udp service.");
exit(1);
}
if (!svc_register(transp, DOUBLE_PROG, DOUBLE_VERS1, double_prog_1, IPPROTO_UDP)) {
fprintf (stderr, "%s", "unable to register (DOUBLE_PROG, DOUBLE_VERS1, udp).");
exit(1);
}
if (!svc_register(transp, DOUBLE_PROG, DOUBLE_VERS2, double_prog_2, IPPROTO_UDP)) {
fprintf (stderr, "%s", "unable to register (DOUBLE_PROG, DOUBLE_VERS2, udp).");
exit(1);
}
if (!svc_register(transp, DOUBLE_PROG, DOUBLE_VERS3, double_prog_12, IPPROTO_UDP)) {
fprintf (stderr, "%s", "unable to register (DOUBLE_PROG, DOUBLE_VERS3, 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, DOUBLE_PROG, DOUBLE_VERS1, double_prog_1, IPPROTO_TCP)) {
fprintf (stderr, "%s", "unable to register (DOUBLE_PROG, DOUBLE_VERS1, tcp).");
exit(1);
}
if (!svc_register(transp, DOUBLE_PROG, DOUBLE_VERS2, double_prog_2, IPPROTO_TCP)) {
fprintf (stderr, "%s", "unable to register (DOUBLE_PROG, DOUBLE_VERS2, tcp).");
exit(1);
}
if (!svc_register(transp, DOUBLE_PROG, DOUBLE_VERS3, double_prog_12, IPPROTO_TCP)) {
fprintf (stderr, "%s", "unable to register (DOUBLE_PROG, DOUBLE_VERS3, tcp).");
exit(1);
}
svc_run ();
fprintf (stderr, "%s", "svc_run returned");
exit (1);
/* NOTREACHED */
}
该文件的代码有这些,很多但重复的也有很多。
其中很多的宏,比如下面这个
其对应的值就是我们在
double_v.x
文件中,等于号后面的数值。还有下面这个
DOUBLE_PROG
所以,这就是等于号后面的那些标识的作用。
如果version DOUBLE_VERS1
这一个,同名了,那么就会有多个define
对应不同的值,是不正确的。
分析一下代码:
选取要执行的函数从上面的代码我们可以看出,是使用的
switch
来选取要执行的函数,将函数赋值给了local
,同时将传入参数进行了转化。
执行函数
上面的代码就是具体的执行了函数。
首先double_prog_2
是服务器端选取函数并执行函数的函数。
他的命名是和double_v.x
中version DOUBLE_VERS2
的命名一致的。这个函数是生成的不需要我们去修改
而选出出来的函数:double_func2_2_svc
是和version DOUBLE_VERS2
中的程序名一直的。这个函数是我们需要编写的。使用rpcgen -Ss -o double_v_server.c double_v.x
,该函数就定义在double_v_server.c
中,但是需要我们去手动编写。
后面讲。
然后我们看一下main
函数:
这部分代码使用来注册端口的,根据
tcp
还是udp
来选择执行的代码。后面在执行流程中再细细的将。 执行函数
这个函数应该就是要执行循环等待的函数了。
。。没找到这个函数的实现。
double_v_clnt.c文件
客户端凭证
该文件定义了我们需要调用的那些远程调用函数。并且封装参数,发送给客户端
/*
* Please do not edit this file.
* It was generated using rpcgen.
*/
#include <memory.h> /* for memset */
#include "double_v.h"
/* Default timeout can be changed using clnt_control() */
static struct timeval TIMEOUT = { 25, 0 };
/*省略了其他几个函数的定义,应为模样都差不多。
*/
void *
double_func2_2(double_in *argp, CLIENT *clnt)
{
static char clnt_res;
memset((char *)&clnt_res, 0, sizeof(clnt_res));
if (clnt_call (clnt, double_func2,
(xdrproc_t) xdr_double_in, (caddr_t) argp,
(xdrproc_t) xdr_void, (caddr_t) &clnt_res,
TIMEOUT) != RPC_SUCCESS) {
return (NULL);
}
return ((void *)&clnt_res);
}
分析一下代码
i封装信息这个函数将我们的参数进行转换。最终都转换为下面这个:
转换
然后clnt_call
应该是将信息封装,发送给客户端,并阻塞,等待返回值吧。
但是这里:static char clnt_res;
,该函数不是一个线程安全函数。
我们可以在rpcgen -M double_v.x
的线程安全版本,这样子生成的客户端凭证代码为:
使用rpcgen -Sc -o double_v_client.c double_v.x
可以生成一个简单的客户端的样例(没啥用的感觉),就是让你爽一爽。
double_v.h文件
头文件
就是个头文件,使用的时候引用他就行。
在这里面可以看到,我们定义的那些标号=1
。
XDR 数据格式
external data representation外部数据表示
存在的情况如下:
- C/C++下类型的字节大小不同
- 大端小端的差异
XDR
是一种用于描述数据类型的语言,同时也用语编码数据的规则。
XDR使用隐式类型指定,也就是发送者和接受者都知道数据的类型和字节序。
以大端传送。
一系列的类型的对应
数据类型
网友评论