远程过程调用 Remote Procedure Call
-------------------------------------
通过stub函数,远程的执行过程可以被本地调用
客户端拥有相同的函数接口
- 和服务器通信的接口
- 获得服务器执行结果的接口
Stub 函数的产生
没有编程语言天生支持Sun RPC
- 使用独立的预编译器rpcgen来使编程语言支持
输入
- 结构定义语言
输出
- 服务器主服务例程
- 客户端stub函数
- 头文件
- 数据传唤函数(如果需要)
接口定义语言 Interface Definition Language
rpcgen用IDL生成stub函数
定义一个PRC程序:RPC过程的集合
结构如下
type definitions
program identifier{
version version_id{
procedure list
}=value;
...
}=value;
program PROG{
version PROG1{
void PROC_A(int) = 1;
}=1;
}=0x3a3afeeb;
数据类型
常量 constants
- 将一个#define转换为整型值
- const MAXSIZE = 10
结构体 structures
- 和C语言中的结构体类似,rpcgen转换结构体定义,然后为结构的名称加一个typedef
struct intpair {int a, b};
struct intpair {int a; int b;};
typedef struct intpair intpair;
数据类型
枚举 enumerations
- 和C语言相似
enum state {BUSY=1, IDLE=2, TRANSIT=3};
联合体 unions
- 和C语言差别较大
- 结构体是一个基于不同标准数据类型的特殊声明
- 举例如下
union identifier switch (declaration) {
case_list
}
union time_s switch (int status) {
case 0: char timeval [MAXBUF];
case 1: void;
case 2: int reason;
}
数据类型
类型定义
- 和C相似
- typedef long counter;
数组
- 和C相似,但具有可变长度
long x_vals<50>代表一个最长为50的数组
指针
- 和C相似。在指针指向的数据前有个布尔值,如果指针为null,则布尔值 为FALSE
数据类型
字符串
类似声明一个长度可变的数组
string name<50> 一个最长50的字符串 string name<> 一个任意长度的字符串
布尔型
可以拥有TRUE和FALSE两种值
非透明数据
一组按位顺序存储的没有类型的数据,长度可变。
opaque extra_bytes[512];
稍后声明将会转换为C形式:
struct {
uint more_len; /* 数组长度 */
char *more_val; /* 数据使用的空间 */
};
使用SUN RPC创建例程
创建一个由RPC定义名字的程序
- 小写
- 由自定义名字,版本号和"svc"三部分组成,例如BLIP, blip_1_svc
程序的参数是一个指向在IDL指明的数据类型的指针
默认情况是每个函数只有一个参数
- 可以使用使用结构体传递多个值
- 在较新的版本中rpcgen对这个限制放宽了,但仍是作为默认情况
程序必须返回一个指向IDL指明的数据类型的指针
由于server stub需要使用返回结果,所以结果必须是静态变量
一个简单的RPC程序
由一个拥有两个函数的stand-alone程序开始
- bin_date返回系统时间,即从1970年第一秒开始的秒数
- str_date把秒数作为输入,返回一个格式化的字符串
目标
- 将bin_date和str_date移动到服务器,通过RPC来调用她们。
Stand-alone程序
#include <stdio.h>
long bin_date(void);
char *str_date(long bintime);
main(int argc, char **argv) {
long lresult; /* 从bin_date()返回的值 */
char *sresult; /* 从str_date()返回的值 */
if (argc != 1) {
fprintf(stderr, "usage: %s/n", argv[0]);
exit(1);
}
/* 调用bin_date()过程 */
lresult = bin_date();
printf("time is %ld/n", lresult);
/* 格式化输出结果 */
sresult = str_date(lresult);
printf("date is %s", sresult);
exit(0);
}
Stand-alone程序:函数
/* bin_date 以二进制的形式返回系统时间 */
long bin_date(void) {
long timeval;
long time(); /* 返回时间 */
timeval = time((long *)0);
return timeval;
}
/* str_date 转换时间从二进制到格式化字符串 */
char *str_date(long bintime) {
char *ptr;
char *ctime();
ptr = ctime(&bintime);
return ptr;
}
定义远程接口(IDL)
定义在服务器运行的两个函数:
- bin_date()没有参数,返回一个long型结果
- str_date()接受一个long型作为输入,然后返回字符串
IDL:
program DATE_PROG {
version DATE_VERS {
long BIN_DATE(void) = 1;
string STR_DATE(long) = 2
} = 1;
} = 0x31423456;
IDL的后缀名".x",编译命令 rpcgen -C date.x
产生服务器函数:rpcgen的模板
我们可以使用rpcgen使用我们定义的接口生成一份服务器端的模板
rpcgen -C -Ss date.x >server.c
生成的代码:
#include "date.h"
long *
bin_date_1_svc(void *argp, struct svc_req *rqstp)
{
static long result;
/* insert server code here */
return &result;
}
char **
str_date_1_svc(long *argp, struct svc_req *rqstp)
{
static char *result;
/* insert server code here */
return &result;
}
产生服务器函数:插入代码
现在从之前stand-alone代码中复制
long *
bin_date_1_svc(void *argp, struct svc_req *rqstp)
{
static long result;
long time();
result = time((long *)0);
return &result;
}
char **
str_date_1_svc(long *bintime, struct svc_req *rqstp)
{
static char *result;
char *ctime();
result = ctime(bintime);
return &result;
}
产生客户端函数:获得服务器名字
我们需要知道服务器的名字
- 使用getopt库函数从命令行接收的-h hostname参数。
extern char *optarg;
extern int optind;
char *server = "localhost"; /* default */
int err = 0;
while ((c = getopt(argc, argv, "h:")) != -1)
switch (c) {
case 'h':
server = optarg;
break;
case '?':
err = 1;
break;
}
/* exit if error or extra arguments */
if (err || (optind < argc)) {
fprintf(stderr, "usage: %s [-h hostname]/n", argv[0]);
exit(1);
}
产生客户端函数:添加头文件和创建客户端句柄
我们需要添加两个额外的头文件
#include <rpc/rpc.h>
#include "date.h"
使用clnt_create初始化RPC连接
CLIENT *c1;
c1 = clnt_create(server, DATE_PROG, DATE_VERS, "netpath");
程序名和版本号已经在date.h中定义过
"netpath"通过读取NETPATH环境变量来决定使用TCP还是UDP
通过联络服务器的RPC名称服务器(端口映射)来找到发送请求的端口
产生客户端函数:修改远程函数的调用
客户端对bin_date和str_date的调用发生了变化
- 给函数添加版本号
- 添加一个客户端的句柄作为参数(由clnt_create返回)
- 经常传递单个参数(如果没有的话就传递NULL)
bin_date_1(NULL, c1);
str_date_1(&value, c1);
产生客户端函数:检查RPC错误
记住:远程调用可能失败!
添加检查返回值的代码
远程函数返回指向结果的指针
如果指针为空,调用即为失败
long *lresult;
if ((lresult = bin_date_1(NULL, c1)) == NULL){
clnt_perror(c1, server);
exit(1);
}
如果bin_date_1调用成功,时间将被正确打印
产生客户端函数:检查RPC错误(2)
对于调用str_date是一样的
char **sresult;
if((sresult = str_date_1(lresult, c1)) == NULL)
{
clnt_perror(cl, server);
exit(1);
}
如果str_date_1成功,我们将看到结果
printf("date is %s", *sresult);
编译-连接-运行
生成stubs
rpcgen -C date.x
编译连接客户端和客户端stub
cc -o client client.c date_clnt.c -lnsl
编译连接服务器和服务器stub
cc -o server -DRPC_SVC_FG server.c date_svc.c -lnsl
- 注意RPC_SVC_FG使服务器作为一个前台程序运行而不是后台程序
运行服务器
$ ./server
运行客户端
$ ./client -h remus
time on localhost is 970457832
date is Sun Oct 1 23:37:12 2000
本文来自优快云博客,转载请标明出处:http://blog.youkuaiyun.com/beff2047/archive/2009/03/27/4028568.aspx