版权归原作者所有,如有侵权,请联系我们

[科普中国]-开放网络运算远程过程调用

科学百科
原创
科学百科为用户提供权威科普内容,打造知识科普阵地
收藏

开放网络运算远程过程调用是一种被广泛应用的远程过程调用(RPC)系统,是一种属于应用层的协议堆栈,底层为TCP/IP协议。开放网络运算(ONC)最早源自于太阳微系统(Sun),是网络文件系统计划的一部分,因此它经常也被称为Sun ONCSun RPC。现今在多数类UNIX系统上都实现了这套系统,微软公司也以Windows Services for UNIX在他们产品上提供ONC RPC的支持。2009年,太阳微系统以标准三条款的BSD许可证发布这套系统。2010年,收购了太阳微系统的甲骨文公司确认了这套软件BSD许可证的有效性与适用范围。

简介Sun Microsystems是首批为RPC支持库和RPC编译器提供商业化支持的公司之一。在20世纪80年代由Sun公司提供并支持Sun公司的网络文件系统(NFS)。由Sun和AT&T牵头,该协议被推动成为开放式网络计算的标准。它是一个轻量级的RPC系统,并可以在大多数的POSIX和类POSIX的操作系统上可用,包括了Linux,SunOS,OSX以及各种版本的BSD。它通常被称之为Sun RPC或者是ONC RPC。

ONC RPC提供了一个编译器,它接受远程过程接口的定义,并生成客户机和服务器的存根函数。这个编译器叫做rpcgen。在运行这个编译器之前,程序员必须提供接口的定义。接口定义包含了函数声明,它们按版本号分组,并由一个惟一的程序编号来标识。这个程序编号就使得客户端能够识别他们需要的接口。版本号对于那些没有更新到最新代码的客户端来说是非常重要的,它确保这个客户端仍然能够连接到一个新的服务器,只要这个服务器依然支持旧的函数接口。

在网络中传输的参数被编组成为一种被称之为XDR(eXternalDataRepresentation)的隐式序列化格式。这就确保了可以将参数发送到可能使用不同字节顺序、不同大小整数、或不同浮点或字符串表示的异构系统中。最后,Sun RPC提供了一个运行时库,它实现了支持RPC的必要协议和Socket例程。1

实例所有的程序员都必须要编写客户端代码,服务器函数,以及一个RPC接口定义。

客户端程序

#include #include #include #include #include "date.h" main(argc, argv) int argc; char **argv; { CLIENT *cl; /* rpc handle */ char *server; long *lresult; /* return from bin_date_1 */ char **sresult; /* return from str_date_1 */ if (argc != 2) { fprintf(stderr, "usage: %s hostnamen", argv[0]); exit(1); } server = argv[1]; /* get the name of the server */ /* create the client handle */ if ((cl=clnt_create(server, DATE_PROG, DATE_VERS, "netpath")) == NULL) { /* failed! */ clnt_pcreateerror(server); exit(1); } /* call the procedure bin_date */ if ((lresult=bin_date_1(NULL, cl))==NULL) { /* failed ! */ clnt_perror(cl, server); exit(1); } printf("time on %s is %ldn", server, *lresult); /* have the server convert the result to a date string */ if ((sresult=str_date_1(lresult, cl)) == NULL) { /* failed ! */ clnt_perror(cl, server); exit(1); } printf("date is %sn", *sresult); clnt_destroy(cl); /* get rid of the handle */ exit(0); }服务端程序

#include #include "date.h" /* bin_date_1 returns the system time in binary format */ long * bin_date_1() { static long timeval; /* must be static!! This value is */ /* used by rpc after bin_date_1 returns */ long time(); /* Unix time function; returns time */ timeval = time((long *)0); return &timeval; } /* str_date_1 converts a binary time into a date string */ char ** str_date_1(bintime) long *bintime; { static char *ptr; /* return value... MUST be static! */ char *ctime(); /* Unix library function that does the work */ ptr = ctime(bintime); return &ptr; }RPC接口定义

/* date.x - description of remote date service */ /* we define two procedures: */ /* bin_date_1 returns the time in binary format */ /* (seconds since Jan 1, 1970 00:00:00 GMT) */ /* str_date_1 converts a binary time to a readable date string */ program DATE_PROG { version DATE_VERS { long BIN_DATE(void) = 1; /* procedure number = 1 */ string STR_DATE(long) = 2; /* procedure number = 2 */ } = 1; } = 0x31415926;当使用rpcgen编译RPC接口定义(文件后缀为.x;例如,名为date.x的文件)时,会创建三个或四个文件。这些用于date.x示例:

