Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1320686
  • 博文数量: 175
  • 博客积分: 2743
  • 博客等级: 少校
  • 技术积分: 4024
  • 用 户 组: 普通用户
  • 注册时间: 2010-12-30 01:41
文章分类

全部博文(175)

文章存档

2015年(1)

2013年(53)

2012年(71)

2011年(50)

分类: LINUX

2011-11-19 17:24:42

什么是远程过程调用 RPC(Remote Procedure Call)? 你可能对这个概念有点陌生, 而你可能非常熟悉 NFS, 是的,
NFS 就是基于 RPC 的. 为了理解远程过程调用,我们先来看一下过程调用。

所谓过程调用,就是将控制从一个过程 A 传递到另一个过程 B, 返回时过程 B 将控制进程交给过程 A。目前大多数系统
中, 调用者和被调用者都在给定主机系统中的一个进程中, 它们是在生成可执行文件时由链接器连接起来的, 这类过程调用称
为本地过程调用。

远程过程调用(RPC)指的是由本地系统上的进程激活远程系统上的进程, 我们将此称为过程调用是因为它对程序员来说表现
为常规过程调用。处理远程过程调用的进程有两个, 一个是本地客户进程, 一个是远程服务器进程。对本地进程来说, 远程过
程调用表现这对客户进程的控制, 然后由客户进程生成一个消息, 通过网络系统调用发往远程服务器。网络信息中包括过程调
用所需要的参数, 远程服务器接到消息后调用相应过程, 然后将结果通过网络发回客户进程, 再由客户进程将结果返回给调用
进程。因此, 远程系统调用对调用者表现为本地过程调用, 但实际上是调用了远程系统上的过程。


二、远程过程调用模型

本地过程调用: 一个传统程序由一个或多个过程组成。它们往往按照一种调用等级来安排。如下图所示:


远程过程调用: 使用了和传统过程一样的抽象, 只是它允许一个过程的边界跨越两台计算机。如下图所示:


三、远程过程和本地过程的对比

首先, 网络延时会使一个远程过程的开销远远比本地过程要大
其次, 传统的过程调用因为被调用过程和调用过程运行在同一块内存空间上, 可以在过程间传递指针。而远程过程不能够将
指针作为参数, 因为远程过程与调用者运行在完全不同的地址空间中。
再次, 因为一个远程调用不能共享调用者的环境, 所以它就无法直接访问调用者的 I/O 描述符或操作系统功能。


四、远程过程调用的几种版本
(1) Sun RPC (UDP, TCP)
(2) Xerox Courier (SPP)
(3) Apollo RPC (UDP, DDS)

其中 Sun RPC 可用于面向连接或非面向连接的协议; Xerox Courier 仅用于面向连接的协议; Apollo RPC 仅用于非连接的协议


五、如何编写远程过程调用程序

为了将一个传统的程序改写成 RPC 程序, 我们要在程序里加入另外一些代码, 这个过程称作 stub 过程。我们可以想象一
个传统程序, 它的一个过程被转移到一个远程机器中。在远程过程一端, stub 过程取代了调用者。这样 stub 实现了远程过
程调用所需要的所有通信。因为 stub 与原来的调用使用了一样的接口, 因此增加这些 stub 过程既不需要更改原来的调用过
程, 也不要求更改原来的被调用过程。如下图所示:




六、示例
    此示例在 Ubuntu 8.04 + gcc 4.2.3 下编译运行通过。

    远程过程调用示例(点击下载)

[转载]RPC协议2规范
信息来源:邪恶八进制信息安全团队

RPC协议2:这个协议是一个够年头的协议

本文介绍用于ONC RPC协议规范。此协议使用XDR语言进行描述,并文不打算描述具体的使用细节而只介绍RPC协议本身。

ONC RPC是基于RPC调用模型,此模型和本地过程调用(LPC)类似。对于LPC而言,调用方只需要将参加放入一些固定的地址,如寄存器,然后将程序的控制 权转交给另一个程序,最后再由那个程序返回即可。RCP与之类似,RCP在调用一个远程过程后,自己进行等待状态,传往远程过程的参数包括过程参数,返回 参数包括执行结果。当收到包括执行结果的消息后,本地进程从消息中取得结果,调用进程重新开始执行。在服务器一方,有一个程序在等待调用,当有一个调用到 达时,服务器进程取得进程参数,计算结果,然后返回结果。

