远程过程调用

作者:Ed Petron

正如任何程序员所知,过程调用是一项至关重要的软件开发技术。它们为实现除最简单的程序之外的所有程序提供了必要的杠杆。远程过程调用 (RPC) 将传统过程调用的功能扩展到网络,并且在分布式系统的开发中至关重要。它们既可以用于分布式文件和数据库系统中的数据交换,也可以用于利用多处理器的能力。Linux 发行版提供了一个 RPC 版本,该版本源自 Sun Microsystems 的开放网络计算 (ONC) 组开发的 RPC 工具。

RPC 和客户端/服务器模型

如果读者不熟悉以下术语,我们将在此处定义它们,因为它们在后面的讨论中很重要

  • 调用者:调用子程序的程序

  • 被调用者:被调用者调用的子程序或过程

  • 客户端:请求连接网络服务器并从网络服务器获取服务的程序

  • 服务器:接受来自客户端的连接并向客户端提供服务的程序

调用者/被调用者关系与客户端/服务器关系之间存在直接的平行关系。使用 ONC RPC(以及我所知道的每种其他形式的 RPC),调用者始终作为客户端进程执行,而被调用者始终作为服务器进程执行。

远程过程调用机制

为了使 RPC 成功执行,必须执行以下几个步骤

  1. 调用程序必须准备任何要传递给 RPC 的输入参数。请注意,调用者和被调用者可能运行完全不同的硬件,并且某些数据类型在不同的机器体系结构中可能以不同的方式表示。因此,调用者不能简单地将 原始 数据馈送到远程过程。

  2. 调用程序必须以某种方式将其数据传递给将执行 RPC 的远程主机。在本地过程调用中,目标地址只是本地处理器上的机器地址。使用 RPC,目标过程具有机器地址和网络地址的组合。

  3. RPC 接收并操作任何输入参数,并将结果传递回调用者。

  4. 调用程序接收 RPC 结果并继续执行。

外部数据表示

正如之前指出的,RPC 可以在运行完全不同的处理器硬件的两台主机之间执行。诸如整数和浮点数之类的数据类型在不同的机器上可能具有不同的物理表示形式。例如,某些机器以低位字节优先的方式存储整数(C int),而某些机器则以低位字节最后的方式存储整数。浮点数值数据也会出现类似的问题。解决此问题的方法是采用数据交换标准。

一种这样的标准是 ONC 外部数据表示 (XDR)。XDR 本质上是一组 C 函数和宏,可以实现从机器特定的数据表示形式到相应的标准表示形式以及反之亦然的转换。它包含简单数据类型(如 int、float 和 string)的原语,并提供定义和传输更复杂数据类型(如记录、任意元素类型的数组和指针绑定结构(如链表))的功能。

大多数 XDR 函数都需要传递指向“XDR”类型结构的指针。此结构的元素之一是名为 x_op 的枚举字段。其可能的值为 XDR_ENCODEXDR_DECODEXDR_FREEXDR_ENCODE 操作指示 XDR 例程将传递的数据转换为 XDR 格式。XDR_DECODE 操作指示将 XDR 表示的数据转换回其本地表示形式。XDR_FREE 提供了一种释放内存的方法,该内存是为不再需要的变量动态分配的。有关 XDR 的更多信息,请参阅本文参考文献部分列出的信息来源。

RPC 数据流

从调用者到被调用者以及再返回的数据流如图 1 所示。调用程序作为客户端进程执行,RPC 在远程服务器上运行。客户端和网络之间以及服务器和网络之间的所有数据移动都通过 XDR 过滤器例程。原则上,可以使用任何类型的网络传输,但我们对实现细节的讨论集中在 ONC RPC 上,后者通常使用由 Internet 协议路由的传输控制协议(熟悉的 TCP/IP)或也由 Internet 协议路由的用户数据报协议(可能不太熟悉的 UDP/IP)。同样,可以使用任何类型的数据表示形式,但我们的讨论重点是 XDR,因为它是由 ONC RPC 使用的方法。

Remote Procedure Calls

图 1. RPC 数据流

网络编程理论回顾

为了完整地了解 RPC 处理,我们需要回顾一些网络编程理论。

