Sun RPC 编程简介 (1)
- 要害字 RPCSocketTI-RPCTCPUDPC/SONC 摘要 本文简单介绍了RPC(RemoteProcedureCall远程过程调用)的原理结构、特点, 及其开放给编程人员不同层次的编程接口。并且例举实例示范如何通过Rpcgen编译工 具来快速开发RPC应用。 一、 概述 在传统的编程概念中,过程是由程序员在本地编译完成,并只能局限在本地运行的一段 代码,也即其主程序和过程之间的运行关系是本地调用关系。因此这种结构
要害字
RPCSocketTI-RPCTCPUDPC/SONC
摘要
本文简单介绍了RPC(RemoteProcedureCall远程过程调用)的原理结构、特点,
及其开放给编程人员不同层次的编程接口。并且例举实例示范如何通过Rpcgen编译工
具来快速开发RPC应用。
一、 概述
在传统的编程概念中,过程是由程序员在本地编译完成,并只能局限在本地运行的一段
代码,也即其主程序和过程之间的运行关系是本地调用关系。因此这种结构在网络日益
发展的今天已无法适应实际需求。总所周知,传统过程调用模式无法充分利用网络上其
他主机的资源(如CPU、Memory等),也无法提高代码在实体间的共享程度,使得主机资
源大量浪费。
而本文要介绍的RPC编程,正是很好地解决了传统过程所存在的一系列弊端。通过RPC我
们可以充分利用非共享内存的多处理器环境(例如通过局域汪连接得多台工作站),这样
可以简便地将你的应用分布在多台工作站上,应用程序就像运行在一个多处理器的计算机
上一样。你可以方便的实现过程代码共享,提高系统资源的利用率,也可以将以大量数值
处理的操作放在处理能力较强的系统上运行,从而减轻前端机的负担。
二、 RPC的结构原理及其调用机制
如前所述RPC其实也是种C/S的编程模式,有点类似C/SSocket编程模式,但要比它
更高一层。当我们在建立RPC服务以后,客户端的调用参数通过底层的RPC传输通道,可以
是UDP,也可以是TCP(也即TI-RPC-无关性传输),并根据传输前所提供的目的地址及RPC
上层应用程序号转至相应的RPCApplicationPorgrammeServer,且此时的客户端处于等
待状态,直至收到应答或TimeOut超时信号。具体的流程图如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文件
(详见SolarisRpcgenManaul)
Rpcgen源程序time.x:
/*time.x:Remotetimeprintingprotocol*/
programTIMEPROG{
versionPRINTIMEVERS{
stringPRINTIME(string)=1;
}=1;
}=0x20000001;
time_proc.c源程序
/*time_proc.c:implementationoftheremoteprocedure"printime"*/
#include%26lt;stdio.h%26gt;
#include%26lt;rpc/rpc.h%26gt;/*alwaysneeded*/
#include"time.h" /*time.hwillbegeneratedbyrpcgen*/
#include%26lt;time.h%26gt;
/*Remoteversionof"printime"*/
char**printime_1(char**msg,structsvc_req*req)
{
staticchar*result;/*mustbestatic!*/
staticchartmp_char[100];
time_trawtime;
FILE*f;
f=fopen("/tmp/rpc_result","a+");
if(f==(FILE*)NULL){
strcpy(tmp_char,"Error");
result=tmp_char;;
return(%26amp;result);
}
fprintf(f,"%s\n",*msg); //usedfordebugging
fclose(f);
time(%26amp;rawtime);
sprintf(tmp_char,"Currenttimeis:%s",ctime(%26amp;rawtime));
result=tmp_char;
return(%26amp;result);
}
rtime.c源代码
/*
*rtime.c:remoteversion
*of"printime.c"
*/
#include%26lt;stdio.h%26gt;
#include"time.h"/*time.hgeneratedbyrpcgen*/
main(intargc,char**argv)
{
CLIENT*clnt;
char*result;
char*server;
char*message;
if(argc!=3){
fprintf(stderr,"usage:%shostmessage\n",argv[0]);
exit(1);
}
server=argv[1];
message=argv[2];
/*
*Createclient"handle"usedfor
*callingTIMEPROGontheserver
*designatedonthecommandline.
*/
clnt=clnt_create(server,TIMEPROG,PRINTIMEVERS,"visible");
if(clnt==(CLIENT*)NULL){
/*
*Couldn'testablishconnection
*withserver.
*Printerrormessageanddie.
*/
clnt_pcreateerror(server);
exit(1);
}
/*
*Calltheremoteprocedure
*"printime"ontheserver
*/
result=*printime_1(%26amp;message,clnt);
if(result==(char*)NULL){
/*
*Anerroroccurredwhilecalling
*theserver.
*Printerrormessageanddie.
*/
clnt_perror(clnt,server);
exit(1);
}
/*Okay,wesuccessfullycalled
*theremoteprocedure.
*/
if(strcmp(result,"Error")==0){
/*
*Serverwasunabletoprint
*thetime.
*Printerrormessageanddie.
*/
fprintf(stderr,"%s:couldnotgetthetime\n",argv[0]);
exit(1);
}
printf("FromtheTimeServer...%s\n",result);
clnt_destroy(clnt);
exit(0);
}
有了以上的三段代码后,就可用rpcgen编译工具进行RPC协议编译,命令如下
$rpcgentime.x
rpcgen会自动生成time.h、time_svc.c、time_clnt.c
再用系统提供的gcc进行C的编译,命令如下
$gccrtime.ctime_clnt.c-ortime-lnsl //客户端编译
$gcctime_proc.ctime_svc.c-otime_server-lnsl //服务器端编译
编译成功后即可在Server端运行time_server,立即将该服务绑定在rpc服务端口上提供
服务。在客户端运行./rdatehostnamemsg(msg是一字符串,笔者用来测试时建立的),
立即会返回hostname端的时间。
由于,在SunSolaris中无法获取远端Server上时钟信息的功能(不改变本
地Server时钟),笔者曾将此程序应用于计费服务器同时钟服务器同步监测的网管
系统中,运行稳定,获得了较好的效果。应该说RPC的应用是十分广泛的,非凡是
在分布式计算领域中尤为显得重要。当然,笔者也是刚接触RPC,还有很多地方了
解的不够深刻,望广大读者多指教。
参考文献:
《SUNSolaris8ONC+Dev》
RPCSocketTI-RPCTCPUDPC/SONC
摘要
本文简单介绍了RPC(RemoteProcedureCall远程过程调用)的原理结构、特点,
及其开放给编程人员不同层次的编程接口。并且例举实例示范如何通过Rpcgen编译工
具来快速开发RPC应用。
一、 概述
在传统的编程概念中,过程是由程序员在本地编译完成,并只能局限在本地运行的一段
代码,也即其主程序和过程之间的运行关系是本地调用关系。因此这种结构在网络日益
发展的今天已无法适应实际需求。总所周知,传统过程调用模式无法充分利用网络上其
他主机的资源(如CPU、Memory等),也无法提高代码在实体间的共享程度,使得主机资
源大量浪费。
而本文要介绍的RPC编程,正是很好地解决了传统过程所存在的一系列弊端。通过RPC我
们可以充分利用非共享内存的多处理器环境(例如通过局域汪连接得多台工作站),这样
可以简便地将你的应用分布在多台工作站上,应用程序就像运行在一个多处理器的计算机
上一样。你可以方便的实现过程代码共享,提高系统资源的利用率,也可以将以大量数值
处理的操作放在处理能力较强的系统上运行,从而减轻前端机的负担。
二、 RPC的结构原理及其调用机制
如前所述RPC其实也是种C/S的编程模式,有点类似C/SSocket编程模式,但要比它
更高一层。当我们在建立RPC服务以后,客户端的调用参数通过底层的RPC传输通道,可以
是UDP,也可以是TCP(也即TI-RPC-无关性传输),并根据传输前所提供的目的地址及RPC
上层应用程序号转至相应的RPCApplicationPorgrammeServer,且此时的客户端处于等
待状态,直至收到应答或TimeOut超时信号。具体的流程图如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文件
(详见SolarisRpcgenManaul)
Rpcgen源程序time.x:
/*time.x:Remotetimeprintingprotocol*/
programTIMEPROG{
versionPRINTIMEVERS{
stringPRINTIME(string)=1;
}=1;
}=0x20000001;
time_proc.c源程序
/*time_proc.c:implementationoftheremoteprocedure"printime"*/
#include%26lt;stdio.h%26gt;
#include%26lt;rpc/rpc.h%26gt;/*alwaysneeded*/
#include"time.h" /*time.hwillbegeneratedbyrpcgen*/
#include%26lt;time.h%26gt;
/*Remoteversionof"printime"*/
char**printime_1(char**msg,structsvc_req*req)
{
staticchar*result;/*mustbestatic!*/
staticchartmp_char[100];
time_trawtime;
FILE*f;
f=fopen("/tmp/rpc_result","a+");
if(f==(FILE*)NULL){
strcpy(tmp_char,"Error");
result=tmp_char;;
return(%26amp;result);
}
fprintf(f,"%s\n",*msg); //usedfordebugging
fclose(f);
time(%26amp;rawtime);
sprintf(tmp_char,"Currenttimeis:%s",ctime(%26amp;rawtime));
result=tmp_char;
return(%26amp;result);
}
rtime.c源代码
/*
*rtime.c:remoteversion
*of"printime.c"
*/
#include%26lt;stdio.h%26gt;
#include"time.h"/*time.hgeneratedbyrpcgen*/
main(intargc,char**argv)
{
CLIENT*clnt;
char*result;
char*server;
char*message;
if(argc!=3){
fprintf(stderr,"usage:%shostmessage\n",argv[0]);
exit(1);
}
server=argv[1];
message=argv[2];
/*
*Createclient"handle"usedfor
*callingTIMEPROGontheserver
*designatedonthecommandline.
*/
clnt=clnt_create(server,TIMEPROG,PRINTIMEVERS,"visible");
if(clnt==(CLIENT*)NULL){
/*
*Couldn'testablishconnection
*withserver.
*Printerrormessageanddie.
*/
clnt_pcreateerror(server);
exit(1);
}
/*
*Calltheremoteprocedure
*"printime"ontheserver
*/
result=*printime_1(%26amp;message,clnt);
if(result==(char*)NULL){
/*
*Anerroroccurredwhilecalling
*theserver.
*Printerrormessageanddie.
*/
clnt_perror(clnt,server);
exit(1);
}
/*Okay,wesuccessfullycalled
*theremoteprocedure.
*/
if(strcmp(result,"Error")==0){
/*
*Serverwasunabletoprint
*thetime.
*Printerrormessageanddie.
*/
fprintf(stderr,"%s:couldnotgetthetime\n",argv[0]);
exit(1);
}
printf("FromtheTimeServer...%s\n",result);
clnt_destroy(clnt);
exit(0);
}
有了以上的三段代码后,就可用rpcgen编译工具进行RPC协议编译,命令如下
$rpcgentime.x
rpcgen会自动生成time.h、time_svc.c、time_clnt.c
再用系统提供的gcc进行C的编译,命令如下
$gccrtime.ctime_clnt.c-ortime-lnsl //客户端编译
$gcctime_proc.ctime_svc.c-otime_server-lnsl //服务器端编译
编译成功后即可在Server端运行time_server,立即将该服务绑定在rpc服务端口上提供
服务。在客户端运行./rdatehostnamemsg(msg是一字符串,笔者用来测试时建立的),
立即会返回hostname端的时间。
由于,在SunSolaris中无法获取远端Server上时钟信息的功能(不改变本
地Server时钟),笔者曾将此程序应用于计费服务器同时钟服务器同步监测的网管
系统中,运行稳定,获得了较好的效果。应该说RPC的应用是十分广泛的,非凡是
在分布式计算领域中尤为显得重要。当然,笔者也是刚接触RPC,还有很多地方了
解的不够深刻,望广大读者多指教。
参考文献:
《SUNSolaris8ONC+Dev》