在上面的模型中,在某一个时刻只有一个进程是活动的。ONC RCP没有规定如何处理并发。调用可以同步的也可以是异步的。服务器可以创建一个线程来接收用户请求,也可以自己来接收用户请求。下面是RCP不同于LPC的几点:

1. 错误处理:对于远程服务器和网络失败必须进行处理;
2. 全局变量:因为远程服务不可能访问本地的变量,因此不能传送全局变量;
3. 性能:RPC通常比LPC要慢许多;
4. 认证:因为RPC通常要经过网络,因此必须进行认证。

虽然有现成的工具可以自动生成相关的服务器和客户方,但是协议本身仍然需要仔细设计。

RPC可由不同的传输协议实现,但RPC依然需要获得传输层的信息(这一过程本文不涉及)。这是因为有时候,传输层会限制信息的长度,同时通信双方 必须遵守同一个通信协议。RPC不会试图实现任何可靠性,应用程序应该注意相关的可靠性问题。如果RPC在TCP上运行,一切好说。TCP代替应用程序做 一些工作,但如果RPC运行在UDP上,应用程序必须注意超时,重传,多次请求检查等工作。因为传输是独立的,因此RPC必须从相关的传输层上获得是否正 确执行的信息。对于UDP来讲,如果应用程序没有得到响应,它必须重新请求;重新请求后得到响应,应用程序只能推断出服务器至少执行了一次请求。通过对请 求加上编号,可以使得服务器不对重复的请求加以响应。因此我们可以确定在收到确定后,服务器至多执行了一次请求。这里需要注意的一点是:客户的请求的ID 号只能用于和服务器内的ID号进行相等比较,不能进行其它操作。
即使使用了TCP,在没有得到响应时,我们也不能确定服务器是不是一定执行了相关的操作。我们还需要使用超时机制以保证服务器没有失败。RPC可以使用其 它的协议,如VMTP。但本文中我们以TCP作为例子。RPC协议本身不包括绑定,这是由高层协议来完成的。RPC必须提供:

1. 被调用过程必须是唯一的;
2. 必须提供调用消息和响应消息的配对机制;
3. 必须提供认证机制;

除上面的要求外还必须提供对下面问题的检测机制:

1. RPC不匹配;
2. 版本号不匹配;
3. 协议错误,如参数错误;
4. 必须提供认证错误的原因;
5. 提供其它错误信息。

RCP调用有三个无符号整数域:远程程序号,程序版本号和远程过程号。程序号必须统一管理 () 。在拥有一个程序号后,我们就可以实现自己的远程程序了。第一个实现通常以版本1来标记,这个版本号用于标记我们应该使用何种版本的程序。过程号用于标记 被调用的程序。RCP本身可以变化,RCP消息协议也可以变化。因此消息本身也有RPC版本号,它通常和这里描述的两个版本号之一一致。返回消息必须提供 信息以足以确定下面错误:

1. 远程实现不支持版本2,返回可以支持的最高和最低版本号;
2. 远程程序在远程机上不可用;
3. 远程程序不支持请求的版本号,返回支持的最高和最低版本号;
4. 请求的过程不存在;
5. 参数错误

认证机制要求请求消息有两个认证域:证书和确认。响应消息有一个域:响应确认。RPC对这三个域统一定义如下:

enum auth_flavor {
AUTH_NONE = 0,
AUTH_SYS = 1,
AUTH_SHORT = 2
/* and more to be defined */
};

struct opaque_auth {
auth_flavor flavor;
opaque body<400>;
};

对于认证结构语义的解释不在本协议规定之列。如果认证失败,必须提供失败原因。程序号成组给出,下面是一些规定:

0 - 1fffffff 由rpc@sun.com定义
20000000 - 3fffffff 用户定义
40000000 - 5fffffff 临时
60000000 - 7fffffff reserved
80000000 - 9fffffff reserved
a0000000 - bfffffff reserved
c0000000 - dfffffff reserved
e0000000 - ffffffff reserved