为了使运行在不同计算机上的两个进程交换数据,需要在每个主机上形成一个 关联。关联定义为以下 5 元组:{协议, 本地地址, 本地进程, 外部地址, 外部进程}

协议 是用于在主机之间移动数据的传输机制(通常为 TCP 或 UDP)。当然,这是需要对两台主机计算机通用的部分。对于任一主机计算机,本地地址/进程 对定义了运行该进程的主机计算机上的端点。外部地址/进程 对是指连接另一端的端点。

进一步分解,术语 地址 是指分配给主机的网络地址。这通常是 Internet 协议 (IP) 地址。术语 进程 不是指实际的进程标识符(例如 Unix PID),而是指将数据传输到正确主机计算机后,将数据传输到正确进程所需的某些整数标识符。这通常称为 端口。使用端口号的原因是,对于在远程主机上运行的进程来说,知道特定服务器的 PID 是不切实际的。标准端口号分配给众所周知的服务,例如 TELNET(端口 23)和 FTP(端口 21)。

RPC 调用绑定

现在我们有了必要的理论来完成我们对 RPC 绑定过程的了解。RPC 应用程序被正式打包成一个 程序,其中包含一个或多个 过程 调用。以类似于上述端口分配的方式,RPC 程序被分配一个整数标识符,该标识符对于将调用其过程的程序是已知的。每个过程也分配一个数字,该数字也被其调用者知道。ONC RPC 使用一个名为 portmap 的程序来为 RPC 程序分配端口号。其操作如图 2 所示。当 RPC 程序 启动时,它会在同一主机上运行的 portmap 进程中注册自身。然后,portmap 进程会分配该应用程序要使用的 TCP 和/或 UDP 端口号。

Remote Procedure Calls

图 2. Portmap 操作

然后,RPC 应用程序等待并接受该端口号的连接。在调用远程过程之前,调用者 还会联系 portmap,以获取它需要调用的应用程序正在使用的相应端口号。网络连接为调用者提供了访问远程主机上正确程序的方法。通过使用 RPC 程序中的调度表来访问正确的程序。建立端口号的同一注册过程也会创建调度表。调度表由过程号索引,并包含所有 XDR 过滤器例程的地址以及实际过程的地址。

RPCGEN:协议编译器

列表 1. avg.x 的源代码

如果对支持 RPC 的机制的讨论听起来很复杂,那是因为它确实很复杂。幸运的是,通过使用协议编译器 rpcgen,可以大大简化 RPC 应用程序的开发。rpcgen 有其自己的输入语言,该语言用于声明程序、其过程以及过程的参数和返回值的数据类型。最好的说明方法是通过示例。平均过程的源代码如列表 1 所示。如果我们将此源代码存储在名为 avg.x 的文件中,并使用以下命令调用 rpcgen

rpcgen avg.x

获取列表 2 中所示的头文件 avg.h。此文件包含我们应用程序开发所需的所有函数原型和数据声明。它还将生成另外三个源文件

  1. avg_clnt.c:我们客户端(调用者)进程的存根程序

  2. avg_svc.c:我们服务器(被调用者)进程的主程序

  3. avg_xdr.c:客户端和服务器都使用的 XDR 例程

这些源文件应“按原样”使用,不得编辑。

列表 2. 头文件 avg.h

为了完成服务器端的应用程序,我们需要代码来提供正确处理输入数据所需的实际“智能”。这必须手动创建。此处提供的示例应用程序的代码如列表 3 所示。此代码从客户端获取 XDR 解码数组,并分离和平均这些值。它返回结果,然后将结果进行 XDR 编码以传输回客户端。

列表 3. 平均应用程序的服务器代码

为了完成客户端的应用程序,必须将输入数据打包成 XDR 格式,以便可以将其发送到服务器。客户端程序也是手动生成的,如列表 4 所示。列表 5 中所示的 Makefile 可用于构建应用程序。

列表 4. 平均应用程序的客户端代码

列表 5. Makefile

测试和调试应用程序

测试 RPC 应用程序的最佳方法是在同一台机器上运行客户端和服务器(调用者和被调用者)。假设您位于客户端和服务器所在的目录中,通过输入以下命令启动服务器