当这个RPC接口定义文件date.x被rpcgen编译器编译的时候,它会生成3个或者4个文件。

date.h

包含程序的定义、版本和函数的声明。客户端和服务器函数都应该包含这个文件。

date_svc.c

实现服务器存根的代码。

date_clnt.c

实现客户端存根的代码。

date_xdr.c

包含将数据转换为XDR格式的XDR例程。如果生成此文件,则应将其编译并链接到客户端和服务器函数。

创建客户端和服务器可执行文件的第一步便是编译接口定义文件date.x。在此之后,客户端和服务器的函数都可以被编译,并与rpcgen生成的各自的存根函数相关联。

在旧的RPC版本中,我们要么使用字符串“tcp”,要么使用字符串“udp”来作为其网络传输的协议。此做法依然适用,在RPC的Linux实现中,这甚至是一个必需的做法。为了使接口能够更加灵活,UNIX System v4(SunOS v5)网络选择例程允许一个更为通用的规范。他们通过搜索一个/etc/netconfig的文件,来找到第一个满足需求的提供者。最后一个参数可以是:

“netpath”

搜索一个NETPATH环境变量,作为传输协议的首选

“circuit_n”

在NETPATH列表中找到第一个虚拟电路提供程序,“datagram_n”(在NETPATH列表中找到第一个数据报提供程序)

“visible”

在/etc/netconfig中找到第一个可见的传输提供程序

“circuit_v”

在/etc/netconfig中找到第一个可见的虚拟电路传输提供程序

“datagram_v”

在/etc/netconfig中找到第一个可视数据报传输提供程序。

远程过程调用在设计之初仅支持单一的参数,在之后的发展过程中,逐步改进以支持多个参数的传递。在一些rpcgen的版本中,依然默认支持单一的参数,例如苹果的OSX操作系统。为了能够支持多个参数,我们需要首先定义一个结构,让它包含所有的参数数据,初始化它并传递这个数据结构。

远程过程调用返回一个指向结果的指针而并非结果本身。因此,服务端的函数也必须因此而做出改动,使其可以接受一个指向一个在RPC接口定义文件中定义的值的指针来作为入参,并返回一个指向结果的指针来作为返回结果。在服务器端,指针必须指向一个静态的数据,否则,在过程的框架被释放或者调用过程返回时,该指针会指向一个未定义的区域。在客户端,这个返回指针可以让我们区分出一个失败的RPC结果(空指针)或者是一个null返回结果(间接的null指针)。

RPC 过程的名称若在 RPC 定义文件中做了定义,则会转换为小写,并在后缀加下划线,后跟一个版本号。例如,BIN_DATE将会被转成为引用函数 bin_date_1 。你的服务器端代码必须实现 bin_date_1。

运行结果

服务端

当服务器启动的时候,服务器存根函数将会在后台进程中运行(如果当你不再需要使用这个存根函数的时候,你可以通过ps的指令来找到对应的进程,并结束此进程)。存根函数会创建一个Socket并把本地任一个可用的端口绑定到此新建的Socket上。紧接着它便会调用一个svc_register的RPC库函数,通过port mapper来注册程序的版本跟编号。这个port mapper是另外一个独立的进程,在服务器启动时便会运行。它负责追踪端口号,本版号以及程序编号。在Unix System v4系统中,这个进程称之为rpcbind;在Linux,OSX以及BSD系统中,它被称之为portmap。

客户端

当我们启动客户端代码的时候,它便会利用远程系统的名称,程序编号,版本号以及传输协议来调用一个clnt_creat函数。它通过远程系统上的port mapper,在系统上找到合适的端口。

客户端紧接着调用RPC存根函数(例如以上的例子,调用bin_date_1)。这个存根函数便会向服务器端发送一条消息并等待返回结果。

服务端接收到来自客户端的消息,接着调用服务端的函数(服务端的bin_date_1函数),处理完毕之后将结果返回给客户端存根。客户端存根再把结果返回给调用的最终客户端代码。

整个过程可以参照概述图。

本词条内容贡献者为:

王慧维 - 副研究员 - 西南大学