1. 简介
本文描述了ONC远程过程调用使用的报文协议。报文协议用XDR语言描述。本文假定读者熟悉XDR语言。
2. 术语
每个远程过程调用有两方面:活跃客户端发出请求,服务器端给出回答。一个网络服务是一个或多个远程程序的集合。远程程序实现一个或多个远程过程;这些过程及其参数、结果由特定的程序协议记录。服务器可能支持多个版本的远程程序以与变化的协议兼容。
例如,网络文件服务可能由两个程序组成。一个程序用于处理高级应用,如文件系统读写控制和锁。另一个程序处理低级文件输入和输出。网络文件服务的客户端将调用这两个程序。
客户端和服务器端只应用与特定的事务中;硬件实体和软件实体在不同时刻可以扮演不同角色。如,一个远程服务程序也可以是网络文件服务的客户端。
3. RPC模型
ONC RPC协议基于远程过程调用模型,类似于本地过程调用模型。在本地过程调用模型中,调用者把过程的参数放在特定的位置。接着,它把控制传给过程,过程的结 果可以从特定的位置得到,调用者继续执行。远程过程调用模型也是类似的。控制在两个进程中传递:调用者进程和服务器进程。调用者进程首先向服务器进程发出 请求报文,然后等待回答。调用报文包括过程的参数,回答报文包括过程的结果。一旦接收到应答报文,就可获取过程的结果,调用者继续执行。
在服务器端,一个进程睡眠等待调用报文的到来。当有调用报文时,服务器进程提取过程的参数,计算出结果,发送应答报文,接着等待下一条报文。
在 这种模型中,在任一时刻最多只有一个进程时活跃的。但这种模型只是一个例子。ONC RPC协议对于并发模型没有限制。例如,可以有填补的RPC调用,这样,客户端在等待服务器回答时可以更有效的工作。另一种作法是在服务器端开辟一个新的 任务处理到来的调用,这样服务器还可以处理其它的请求。
远程过程调用和本地过程调用有以下不同:
错误处理:远程过程调用中必须处理远程服务器和网络错误。
全局变量:由于服务器端不能读写客户端的地址空间,隐含参数不能作为全局变量传递。
效率:远程过程比本地过程慢。
认证:由于远程过程调用可以在不安全的网络上传递,认证就是必须的。认证防止一个实体被冒充成另一个实体。
结论:尽管有工具可以自动为特定服务产生客户端和服务器端的库文件,协议仍需仔细设计。
4. 传输和语义
RPC 协议可以在不同传输协议上实现。RPC不限制报文是如何从一个进程传递到另一个进程,但对报文规范和解释有限制。应用程序可能希望得到传输层的信息。如, 传输协议可能对RPC报文的大小有限制,也可能是面向流的,如TCP,对 报文的大小没有限制。客户端和服务器端必须在协议的选择上取得一致。
必须指出RPC没有实现任何的可靠性。应用程序必须重视RPC下的传输协议。如果RPC运行在可靠传输的基础上,如TCP,大部分工作就已完成。如果运行在不可靠协议的基础上,如UDP,它就必须自己实现RPC中未支持的超时,重传,重复检测策略。
由 于传输的独立性,RPC协议没有在日常过程中附加特定的语义。语义可以从下层的传输协议中得到。例如,RPC运行在不可靠传输协议上,如UDP。如果应用 程序超时后重传RPC调用报文,但没有得到回答,它就不能推断出程序执行次数的信息。当它收到应答后,就可推断出程序至少被执行了一次。
服 务器可能希望记住先前客户端的请求,以保证最多执行一次的语义。服务器可以通过在每个RPC报文中加上一个事务ID域来实现。事务ID域主要用于客户端实 体对调用应答的匹配上。客户端应用还可以在重传一个调用时使用相同的事务ID。服务器可以在执行完一个调用后记录这个ID,然后不再执行相同ID的调用, 以实现最多执行一次的语义。除非检测是否相同,服务器不能使用这个ID。
另一方面,如果使用可靠传输,如TCP,应用可以从应答报文中推 断程序只执行了一次,但如果它没有收到应答报文,它不能推出远程过程没有被执行。注意即使使用了面向连接的协议,如TCP,应用仍需超时和重连以处理服务 器崩溃。除了数据报和面向连接的协议,还有其它的传输协议。如,请求-应答协议如VMTP也是可以的。ONC RPC使用TCP和UDP传输协议。
5. 绑定和会晤的独立性
客 户端和服务的绑定,传输参数不是RPC协议的一部分。这个重要而必须的功能由上层软件实现。开发者可能认为RPC协议是网络的跳转子程序指令(JSR), 绑定者使JSR有用,而且绑定者使用JSR完成自身的任务。同样的,绑定软件使RPC有用,而且使用RPC完成自身的任务。
6. 认证
RPC协议提供了使客户端与服务、调用、应答报文互相识别的域。安全和读写控制机制可以建立在报文认证基础之上。它支持几个认证协议。RPC头中有一个域指出使用了哪个协议。
7. RPC协议的需求
RPC协议提供以下内容:
调用过程的唯一规范
匹配请求和应答报文的机制
调用者和服务的互相认证
除了这些需求,以下特性也需要支持:
RPC协议的错误匹配
远程程序协议版本的错误匹配
协议错误(如过程参数的错误声明)
远程认证失败的原因
目标过程没有被调用的其它原因
7.1 RPC程序和过程
RPC 调用报文有三个无符号整数域:远程程序序号、远程程序版本号、远程过程序号,它们唯一指出被调用的过程。程序序号由中心权威管理。一旦开发者有了程序序 号,就可以实现远程程序;最初版本号通常为1。调用报文的版本域指出调用者使用哪个版本的协议。版本号使在同一服务器进程中同时支持新旧协议成为可能。过 程序号指出被调用的过程。这些序号在程序协议规范中记录。例如,文件服务协议规范记录序号为5的过程使“读”,序号为12的过程是“写”。远程程序协议可 能有几个版本,RPC报文协议也可能改变。因此,调用报文同样包括RPC版本号。应答报文包含了足够的信息来识别以下错误:
RPC的远程实现不支持版本2的协议。支持的最高、最低版本号被返回。
远程系统的远程程序不可用。
远程程序不支持所要求的版本号。最高和最低远程程序版本号被返回。
请求的过程序号不存在。(这通常是客户端协议或程序错误)
远程过程的参数不可用。(这通常是客户和服务器的协议不匹配)
7.2 认证
调用者和服务的互相认证被提供为RPC协议的一部分。调用报文包含两个认证域,凭证和确认。应答报文包含一个认证域,应答确认。RPC协议规范定义这三个域为以下不透明类型(使用XDR语言)
enum auth_flavor {
AUTH_NONE = 0,
AUTH_SYS = 1,
AUTH_SHORT = 2
/*还可有更多*/
};
struct opaque_auth {
auth_flavor flavor;
opaque body<400>;
};
换而言之,任何“opaque_auth”结构是一个对于RPC不透明(不可解释)的400字节的枚举类型“auth_flavor”。认证域数据的解释和语义由独立认证协议所规范。如果认证参数被拒绝,应答报文包含拒绝的原因。
7.3 程序序号的指定
程序序号由16进制20000000(10进制536870912)指定,如下所示:
0 - 1fffffff defined by rpc@sun.com
20000000 - 3fffffff defined by user
40000000 - 5fffffff transient
60000000 - 7fffffff reserved
80000000 - 9fffffff reserved
a0000000 - bfffffff reserved
c0000000 - dfffffff reserved
e0000000 - ffffffff reserved
第一组是由 rpc@sun.com管理的序号范围,在所有站点都必须相同。第二组是特定于站点的应用的范围。这组主要被用于跟踪新程序。当一个站点开发出一个通用应用,这个应用应在第一组中指定一个序号。开发者可以向 rpc@sun.com发邮件来申请RPC程序序号。第三组应用动态产生程序序号的应用。最后一组被将来的应用所保留,不能被使用。
7.4 RPC协议的其它应用
用于远程过程调用。通常,每个调用报文都与一个应答报文匹配。
7.4.1 批处理
当客户端希望向服务器端发送大量调用报文时非常有用。批处理通常使用可靠字节流协议传输(如TCP)。批处理中,客户端不等待服务器端的回答,服务器也不向客户端发应答。批处理调用通常在合法远程过程调用操作完成后结束。
7.4.2广播远程过程调用
广播协议中,客户端向网络发送调用并等待回答。这需要基于包的协议(如UDP)作为传输协议。支持广播协议的服务器只有在成功完成调用后才发送应答,遇到错误时保持沉默。但这根据应用而不同。广播RPC的规则也适用于多点传输棗一个RPC请求被发送到多个地址。
8. RPC报文协议
用XDR语言定义的RPC报文:
enum msg_type {
CALL = 0,
REPLY = 1
};
对调用的应答报文有两种形式:接受或拒绝。
enum reply_stat {
MSG_ACCEPTED = 0,
MSG_DENIED = 1
};
假定调用报文都被接受,以下是一个远程过程调用的可能状态:
enum accept_stat {
SUCCESS = 0, /* RPC成功执行 */
PROG_UNAVAIL = 1, /* 远程不支持程序 */
PROG_MISMATCH = 2, /* 远程不支持版本号 */
PROC_UNAVAIL = 3, /* 程序不支持过程 */
GARBAGE_ARGS = 4, /* 过程不能解析参数 */
SYSTEM_ERR = 5 /* 类似内存分配的错误 */
};
远程过程调用被拒绝的原因:
enum reject_stat {
RPC_MISMATCH = 0, /* RPC version number != 2 */
AUTH_ERROR = 1 /* remote can't authenticate caller */
};
认证失败的原因:
enum auth_stat {
AUTH_OK = 0, /* success */
/*
* failed at remote end
*/
AUTH_BADCRED = 1, /* bad credential (seal broken) */
AUTH_REJECTEDCRED = 2, /* client must begin new session */
AUTH_BADVERF = 3, /* bad verifier (seal broken) */
AUTH_REJECTEDVERF = 4, /* verifier expired or replayed */
AUTH_TOOWEAK = 5, /* rejected for security reasons */
/*
* failed locally
*/
AUTH_INVALIDRESP = 6, /* bogus response verifier */
AUTH_FAILED = 7 /* reason unknown */
};
RPC报文:
所 有的报文都以事务ID,xid,开始,后跟一个联合(union)。联合的判别式是msg_type,它转换成两种报文类型之一。REPLY报文的xid 总是与初始的CALL报文相匹配。注意:xid域只被客户端用于应答报文与调用报文的匹配或服务器端检测重传;服务器端不能把xid当成序号。
struct rpc_msg {
unsigned int xid;
union switch (msg_type mtype) {
case CALL:
call_body cbody;
case REPLY:
reply_body rbody;
} body;
};
RPC调用的结构:
RPC版本2协议规范中,rpcvers必须等于2。prog、vers、proc域确定远程程序,它的版本号,以及内部的过程。这些域后是两个认证参数:cred(认证凭证)和verf(认证确认)。
认证确认的目的是确认认证凭证。注意这两项虽然分开,但逻辑上是一个实体。
struct call_body {
unsigned int rpcvers; /* must be equal to two (2) */
unsigned int prog;
unsigned int vers;
unsigned int proc;
opaque_auth cred;
opaque_auth verf;
/* procedure specific parameters start here */
};
PRC调用的应答体:
union reply_body switch (reply_stat stat) {
case MSG_ACCEPTED:
accepted_reply areply;
case MSG_DENIED:
rejected_reply rreply;
} reply;
被服务器接受的RPC调用的应答:
即 使调用被接受,仍可能有错。第一个域是由服务器产生的认证确认,以便对客户端确认自身。接着是一个联合,联合的判别式是枚举accept_stat。联合 的SUCCESS分支是特定于协议的。联合的PROG_UNAVAIL、 PROC_UNAVAIL、GARBAGE_ARGS和SYSTEM_ERR分支为void。PROG_MISMATCH分支指出服务器支持的远程程序最 高和最低版本号。
struct accepted_reply {
opaque_auth verf;
union switch (accept_stat stat) {
case SUCCESS:
opaque results[0];
/*
* procedure-specific results start here
*/
case PROG_MISMATCH:
struct {
unsigned int low;
unsigned int high;
} mismatch_info;
default:
/*
* Void. Cases include PROG_UNAVAIL, PROC_UNAVAIL,
* GARBAGE_ARGS, and SYSTEM_ERR.
*/
void;
} reply_data;
};
被服务器拒绝的RPC调用的应答:
调用被拒绝有两个理由:服务器端运行一个不兼容的RPC协议(RPC_MISMATCH)或服务器拒绝调用者(AUTH_ERROR)。若RPC版本不兼容,服务器返回所支持的RPC最高和最低版本号。若为非法认证,返回失败状态。
union rejected_reply switch (reject_stat stat) {
case RPC_MISMATCH:
struct {
unsigned int low;
unsigned int high;
} mismatch_info;
case AUTH_ERROR:
auth_stat stat;
};
9. 认证协议
如 前所述,认证参数是不透明的,但对RPC协议的其余部分是无限制的。本节定义两种标准的认证flavor。开发者可以发明新的认证类型,只要遵从 flavor号指定规则。凭证和确认的flavor指opaque_auth结构中的flavor域。flavor号,与RPC程序序号相似,被集中控 制。开发者可以向rpc2sun.com发邮件来申请新的flavor号。空认证是强制性的,它必须被实现。系统认证应尽可能地实现,许多应用使用这种认 证,实现后能增强互用性。
9.1 空认证
许多调用中,并不在意客户端的身份。这时,RPC报文的凭证、确认和应答确认的flavor是“AUTH_NONE”。未定义与“AUTH_NONE”协调的透明数据。建议透明数据长度为0。
10. 记录标志标准
当RPC 报文在字节流传输协议(如TCP)之上传输时,有必要把一个报文与另一个报文分隔开,以便检测和恢复协议错误。这被称为记录标志(RM)。一个RPC报文 与一个RM记录相匹配。一个记录由一个或多个片段组成。一个记录片段是四字节头加0到1个字节的片段数据。字节被编码成无符号二进制数,字节顺序从高到 低。数字有两个值:一个布尔型值表示这是否是记录的最后片段(若是,为1);一个31位无符号数,表示片段数据的长度。布尔型为头的高位。(注意这种记录 规则不包含在SDR标准格式中)
11. RPC语言
如同有必要用正式语言描述XDR数据类型一样,也有必要用正式语言描述操作这些XDR数据类型的过程。RPC语言是XDR语言的控制扩展,以下是这种语言的精华。
11.1 RPC语言的服务例子
ping程序的例子:
program PING_PROG {
/*
* Latest and greatest version
*/
version PING_VERS_PINGBACK {
void
PINGPROC_NULL(void) = 0;
/*
* Ping the client, return the round-trip time
* (in microseconds). Returns -1 if the operation
* timed out.
*/
int
PINGPROC_PINGBACK(void) = 1;
} = 2;
/*
* Original version
*/
version PING_VERS_ORIG {
void
PINGPROC_NULL(void) = 0;
} = 1;
} = 1;
const PING_VERS = 2; /* latest version */
第 一个版本是带有两个过程PINGPROC_NULL和PINGPROC_PINGBACK的PING_VERS_PINGBACK。 PINGPROC_NULL不要参数,不返回结果,它在计算客户端与服务器之间的往返次数时有用。约定,任何RPC协议的0号过程应有相同的语义,不需要 任何类型的认证。第二个过程由客户端使用,让服务器ping客户端,返回操作所用的时间。下一版本,PING_VERS_ORIG,是协议的最初版本,不 包括PINGPROC_PINGBACK过程。它与旧的客户端程序兼容。
11.2 RPC语言规范
RPC语言与XDR语言一样在RFC1014中定义,除了一下不同。
program-def:
"program" identifier "{"
version-def
version-def *
"}" "=" constant ";"
version-def:
"version" identifier "{"
procedure-def
procedure-def *
"}" "=" constant ";"
procedure-def:
type-specifier identifier "(" type-specifier
("," type-specifier )* ")" "=" constant ";"
11.3 句法的注意事项
有两个保留字:“program”和“version”。
一个程序定义中不能出现两次版本名或版本号。
在一个版本的定义中,过程名称至多只能出现一次。
程序标识与常量和类型标识在同一空间中。
只有无符号常数才能被附值给程序,版本和过程。