avg_svc &

可以使用 rpcinfo 实用程序来验证服务器是否正在运行。键入命令

$ rpcinfo -p localhost
给出以下输出
program vers proto   port
 100000    2   tcp    111  portmapper
 100000    2   udp    111  portmapper
  22855    1   udp   1221
  22855    1   tcp   1223
请注意,22855 是我们来自 avg.x 的应用程序的程序号,1 显示为版本号。由于 22855 不是注册的 RPC 应用程序,因此最右边的列为空白。如果我们将以下行添加到 /etc/rpc 文件中
avg        22855
rpcinfo 然后给出以下输出
program vers proto   port
 100000    2   tcp    111  portmapper
 100000    2   udp    111  portmapper
  22855    1   udp   1221  avg
  22855    1   tcp   1223  avg
要测试应用程序,请使用命令
$ ravg localhost $RANDOM $RANDOM $RANDOM
并返回以下值
value   = 9.196000e+03
value   = 2.871200e+04
value   = 3.198900e+04
average = 2.329900e+04
由于命令的第一个参数是运行服务器的主机的 DNS 名称,因此使用 localhost。如果您有权访问允许 RPC 连接的远程主机(在尝试之前请咨询系统管理员),则可以将服务器上传并在远程主机上运行,并且可以像以前一样运行客户端,将 localhost 替换为主机的 DNS 名称或 IP 地址。如果您的远程主机不允许 RPC 连接,您或许可以从那里运行您的客户端,将 localhost 替换为本地系统的 DNS 名称或 IP 地址。
DCE RPC 简介

ONC RPC 的实现不是唯一可用的实现。开放软件基金会开发了一套名为分布式计算环境 (DCE) 的工具,使程序员能够开发分布式应用程序。其中一个工具是 DCE RPC,它是 DCE 提供的所有其他服务的基础。它的操作与 ONC RPC 非常相似,因为它使用的组件与 ONC RPC 的组件非常相似。

应用程序接口通过接口定义语言 (IDL) 定义,该语言类似于 ONC RPC 用于定义 XDR 过滤器的语言。网络数据表示 (NDR) 用于提供硬件独立的数据表示。DCE RPC 没有像 ONC RPC 那样使用程序员定义的整数程序号来标识服务器,而是使用一个名为通用唯一标识符 (UUID) 的字符串,该字符串由一个名为 uuidgen 的程序生成。一个名为 rpcd(RPC 守护程序)的程序取代了 portmap。IDL 编译器可用于以类似于 rpcgen 的方式生成 C 头文件和客户端/服务器存根。

尽管整个 DCE 套件是商业销售和许可的,但 RPC 组件(它是所有其他服务的基础)作为免费软件提供。有关 DCE RPC 的更多信息,请参阅参考文献部分。

进一步研究

此处提供的示例应用程序当然是一个简单的应用程序,但它很好地展示了 RPC 的基本原理。可以在 Linux 的网络信息系统 (NIS) 包中找到更有趣的一组应用程序(请参阅参考文献部分)。此外,Linux 内核源代码包含 Sun 的网络文件系统 (NFS) 的实现,这是将 RPC 应用于分布式文件访问问题的绝佳示例。

除了分布式数据访问之外,RPC 还可以用于利用大多数网络上存在的未使用处理能力。《RPC 强大编程》一书(在参考文献部分列出)介绍了一个图像处理应用程序,该应用程序使用 RPC 在多个处理器上分配 CPU 密集型任务。借助 RPC,您无需花费一分钱购买额外的硬件即可提高应用程序的性能。

参考文献

Remote Procedure Calls
Ed Petron 是一位对异构计算感兴趣的计算机顾问。他拥有印第安纳大学的键盘演奏(钢琴、大键琴和管风琴)音乐学士学位和查普曼学院的计算机科学理学学士学位。他的主页,位于 http://www.leba.net/~epetron 的技术和网络计算主页,致力于 Linux、X Window 系统、异构计算和自由软件。可以通过电子邮件 epetron@wilbur.leba.net 与 Ed 联系。
加载 Disqus 评论