RPC简介
RPC(Remote Procedure Call)——远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的细节的技术。
通过RPC我们可以充分利用非共享内存的多处理器环境(例如通过局域网连接得多台工作站),这样可以简便地将你的应用分布在多台工作站上,应用程序就像运行在一个多处理器的计算机上一样。你可以方便的实现过程代码共享,提高系统资源的利用率,也可以将以大量数值处理的操作放在处理能力较强的系统上运行,从而减轻前端机的负担。
在OSI网络通信模型中,RPC跨越了传输层和应用层。RPC使得开发包括网络分布式多程序在内的应用程序更加容易。
RPC 的主要目的是为组件提供一种相互通信的方式,使这些组件之间能够相互发出请求并传递这些请求的结果。
RPC调用过程
客户机对服务器的一次RPC调用,其内部操作大致有如下十步:
1.调用客户端句柄;执行传送参数
2.调用本地系统内核发送网络消息
3.消息传送到远程主机
4.服务器句柄得到消息并取得参数
5.执行远程过程
6.执行的过程将结果返回服务器句柄
7.服务器句柄返回结果,调用远程系统内核
8.消息传回本地主机
9.客户句柄由内核接收消息
10.客户接收句柄返回的数据
RPC的应用场景
RPC在分布式系统中的系统环境建设和应用程序设计中有着广泛韵应用,应用包括如下方面:
1. 分布式操作系统的进程间通讯
进程间通讯是操作系统必须提供的基本设施之一,分布式操作系统必须提供分布于异构的结点机上进程间的通讯机制,RPC是实现消息传送模式的分布式进程间通讯的手段之一。
2. 构造分布式计算的软件环境
由于分布式软件环境本身地理上的分布性,它的各个组成成份之间存在大量的交互和通讯,RPC是其基本的实现方法之一。ONC+和DCE两个流行的分式布式计算软件环境都是使用RPC构造的,其它一些分布式软件环境也采用了RPC方式。
3. 远程数据库服务
在分布式数据库系统中,数据库一般在服务器上,客户机通过远程数据库服务功能访问数据库服务器,现有的远程数据库服务是使用RPC模式的。锝如,Sybase和Oracle都提供了存储过程机制,系统与用户定义的存储过程存储在数据库服务器上,用户在客户端使用RPC模式调用存储过程。
4. 分布式应用程序设计
RPC机制与RPC工具为分布式应用程序设计提供了手段和方便,用户可以无需知道网络结构和协议细节而直接使用RPC工具设计分布式应用程序。
5. 分布式程序的调试
RPC可用于分布式程序的调试。使用反RPC使服务器成为客户并向它的客户进程发出RPC,可以调试分布式程序。例如.在服务器上运行一个远端调试程序,它不断接收客户端的RPC,当遇到一个调试程序断点时,它向客户机发回一个RPC,通知断点已经到达,这也是RPC用于进程通讯的例子。
Rpcgen介绍
Rpcgen是一个编译器,她可以自动生成RPC服务器程序的大多数代码,它的输入为一个规格说明文件(*.x),它的输出为一个C语言的源程序。
规格文件(*.x)包含常量、全局数据类型以及远程过程的声明。rpcgen产生的代码包含了实现客户机和服务器程序所需要的大部分源代码,包括参数整理、发送RPC报文、参数和结果的外部数据表示以及本地数据表示的转换等。不过在由rpcgen生成的源文件中,没有过程的具体实现,所以程序员必须要手工编辑这些文件,实现这些过程。
规格文件是使用RPC语言编写的,RPC语言是XDR语言的扩展,RPC对XDR的唯一扩展,就是增加了program类型。下面来介绍一下XDR语言的语法。
XDR语言介绍
XDR(External Data Representation)是SunSoft的开放网络计算环境的一种功能。XDR提供了一种与体系结构无关的表示数据,解决了数据字节排序的差异、数据字节大小、数据表示和数据对准的方式。使用XDR的应用程序,可以在异构硬件系统上交换数据。
外部数据表示法(XDR) 是在OSI模型的表示层presentation layer中实现。XDR允许把数据包装在独立于介质的结构中使得数据可以在异构的计算机系统中传输。从局部表示转换到XDR称为编码,从XDR转换到局部表示称为译码。XDR使用软件来完成变换,所以在不同的操作系统中可以灵活的运用。另外,XDR还是独立于传输层的transport layer。Sun的远端程序呼叫RPC就是使用XDR。
XDR语言跟C语言非常接近。
结构类型
下面左边是XDR语言定义的一个二维坐标的结构体,右边是左边结构体编译后生成的C语言结构体:
struct coord{ struct coord{
int x; --> int x;
int y; int y;
}; };
typedef struct coord coord;
联合类型
XDR的联合类型跟C语言的联合类型很不一样,它更像Pascal的records变量类型。
union read_result switch (int errno) {
case 0:
opaque data[1024];
default:
void;
};
上面的联合类型编译后生成的C语言如下:
struct read_result {
int errno;
union {
char data[1024];
} read_result_u;
};
枚举类型
下面左边是XDR语言定义的一个颜色枚举类型,右边是左边枚举类型编译后生成的C语言枚举类型:
enum colortype { enum colortype {
RED = 0, RED = 0,
GREEN = 1, --> GREEN = 1,
BLUE = 2, BLUE = 2,
}; };
typedef enum colortype colortype;
Typedef
下面左边是XDR语言定义的typedef,右边是左边的typedef编译后生成的C语言typedef:
typedef string fname_type<255>; --> typedef char* fname_type;
常量(Constants)
const DOZEN = 12; --> #define DOZEN 12
XDR里面的常量都是整型常量,下面定义了一个常量DOZEN,他的值为整形12
Program
Program定义的其实就是RPC的协议
program TIMEPROG {
version TIMEVERS {
unsigned int TIMEGET(void) = 1;
void TIMESET(unsigned) = 2;
} = 1;
} = 44;
上面的代码编译后会在头文件里面生成如下定义:
#define TIMEPROG 44
#define TIMEVERS 1
#define TIMEGET 1
#define TIMESET 2
声明
XDR语言里面只有四种声明类型:普通类型声明,固定数组声明,可变数组声明,指针声明。
普通类型声明
colortype color; --> colortype color;
固定数组声明
colortype palette[8]; --> colortype palette[8];
可变数组声明
可变数组声明在C语言中,没有类似的语法,所以XDR自创了这种声明。它使用尖括号来声明数组,数组里面的数字代表这个数组的最大size,尖括号里面也可以没有数字,代表这个数组可以是任意大小:
int heights<12>; /*at most 12 items*/
int widths<>; /*any number of items*/
因为可变数组在C语言里面没有类似的语法,所以这种XDR声明在编译成C语言时,会被转换为结构体类型(struct),比如说上面对heights的可变数组声明,编译后生成的C代码如下:
struct {
u_int heights_len; /*number of items in array*/
int *heights_val; /*pointer array*/
}heights;
数组的个数存放在heights_len字段里面,数组的指针存放在heights_val字段里面。
指针声明
XDR的指针语法跟C语言指针的语法基本上是一样的,但,这并不是说你可以在网络上传输指针类型数据。你可以使用XDR里面的指针类型传输列表或者树类型的数据。
listitem *next; --> listitem *next;
特殊类型
布尔类型
C没有内置的布尔类型,但是rpc库里面定义了布尔类型,类型名是bool_t,值为TRUE或者FALSE。使用XDR bool类型定义的数据,编译后会被转换成bool_t类型:
bool married; --> bool_t married;
字符串类型
C没有内置的string类型,只有‘\0’结尾的char*类型。XDR语言里面使用关键字string来定义字符串类型数据,编译后会转换为char*类型的数据。尖括号里面的数字代码字符串的最大长度(不包括’\0’结束符),尖括号里面如果没有数字,代表可以是任意长度的字符串:
string name<32>; --> char *name;
string longname<>; --> char *longname;
Opaque Data类型
Opaque data在XDR和RPC语言里面用来定义没有类型的数据,代表任意类型的序列,它可以被定义成固定的或者非固定的数组:
opaque diskblock[512]; --> char diskblock[512];
opaque filedata<1024>; --> struct {
u_int filedata_len;
char *filedata_val;
}filedata;
Void类型
Void 类型在XDR语言里面只会出现在两个地方:union和program的定义里面。
在program定义里面代表没有返回值或者没有参数。
Rpcgen自动生成的文件
文件名 | 作用 |
Makefile.file | 该文件用于编译所有客户机,服务器代码 |
File_clnt.c | 该文件包含client_stub,程序员一般不用修改 |
File_svc.c | 该文件包含server_stub,程序员一般不用修改 |
File.h | 该文件包含了从说明中产生的所有XDR类型 |
File_xdr.c | 该文件包含了客户机和服务器stub所需的XDR过滤器,程序员一般不用修改 |
File_server.c | 如果生成此文件,则该文件包含远程服务的stub |
File_client.c | 如果生成此文件,则该文件包含了骨架客户机程序 |
Rpcgen的部分编译选项
选项 | 作用 |
-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),生成后还需要手动添加代码 |
实例:
Rpcgen -C file.x 生成file_xdr.c, file.h, Makefile.file, file_svc.c 和 file_clnt.c
Rpcgen -C -a file.x 比上面多生成了2个文件,file_server.c 和 file_client.c
使用C实现简单的RPC
我们先看一张rpc应用的调用过程图:
一般而言在开发RPC时,我们通常分为三个步骤:
1:定义说明客户/服务器的通信协议。这里所说的通信协议是指定义服务过程的名称、调用参数的数据类型和返回参数的数据类型,还包括底层传输类型(可以是UDP或TCP),当然也可以由RPC底层函数自动选择连接类型建立TI-RPC。最简单的协议生成的方法是采用协议编译工具,常用的有rpcgen。
2:开发客户端程序。
3:开发服务器端程序。
下面是一个rpc实例,我们使用rpcgen 工具来生成远程程序接口模块,它将以RPC语言书写的源代码进行编译,rpc语言在结构和语法上同C语言相似。由rpcgen 编译生成的C源程序可以直接用C编译器进行编译,rpcgen的源程序以.x结尾。
rpcgen 源程序 time.x:
program TIMEPROG {
version PRINTTIMEVERS {
string PRINTTIME(string) = 1; /* 过程号 */
} = 1; /* 版本号 */
} = 99; /* 程序号 */
time_proc.c源程序:
/* time_proc.c: implementation of the remote procedure "printime" */
#include <stdio.h>
#include <time.h>
#include "time.h"
/* Remote version of "printime" */
char ** printime_1_svc(char **msg,struct svc_req *req)
{
static char * result;
static char tmp_char[100];
time_t rawtime;
FILE *f = fopen("/tmp/rpc_result", "a+");
if (f ==NULL) {
strcpy(tmp_char,"Error");
result = tmp_char;;
return (&result);
}
fprintf(f, "%s", *msg);
fclose(f);
time(&rawtime);
sprintf(tmp_char,"Current time is :%s",ctime(&rawtime));
result =tmp_char;
return (&result);
}
rtime.c源代码
#include <stdio.h>
#include "time.h" /* time.h generated by rpcgen */
int main(int argc, char **argv)
{
CLIENT *clnt;
char *result;
char *server;
char *message;
if (argc != 3) {
fprintf(stderr, "usage: %s host message", argv[0]);
return -1;
}
server = argv[1];
message = argv[2];
clnt = clnt_create(server, TIMEPROG, PRINTTIMEVERS, "TCP");
if (clnt == NULL) {
clnt_pcreateerror(server);
return -1;
}
result =*printime_1(&message, clnt);
if (result== (char *)NULL) {
clnt_perror(clnt, server);
return -1;
}
if (strcmp(result,"Error")==0){
fprintf(stderr, "%s: could not get the time",argv[0]);
return -1;
}
printf("From the Time Server : %s",result);
clnt_destroy( clnt );
return 0;
编译方法:
有了以上的三段代码后,就可用rpcgen 编译工具进行RPC协议编译,命令如下:
$rpcgen time.x
rpcgen 会自动生成time.h、time_svc.c、time_clnt.c
再用系统提供的gcc进行C的编译,命令如下:
$gcc rtime.c time_clnt.c -o rtime //客户端编译
$gcc time_proc.c time_svc.c -o time_server //服务器端编译
编译成功后即可在Server端运行time_server,立即将该服务绑定在rpc服务端口上提供服务。在客户端运行./rtime hostname msg (msg 是一字符串,笔者用来测试时建立的),立即会返回hostname 端的时间。
------------------------------------------------------------------------
欢迎关注我的微信公众号 ^_^
来源:oschina
链接:https://my.oschina.net/u/1263964/blog/608234