摘要:
本文简单介绍了RPC(Remote Procedure Call 远程过程调用)的原理结构、特点,
及其开放给编程人员不同层次的编程接口。并且例举实例示度绾瓮ü齊pcgen 编译工
具来快速开发RPC应用。
一、 概述
在传统的编程概念中,过程是由程序员在本地编译完成,并只能局限在本地运行的一段
代码,也即其主程序和过程之间的运行关系是本地调用关系。因此这种结构在网络日益
发展的今天已无法适应实际需求。总所周知,传统过程调用模式无法充分利用网络上其
他主机的资源(如CPU、Memory等),也无法提高代码在实体间的共享程度,使得主机资
源大量浪费。
而本文要介绍的RPC编程,正是很好地解决了传统过程所存在的一系列弊端。通过RPC我
们可以充分利用非共享内存的多处理器环境(例如通过局域汪连接得多台工作站),这样
可以简便地将你的应用分布在多台工作站上,应用程序就像运行在一个多处理器的计算机
上一样。你可以方便的实现过程代码共享,提高系统资源的利用率,也可以将以大量数值
处理的操作放在处理能力较强的系统上运行,从而减轻前端机的负担。
二、 RPC的结构原理及其调用机制
如前所述RPC其实也是种C/S的编程模式,有点类似C/S Socket 编程模式,但要比它
更高一层。当我们在建立RPC服务以后,客户端的调用参数通过底层的RPC传输通道,可以
是UDP,也可以是TCP(也即TI-RPC-无关性传输),并根据传输前所提供的目的地址及RPC
上层应用程序号转至相应的RPC Application Porgramme Server ,且此时的客户端处于等
待状态,直至收到应答或Time Out超时信号。具体的流程图如F1。当服务器端获得了请求
消息,则会根据注册RPC时告诉RPC系统的例程入口地址,执行相应的操作,并将结果返回
至客户端。
F1
当一次RPC调用结束后,相应线程发送相应的信号,客户端程序才会继续运行。
当然,一台服务主机上可以有多个远程过程提供服务,那么如何来表示一个唯一存
在的远程过程呢?一个远程过程是有三个要素来唯一确定的:程序号、版本号和过程号。
程序号是用来区别一组相关的并且具有唯一过程好的远程过程。一个程序可以有一个或几
个不同的版本,而每个版本的程序都包含一系列能被远程调用的过程,通过版本的引入,
使得不同版本下的RPC能同时提供服务。每个版本都包含有许多可供远程调用的过程,每个
过程则有其唯一标示的过程号。
三、 基于RPC的应用系统开发
通过以上对RPC原理的简介后,我们再来继续讨论如何来开发基于RPC的应用系统。
一般而言在开发RPC时,我们通常分为三个步骤:
a、 定义说明客户/服务器的通信协议。
这里所说的通信协议是指定义服务过程的名称、调用参数的数据类型和返回参数的数据
类型,还包括底层传输类型(可以是UDP或TCP),当然也可以由RPC底层函数自动选择
连接类型建立TI-RPC。最简单的协议生成的方法是采用协议编译工具,常用的有Rpcgen,
我会在后面实例中详细描述其使用方法。
b、 开发客户端程序。
c、 开发服务器端程序。
开发客户端和服务器端的程序时,RPC提供了我们不同层次的开发例程调用接口。不
同层次的接口提供了对RPC不同程度控制。一般可分为5个等级的编程接口,接下来我们
分别讨论一下各层所提供的功能函数。
1、 简单层例程
简单层是面向普通RPC应用,为了快速开发RPC应用服务而设计的,他提供
了如下功能函数。
函数名 功能描述
Rpc_reg( ) 在一特定类型的传输层上注册某个过程,来作为提供服务的RPC程序
Rpc_call( ) 远程调用在指定主机上指定的过程
Rpc_Broadcast( ) 向指定类型的所有传输端口上广播一个远程过程调用请求
2、 高层例程
在这一层,程序需要在发出调用请求前先创建一个客户端句柄,或是在侦听请
求前先建立一个服务器端句柄。程序在该层可以自由的将自己的应用绑在所有的
传输端口上,它提供了如下功能函数。
函数名 功能描述
Clnt_create( ) 程序通过这个功能调用,告诉底层RPC服务器的位置及其传输类型
Clnt_create_timed( ) 定义每次尝试连接的超时最大时间
Svc_create( ) 在指定类型的传输端口上建立服务器句柄,告诉底层RPC事件过程的相应入口地址
Clnt_call() 向服务器端发出一个RPC调用请求
3、 中间层例程
中间层向程序提供更为详细的RPC控制接口,而这一层的代码变得更为复杂,
但运行也更为有效,它提供了如下功能函数。
函数名 功能描述
Clnt_tp_create( ) 在指定的传输端口上建立客户端句柄
Clnt_tp_create_timed( ) 定义最大传输时延
Svc_tp_creaet( ) 在指定的传输端口上建立服务句柄
Clnt_call( ) 向服务器端发出RPC调用请求
4、 专家层例程
这层提供了更多的一系列与传输相关的功能调用,它提供了如下功能函数。
函数名 功能描述
Clnt_tli_create( ) 在指定的传输端口上建立客户端句柄
Svc_tli_create( ) 在指定的传输端口上建立服务句柄
Rpcb_set( ) 通过调用rpcbind将RPC服务和网络地址做映射
Rpcb_unset( ) 删除rpcb_set( ) 所建的映射关系
Rpcb_getaddr( ) 调用rpcbind来犯会指定RPC服务所对应的传输地址
Svc_reg( ) 将指定的程序和版本号与相应的时间例程建起关联
Svc_ureg( ) 删除有svc_reg( ) 所建的关联
Clnt_call( ) 客户端向指定的服务器端发起RPC请求
5、 底层例程
该层提供了所有对传输选项进行控制的调用接口,它提供了如下功能函数。
函数名 功能描述
Clnt_dg_create( ) 采用无连接方式向远程过程在客户端建立客户句柄
Svc_dg_create( ) 采用无连接方式建立服务句柄
Clnt_vc_create( ) 采用面向连接的方式建立客户句柄
Svc_vc_create( ) 采用面向连接的方式建立RPC服务句柄
Clnt_call( ) 客户端向服务器端发送调用请求
四、 实例介绍
以下我将通过实例向读者介绍通过简单层RPC的实现方法。通常在此过程中我们
将使用RPC协议编译工具-Rpcgen。Rpcgen 工具用来生成远程程序接口模块,它将以RPC
语言书写的源代码进行编译,Rpc 语言在结构和语法上同C语言相似。由Rpcgen 编译生
成的C源程序可以直接用C编译器进行编译,因此整个编译工作将分为两个部分。Rpcgen
的源程序以.x结尾,通过其编译将生成如下文件:
a) 一个头文件(.h)包括服务器和客户端程序变量、常量、类型等说明。
b) 一系列的XDR例程,它可以对头文件中定义的数据类型进行处理。
c) 一个Server 端的标准程序框架。
d) 一个Client 端的标准程序框架。
当然,这些输出可以是选择性的,Rpcgen 的编译选项说明如下:
选项 功能
'-' a 生成所有的模板文件
'-' Sc 生成客户端的模板文件
'-' Ss 生成服务器端的模板文件
'-' Sm 生成Makefile 文件
(详见Solaris Rpcgen Manaul)
Rpcgen 源程序 time.x:
/* time.x: Remote time printing protocol */
program TIMEPROG {
version PRINTIMEVERS {
string PRINTIME(string) = 1;
} = 1;
} = 0x20000001;
time_proc.c源程序(服务端):
/* time_proc.c: implementation of the remote procedure "printime" */
#include <stdio.h>
#include <rpc/rpc.h> /* always needed */
#include "time.h" /* time.h will be generated by rpcgen */
#include <time.h>
/* Remote version of "printime" */
char ** printime_1(char **msg,struct svc_req *req)
{
static char * result; /* must be static! */
static char tmp_char[100];
time_t rawtime;
FILE *f;
f = fopen("/tmp/rpc_result", "a+");
if (f == (FILE *)NULL) {
strcpy(tmp_char,"Error");
result = tmp_char;;
return (&result);
}
fprintf(f, "%s\n", *msg); //used for debugging
fclose(f);
time(&rawtime);
sprintf(tmp_char,"Current time is :%s",ctime(&rawtime));
result =tmp_char;
return (&result);
}
rtime.c源代码 (客户端)
/*
* rtime.c: remote version
* of "printime.c"
*/
#include <stdio.h>
#include "time.h" /* time.h generated by rpcgen */
main(int argc, char **argv)
{
CLIENT *clnt;
char *result;
char *server;
char *message;
if (argc != 3) {
fprintf(stderr, "usage: %s host message\n", argv[0]);
exit(1);
}
server = argv[1];
message = argv[2];
/*
* Create client "handle" used for
* calling TIMEPROG on the server
* designated on the command line.
*/
clnt = clnt_create(server, TIMEPROG, PRINTIMEVERS, "visible");
if (clnt == (CLIENT *)NULL) {
/*
* Couldn't establish connection
* with server.
* Print error message and die.
*/
clnt_pcreateerror(server);
exit(1);
}
/*
* Call the remote procedure
* "printime" on the server
*/
result =*printime_1(&message,clnt);
if (result== (char *)NULL) {
/*
* An error occurred while calling
* the server.
* Print error message and die.
*/
clnt_perror(clnt, server);
exit(1);
}
/* Okay, we successfully called
* the remote procedure.
*/
if (strcmp(result,"Error") == 0) {
/*
* Server was unable to print
* the time.
* Print error message and die.
*/
fprintf(stderr, "%s: could not get the time\n",argv[0]);
exit(1);
}
printf("From the Time Server ...%s\n",result);
clnt_destroy( clnt );
exit(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 -lnsl //客户端编译 在freebsd下不要用-lnsl
$gcc time_proc.c time_svc.c -o time_server -lnsl //服务器端编译 在freebsd下不要用-lnsl
编译成功后即可在Server端运行time_server,立即将该服务绑定在rpc服务端口上提供
服务。在客户端运行./rtime hostname msg (msg 是一字符串,笔者用来测试时建立的),
立即会返回hostname 端的时间。
由于,在Sun Solaris 中无法获取远端Server 上时钟信息的功能(不改变本
地Server时钟),笔者曾将此程序应用于计费服务器同时钟服务器同步监测的网管
系统中,运行稳定,获得了较好的效果。应该说RPC的应用是十分广泛的,特别是
在分布式计算领域中尤为显得重要。当然,笔者也是刚接触RPC,还有很多地方了
解的不够深刻,望广大读者多指教。
本文简单介绍了RPC(Remote Procedure Call 远程过程调用)的原理结构、特点,
及其开放给编程人员不同层次的编程接口。并且例举实例示度绾瓮ü齊pcgen 编译工
具来快速开发RPC应用。
一、 概述
在传统的编程概念中,过程是由程序员在本地编译完成,并只能局限在本地运行的一段
代码,也即其主程序和过程之间的运行关系是本地调用关系。因此这种结构在网络日益
发展的今天已无法适应实际需求。总所周知,传统过程调用模式无法充分利用网络上其
他主机的资源(如CPU、Memory等),也无法提高代码在实体间的共享程度,使得主机资
源大量浪费。
而本文要介绍的RPC编程,正是很好地解决了传统过程所存在的一系列弊端。通过RPC我
们可以充分利用非共享内存的多处理器环境(例如通过局域汪连接得多台工作站),这样
可以简便地将你的应用分布在多台工作站上,应用程序就像运行在一个多处理器的计算机
上一样。你可以方便的实现过程代码共享,提高系统资源的利用率,也可以将以大量数值
处理的操作放在处理能力较强的系统上运行,从而减轻前端机的负担。
二、 RPC的结构原理及其调用机制
如前所述RPC其实也是种C/S的编程模式,有点类似C/S Socket 编程模式,但要比它
更高一层。当我们在建立RPC服务以后,客户端的调用参数通过底层的RPC传输通道,可以
是UDP,也可以是TCP(也即TI-RPC-无关性传输),并根据传输前所提供的目的地址及RPC
上层应用程序号转至相应的RPC Application Porgramme Server ,且此时的客户端处于等
待状态,直至收到应答或Time Out超时信号。具体的流程图如F1。当服务器端获得了请求
消息,则会根据注册RPC时告诉RPC系统的例程入口地址,执行相应的操作,并将结果返回
至客户端。
F1
当一次RPC调用结束后,相应线程发送相应的信号,客户端程序才会继续运行。
当然,一台服务主机上可以有多个远程过程提供服务,那么如何来表示一个唯一存
在的远程过程呢?一个远程过程是有三个要素来唯一确定的:程序号、版本号和过程号。
程序号是用来区别一组相关的并且具有唯一过程好的远程过程。一个程序可以有一个或几
个不同的版本,而每个版本的程序都包含一系列能被远程调用的过程,通过版本的引入,
使得不同版本下的RPC能同时提供服务。每个版本都包含有许多可供远程调用的过程,每个
过程则有其唯一标示的过程号。
三、 基于RPC的应用系统开发
通过以上对RPC原理的简介后,我们再来继续讨论如何来开发基于RPC的应用系统。
一般而言在开发RPC时,我们通常分为三个步骤:
a、 定义说明客户/服务器的通信协议。
这里所说的通信协议是指定义服务过程的名称、调用参数的数据类型和返回参数的数据
类型,还包括底层传输类型(可以是UDP或TCP),当然也可以由RPC底层函数自动选择
连接类型建立TI-RPC。最简单的协议生成的方法是采用协议编译工具,常用的有Rpcgen,
我会在后面实例中详细描述其使用方法。
b、 开发客户端程序。
c、 开发服务器端程序。
开发客户端和服务器端的程序时,RPC提供了我们不同层次的开发例程调用接口。不
同层次的接口提供了对RPC不同程度控制。一般可分为5个等级的编程接口,接下来我们
分别讨论一下各层所提供的功能函数。
1、 简单层例程
简单层是面向普通RPC应用,为了快速开发RPC应用服务而设计的,他提供
了如下功能函数。
函数名 功能描述
Rpc_reg( ) 在一特定类型的传输层上注册某个过程,来作为提供服务的RPC程序
Rpc_call( ) 远程调用在指定主机上指定的过程
Rpc_Broadcast( ) 向指定类型的所有传输端口上广播一个远程过程调用请求
2、 高层例程
在这一层,程序需要在发出调用请求前先创建一个客户端句柄,或是在侦听请
求前先建立一个服务器端句柄。程序在该层可以自由的将自己的应用绑在所有的
传输端口上,它提供了如下功能函数。
函数名 功能描述
Clnt_create( ) 程序通过这个功能调用,告诉底层RPC服务器的位置及其传输类型
Clnt_create_timed( ) 定义每次尝试连接的超时最大时间
Svc_create( ) 在指定类型的传输端口上建立服务器句柄,告诉底层RPC事件过程的相应入口地址
Clnt_call() 向服务器端发出一个RPC调用请求
3、 中间层例程
中间层向程序提供更为详细的RPC控制接口,而这一层的代码变得更为复杂,
但运行也更为有效,它提供了如下功能函数。
函数名 功能描述
Clnt_tp_create( ) 在指定的传输端口上建立客户端句柄
Clnt_tp_create_timed( ) 定义最大传输时延
Svc_tp_creaet( ) 在指定的传输端口上建立服务句柄
Clnt_call( ) 向服务器端发出RPC调用请求
4、 专家层例程
这层提供了更多的一系列与传输相关的功能调用,它提供了如下功能函数。
函数名 功能描述
Clnt_tli_create( ) 在指定的传输端口上建立客户端句柄
Svc_tli_create( ) 在指定的传输端口上建立服务句柄
Rpcb_set( ) 通过调用rpcbind将RPC服务和网络地址做映射
Rpcb_unset( ) 删除rpcb_set( ) 所建的映射关系
Rpcb_getaddr( ) 调用rpcbind来犯会指定RPC服务所对应的传输地址
Svc_reg( ) 将指定的程序和版本号与相应的时间例程建起关联
Svc_ureg( ) 删除有svc_reg( ) 所建的关联
Clnt_call( ) 客户端向指定的服务器端发起RPC请求
5、 底层例程
该层提供了所有对传输选项进行控制的调用接口,它提供了如下功能函数。
函数名 功能描述
Clnt_dg_create( ) 采用无连接方式向远程过程在客户端建立客户句柄
Svc_dg_create( ) 采用无连接方式建立服务句柄
Clnt_vc_create( ) 采用面向连接的方式建立客户句柄
Svc_vc_create( ) 采用面向连接的方式建立RPC服务句柄
Clnt_call( ) 客户端向服务器端发送调用请求
四、 实例介绍
以下我将通过实例向读者介绍通过简单层RPC的实现方法。通常在此过程中我们
将使用RPC协议编译工具-Rpcgen。Rpcgen 工具用来生成远程程序接口模块,它将以RPC
语言书写的源代码进行编译,Rpc 语言在结构和语法上同C语言相似。由Rpcgen 编译生
成的C源程序可以直接用C编译器进行编译,因此整个编译工作将分为两个部分。Rpcgen
的源程序以.x结尾,通过其编译将生成如下文件:
a) 一个头文件(.h)包括服务器和客户端程序变量、常量、类型等说明。
b) 一系列的XDR例程,它可以对头文件中定义的数据类型进行处理。
c) 一个Server 端的标准程序框架。
d) 一个Client 端的标准程序框架。
当然,这些输出可以是选择性的,Rpcgen 的编译选项说明如下:
选项 功能
'-' a 生成所有的模板文件
'-' Sc 生成客户端的模板文件
'-' Ss 生成服务器端的模板文件
'-' Sm 生成Makefile 文件
(详见Solaris Rpcgen Manaul)
Rpcgen 源程序 time.x:
/* time.x: Remote time printing protocol */
program TIMEPROG {
version PRINTIMEVERS {
string PRINTIME(string) = 1;
} = 1;
} = 0x20000001;
time_proc.c源程序(服务端):
/* time_proc.c: implementation of the remote procedure "printime" */
#include <stdio.h>
#include <rpc/rpc.h> /* always needed */
#include "time.h" /* time.h will be generated by rpcgen */
#include <time.h>
/* Remote version of "printime" */
char ** printime_1(char **msg,struct svc_req *req)
{
static char * result; /* must be static! */
static char tmp_char[100];
time_t rawtime;
FILE *f;
f = fopen("/tmp/rpc_result", "a+");
if (f == (FILE *)NULL) {
strcpy(tmp_char,"Error");
result = tmp_char;;
return (&result);
}
fprintf(f, "%s\n", *msg); //used for debugging
fclose(f);
time(&rawtime);
sprintf(tmp_char,"Current time is :%s",ctime(&rawtime));
result =tmp_char;
return (&result);
}
rtime.c源代码 (客户端)
/*
* rtime.c: remote version
* of "printime.c"
*/
#include <stdio.h>
#include "time.h" /* time.h generated by rpcgen */
main(int argc, char **argv)
{
CLIENT *clnt;
char *result;
char *server;
char *message;
if (argc != 3) {
fprintf(stderr, "usage: %s host message\n", argv[0]);
exit(1);
}
server = argv[1];
message = argv[2];
/*
* Create client "handle" used for
* calling TIMEPROG on the server
* designated on the command line.
*/
clnt = clnt_create(server, TIMEPROG, PRINTIMEVERS, "visible");
if (clnt == (CLIENT *)NULL) {
/*
* Couldn't establish connection
* with server.
* Print error message and die.
*/
clnt_pcreateerror(server);
exit(1);
}
/*
* Call the remote procedure
* "printime" on the server
*/
result =*printime_1(&message,clnt);
if (result== (char *)NULL) {
/*
* An error occurred while calling
* the server.
* Print error message and die.
*/
clnt_perror(clnt, server);
exit(1);
}
/* Okay, we successfully called
* the remote procedure.
*/
if (strcmp(result,"Error") == 0) {
/*
* Server was unable to print
* the time.
* Print error message and die.
*/
fprintf(stderr, "%s: could not get the time\n",argv[0]);
exit(1);
}
printf("From the Time Server ...%s\n",result);
clnt_destroy( clnt );
exit(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 -lnsl //客户端编译 在freebsd下不要用-lnsl
$gcc time_proc.c time_svc.c -o time_server -lnsl //服务器端编译 在freebsd下不要用-lnsl
编译成功后即可在Server端运行time_server,立即将该服务绑定在rpc服务端口上提供
服务。在客户端运行./rtime hostname msg (msg 是一字符串,笔者用来测试时建立的),
立即会返回hostname 端的时间。
由于,在Sun Solaris 中无法获取远端Server 上时钟信息的功能(不改变本
地Server时钟),笔者曾将此程序应用于计费服务器同时钟服务器同步监测的网管
系统中,运行稳定,获得了较好的效果。应该说RPC的应用是十分广泛的,特别是
在分布式计算领域中尤为显得重要。当然,笔者也是刚接触RPC,还有很多地方了
解的不够深刻,望广大读者多指教。
作者:jackxiang@向东博客 专注WEB应用 构架之美 --- 构架之美,在于尽态极妍 | 应用之美,在于药到病除
地址:https://jackxiang.com/post/781/
版权所有。转载时必须以链接形式注明作者和原始出处及本声明!
最后编辑: jackxiang 编辑于2007-11-23 18:03
评论列表