其中第一组中要求的号码要求所有的RPC过程必须遵守,即同一个号必须提供相同的功能。第二组主要用于调试。第三组是由程序临时产生的号码。其它号 码保留,不得使用。用户可以向服务器发送一组请求,称为批处理。批处理时用户不用等待服务器返回,而是使用一个特定的请求获得这一组请求的响应。批处理时 通常使用TCP。而UDP多使用于广播式RPC,对于支持广播的服务器,它们在成功时返回确定,在失败时什么也不返回(此规定可根据实现不同而不用加以遵 守)。下面我们来定义RPC消息:

enum msg_type {
CALL = 0,
REPLY = 1
};

当一个消息接收到时,下面是调用远程过程调用的状态:

enum accept_stat {
SUCCESS = 0, /* RPC executed successfully */
PROG_UNAVAIL = 1, /* remote hasn't exported program */
PROG_MISMATCH = 2, /* remote can't support version # */
PROC_UNAVAIL = 3, /* program can't support procedure */
GARBAGE_ARGS = 4, /* procedure can't decode params */
SYSTEM_ERR = 5 /* errors like memory allocation failure */
};

调用消息被拒绝的原因如下:

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 */
};

消息体格式如下,其中XID是消息号,请求消息和返回消息的消息号必须一致。

struct rpc_msg {
unsigned int xid;
union switch (msg_type mtype) {
case CALL:
call_body cbody;
case REPLY:
reply_body rbody;
} body;
};

RPC的消息体,版本号为2:

struct call_body {
unsigned int rpcvers; /* must be equal to two (2) */

//RPC协议2......?
unsigned int prog;
unsigned int vers;
unsigned int proc;
opaque_auth cred;
opaque_auth verf;
/* procedure specific parameters start here */
};

虽然cred和verf是两个数据域,但我们通常将它们做一个处理。而响应消息格式如下:

union reply_body switch (reply_stat stat) {
case MSG_ACCEPTED:
accepted_reply areply;
case MSG_DENIED:
rejected_reply rreply;
} reply;

如果服务器接收这个请求,必须返回一个认证域以使自己为客户认证:

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;
};

服务器可以因为版本原因或认证原因拒绝请求,下面是拒绝消息的格式:

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;
};

认证虽然对于RPC是透明的,可是RPC必须有两种认证必须实现。任何一种新的认证机制必须有一种独立的认证号,这个号是统一管理的,用户可以通过 获得。其中NULL认证必须实现,我们推荐实现系统认证。NULL认证其实就是不认证,这里推荐相关的数据域长度为0。

RPC在其它通信协议之上,为了保证一个消息和另一消息分离并能够从错误中进行恢复,我们规定了一个消息片断称为RM,一个消息在一个RM中。一个RM中可以有多个记录片断,每个片断除四个字节的头外,还有一个0到2的31次方减1的数据域。
下面我们来描述一下RPC语言,这是对XDR的一个扩展。下面是一个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 */

上程序中包括了三个小程序。通过上面的例子,我们可以看出RPC语言和XDR语言很象,不过就加了一个program-def而已。

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 ";"

下面是一些基本的规定:

不使用program和version作为变量名;在一个域中版本名,过程名,版本号,过程号不能出现两次;程序标记在一个域中是常量,只能使用未 使用的常量可被指定到程序,版本和过程。最后我们来看看系统认证。如果要使用系统认证,它的标识为AUTH_SYS。证书由下面的结构表示:

struct authsys_parms {
unsigned int stamp;
string machinename<255>;
unsigned int uid;
unsigned int gid;
unsigned int gids<16>;
};

stamp是一个ID号由调用机产生。machinename是调用机的机器名,uid和gid是用户ID和组ID,gids是包括此用户的组的集合。一般verf指定为AUTH_NONE。这个证书只在一个特定的域内有效(此域是由机器名,用户ID和组ID组成的)。

从服务器接收到的Verf的认证号可以是AUTH_NONE或AUTH_SHORT。在接收到后者时,返回的是一个新的认证结构。这个新的结构可以 取代原来的AUTH_SYS结构而被传送到服务器。这样客户可以传送比较少的数据而获得认证。服务器保留结构和旧结构的一个映射。当然,服务器可以随时取 消这种映射,此时客户的请求会被拒绝。此时用户可以必须使用原有的结构进行认证。这种认证本身并不能保证安全,真正的网络安全必须通过别的方式进行保证。

阅读(8521) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~