远程过程调用RPC(二)
--译文
原文地址:https://www.cs.rutgers.edu/~pxk/417/notes/03-rpc.html
第一代远程过程调用
ONC RPC
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(eXternal Data Representation)的隐式序列化格式。这就确保了可以将参数发送到可能使用不同字节顺序、不同大小整数、或不同浮点或字符串表示的异构系统中。最后,Sun RPC提供了一个运行时库,它实现了支持RPC的必要协议和Socket例程。
所有的程序员都必须要编写客户端代码,服务器函数,以及一个RPC接口定义。具体让我们来看看以下的C语言代码例子。
客户端程序:(client.c)

1 #include <stdlib.h> 2 #include <stdio.h> 3 #include <rpc/rpc.h> 4 #include <netconfig.h> 5 #include "date.h" 6 7 main(argc, argv) 8 int argc; 9 char **argv; 10 { 11 CLIENT *cl; /* rpc handle */ 12 char *server; 13 long *lresult; /* return from bin_date_1 */ 14 char **sresult; /* return from str_date_1 */ 15 16 if (argc != 2) { 17 fprintf(stderr, "usage: %s hostname\n", argv[0]); 18 exit(1); 19 } 20 server = argv[1]; /* get the name of the server */ 21 22 /* create the client handle */ 23 if ((cl=clnt_create(server, DATE_PROG, 24 DATE_VERS, "netpath")) == NULL) { 25 /* failed! */ 26 clnt_pcreateerror(server); 27 exit(1); 28 } 29 30 /* call the procedure bin_date */ 31 if ((lresult=bin_date_1(NULL, cl))==NULL) { 32 /* failed ! */ 33 clnt_perror(cl, server); 34 exit(1); 35 } 36 printf("time on %s is %ld\n", server, *lresult); 37 38 /* have the server convert the result to a date string */ 39 if ((sresult=str_date_1(lresult, cl)) == NULL) { 40 /* failed ! */ 41 clnt_perror(cl, server); 42 exit(1); 43 } 44 45 printf("date is %s\n", *sresult); 46 clnt_destroy(cl); /* get rid of the handle */ 47 exit(0); 48 }
服务端程序:(server.c)

1 #include <rpc/rpc.h> 2 #include "date.h" 3 4 /* bin_date_1 returns the system time in binary format */ 5 6 long * 7 bin_date_1() 8 { 9 static long timeval; /* must be static!! This value is */ 10 /* used by rpc after bin_date_1 returns */ 11 long time(); /* Unix time function; returns time */ 12 13 timeval = time((long *)0); 14 return &timeval; 15 } 16 17 /* str_date_1 converts a binary time into a date string */ 18 19 char ** 20 str_date_1(bintime) 21 long *bintime; 22 { 23 static char *ptr; /* return value... MUST be static! */ 24 char *ctime(); /* Unix library function that does the work */ 25 ptr = ctime(bintime); 26 return &ptr; 27 }
RPC接口定义:(date.x)

1 /* date.x - description of remote date service */ 2 3 /* we define two procedures: */ 4 /* bin_date_1 returns the time in binary format */ 5 /* (seconds since Jan 1, 1970 00:00:00 GMT) */ 6 /* str_date_1 converts a binary time to a readable date string */ 7 8 program DATE_PROG { 9 version DATE_VERS { 10 long BIN_DATE(void) = 1; /* procedure number = 1 */ 11 string STR_DATE(long) = 2; /* procedure number = 2 */ 12 } = 1; 13 } = 0x31415926;
当这个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。
What happens when we run the program?
服务端
当服务器启动的时候,服务器存根函数将会在后台进程中运行(如果当你不再需要使用这个存根函数的时候,你可以通过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函数),处理完毕之后将结果返回给客户端存根。客户端存根再把结果返回给调用的最终客户端代码。
整个过程可以参见下图图3所示:
图3:Function Lookup in ONC RPC
分布式环境下的RPC (DEC RPC)
分布式计算环境(DCE)是由开放软件基金会(Open Software Fundation)设计的一组组件,用于为分布式应用程序和分布式环境提供支持。在与X/Open合并之后,这个组织成为The Open Group。DCE提供的组件包括:分布式文件服务、时间服务、目录、服务和其他几个组件。这里,我们感兴趣的是DCE的远程过程调用。它与Sun RPC非常相似。接口是用一个接口定义语言编写的,称为接口定义符号(IDN),它与函数原型定义非常的类似。
Sun RPC的一个不足之处在于用一个32位的数字来唯一标识一个服务器的标识。尽管这个空间可以表示的范围比在Socket下的16位数字空间要大得多,但它依然无法满足唯一性的要求。DCE RPC通过不让程序员来“想”这个数字来解决这个问题。在编写应用程序的时候,第一步我们要做的便是使用uuidgen程序来获得一个唯一ID。该程序生成一个包含接口ID的IDN原型文件,并保证此ID不再被其他程序使用。它是一个128位的值,包含一个encoded的位置代码和创建时间。然后用户编辑原型文件,填充远程过程声明。
在这一步之后,使用一个类似于rpcgen的IDN编译器---dceidl,来生成头部,客户存根函数以及服务器存根函数。
Sun RPC的另一个缺陷是客户端必须知道服务器所在的机器。然后,它可以向该机器上的RPC名称服务器请求对应于它希望访问的程序号的端口号。DCE支持将多台机器组织成称为cell的管理实体。每台机器都知道如何与负责维护cell服务信息的机器通信:cell directory server。
对于Sun RPC,服务器只向本地名称服务器(portmapper)来注册它的{程序号到端口映射}。在DCE下,服务器在本地机器上使用RPC守护进程(名称服务器)来注册它的端点(端口),并将它的程序名称注册到它的cell目录服务器上的机器映射。当客户端想要与RPC服务器建立通信的时候,它首先要求它的cell directory server定位服务器所在的机器。然后,客户端与该机器上的RPC守护进程进行对话,以获取服务器进程的端口号。DCE也支持更为复杂的跨cell搜索的问题。
DCE RPC定义了一种称之为NDR (Network Data Representation)的数据格式用于对网络消息进行编码来封送信息。与用一个单一的规范来表示不同的数据类型不同,NDR 支持多规范(multi-canonical)格式。它允许客户端来自由选择使用哪一种格式,理想的情况是,它不需要将它从本地类型来进行转换。如果这不同于服务器的本地数据表示,服务器仍然需要进行转换,但多规范格式可以避免当客户端和服务器都共享相同的本地格式的情况下转换为其他外部格式。例如,在一个规定了使用大端字节序网络数据格式的情况下,客户端和服务器都只支持小端字节序,那么客户端必须将每个数据从小端字节序转为大端字节序,然后从客户端发送给服务端,而当服务器接受到消息后,又将每个数据转回小端字节序。多规范网络数据表示,将允许客户端向网络中发送网络消息包含小端字节序格式的数据。
图4:Function Lookup in DCE RPC
下一篇将继续,介绍第二代的RPC系统。
我要小额赞助,鼓励作者写出更好的文章