Linux RPC编程

1. RPC简介

RPC,英文全称为Remote Procedure Call Protocol,也就是远程过程调用协议。所谓远程,顾名思义,就不是本地,在传统的编程概念中,过程是由程序员在本地编译完成,并只能局限在本地运行的一段代码,也即其主程序和过程之间的运行关系是本地调用关系。但是,这种传统调用模式有一个最大的弊端,就是无法充分利用网络上其他主机的资源(如CPU, Memory等等)。而是通过RPC, 我们可以充分利用共享内存的多处理器环境(例如通过局域网连接的多台工作站),这样就可以简单地将你的应用分布在多台工作站上,应用程序就像运行在一个多处理器的计算机上一样。你可以方便的实现过程代码共享,提高系统资源的利用率。

一般来说,我们目前谈论的最多的就是SUN公司的RPC,由于其开源性,使得SUN RPC得以大规模的使用,所以,我们这里讨论的就是SUN RPC。当然还有其他公司定义的RPC,这个有兴趣的请自行去了解下。

2. RPC工作原理

RPC采用客户机/服务器模式,有点类似C/S Socket 编程模式,但要比它更高一层。请求程序就是一个客户机,而服务提供程序就是一个服务器。首先,客户机调用进程发送一个有进程参数的调用信息到服务进程,然后等待应答信息。在服务器端,进程保持睡眠状态直到调用信息的到达为止。当一个调用信息到达,服务器获得进程参数,计算结果,发送答复信息,然后等待下一个调用信息,最后,客户端调用进程接收答复信息,获得进程结果,然后调用执行继续进行。可参考以下图来理解:

clipboard

这个图很清楚的说明了RPC的工作流程,就不在一一说明了,更详细的资料可以参考《UNIX网络编程 第2卷 进程间通信》有关SUN RPC这一章节,里面详尽描述了整个RPC过程调用。

3. RPC开发流程:

一般而言在开发RPC时,我们通常分为三个步骤:
(1)定义说明客户/服务器的通信协议。
这里所说的通信协议是指定义服务过程的名称、调用参数的数据类型和返回参数的数据类型,还包括底层传输类型(可以是UDP或TCP),当然也可以由RPC底层函数自动选择连接类型建立TI-RPC。最简单的协议生成的方法是采用协议编译工具,常用的有Rpcgen,整个是SUN RPC提供的运行例库。
(2)开发客户端程序。
(3)开发服务器端程序。

4. RPC编程开发一般思路:

客户端: 调用相关的函数,创建句柄,使用tcp/udp进行通信。

服务器: 以线程运行,等待客户端发过来的链接请求,再调用相关的函数处理后再返回相关的值。

重要相关知识要点:

(1) 如果有多个CLIENT *cln句柄的问题,要注意其创建及管理,同时要考虑多线程的问题,可以使用线程存储来解决。

(2)创建TCP/UDP协议的CLIENT *clnt句柄。可以细分到创建tcp/udp类型的socket。

(3)客户端中UPD PORT限制范围的问题。比如,如何使用自己定义的范围,而不是让系统自动分配。

5. 关于程序号,版本号和过程号的说明

前面说到客户/服务器之间的通信协议,不得不说下程序号,版本号和过程号。首先,我们要知道如何表示一个唯一存在的远程过程?当一次RPC调用结束后,相应线程发送相应的信号,客户端程序才会继续运行。当然,一台服务主机上可以有多个远程过程提供服务,那么,问题来了,如何来表示一个唯一存在的远程过程呢?一个远程过程是有三个要素来唯一确定的:程序号、版本号和过程号。程序号是用来区别一组相关的并且具有唯一过程号的远程过程。一个程序可以有一个或几个不同的版本,而每个版本的程序都包含一系列能被远程调用的过程,通过版本的引入,使得不同版本下的RPC能同时提供服务。每个版本都包含有许多可供远程调用的过程,每个过程则有其唯一标示的过程号。

例如,下面就是一个实例,我们的rpc.x文件定义如下:

#define RPC1_RPC_PROG_NUM 0x38000023 //this should be bigger than 0x38000000

/* data Structure */

struct _rpc1_data_t {

        int type; /* 1: time, 2: pid number */

        char data[0];

};

typedef struct _rpc1_data_t rpc1_data_t;

program RPC1_RPC_PROG {

        version RPC1_RPC_VERS1 {

                int RPC1_RPCC(rpc1_data_t) = 1; /* Procedure number = 1 */

        } = 1; /* Version number = 1 */

} = RPC1_RPC_PROG_NUM; /* Program number */

6. 关于Rpcgen

rpcgen可以说是SUN公司提供的一个快速自动生成相关RPC代码的一个工具。它的输入为一个规格说明文件,它的输出为一个C语言的源程序。规格文件(*.x)包含常量、全局数据类型以及远程过程的声明。Rpcgen产生的代码包含了实现客户机和服务器程序所需要的大部分源代码。它包括参数整理、发送RPC报文、参数和结果的外部数据表示以及本地数据表示的转换等。不过在由rpcgen生成的源文件中,没有过程的具体实现,所以程序员必须要手工编辑这些文件,实现这些过程。

一般Linux上都默认安装好了rpcgen, 通过查看帮助,可以快速产生rpc相关的代码文件。

xx@xx ~ $ rpcgen --h

usage: rpcgen infile

rpcgen [-abkCLNTM][-Dname[=value]] [-i size] [-I [-K seconds]] [-Y path] infile

rpcgen [-c | -h | -l | -m | -t | -Sc | -Ss | -Sm] [-o outfile] [infile]

rpcgen [-s nettype]* [-o outfile] [infile]

rpcgen [-n netid]* [-o outfile] [infile]

options:

-a generate all files, including samples

-b backward compatibility mode (generates code for SunOS 4.1)

-c generate XDR routines

-C ANSI C mode

-Dname[=value] define a symbol (same as #define)

-h generate header file

-i size size at which to start generating inline code

-I generate code for inetd support in server (for SunOS 4.1)

-K seconds server exits after K seconds of inactivity

-l generate client side stubs

-L server errors will be printed to syslog

-m generate server side stubs

-M generate MT-safe code

-n netid generate server code that supports named netid

-N supports multiple arguments and call-by-value

-o outfile name of the output file

-s nettype generate server code that supports named nettype

-Sc generate sample client code that uses remote procedures

-Ss generate sample server code that defines remote procedures

-Sm generate makefile template

-t generate RPC dispatch table

-T generate code to support RPC dispatch tables

-Y path directory name to find C preprocessor (cpp)

For bug reporting instructions, please see:

<http://bugs.gentoo.org/>.

xx@xx ~ $

通过rpcgen,可以产生以下文件:

(1)一个头文件(.h)包括服务器和客户端程序变量、常量、类型等说明。

(2)一系列的XDR例程,它可以对头文件中定义的数据类型进行处理。

(3) 一个Server 端的标准程序框架。

(4) 一个Client 端的标准程序框架。

举例说明,比如如何利用前面的rpc.x文件,通过rpcgen来产生相关的客户端和服务器rpc代码呢?如下:

(1)#rpcgen -a [-C] rpc.x                           //产生所有的模板文件

(2)#rpcgen -h rpc.x  -o rpc.h                   //产生头文件

(3)#rpcgen -Sc rpc.x -o rpc_client.c        //产生客户端的模板文件

(4)#rpcgen -Ss rpc.x  -o rpc_server.c       //产生服务器的模板文件

(5)#rpcgen -Sm rpc.x                               //产生Makefile文件

(6)#rpcgen -l rpc.x -o rpc_clnt.c              //产生客户端stub

(7)#rpcgen -m rpc.x -o rpc_svc.c             //产生服务器stub

(8)#rpcgen -c rpc.x -o rpc_xdr.c              //产生xdr文件

7. 关于rpc编程中需要注意的知识点

(1)关于rpc timeout问题

由于是远程调用,所以相对于本地调用来说,rpc所要花费的时间更长,很容易出现timeout的问题。注意,因TCP是可靠的面向连接的协议,有自己的一套机制来保证端到端的传输可靠性,故使用TCP建立的应用不需要考虑超时重传等问题。、

1. 总超时值:一个客户端等待其服务器的应答的总时间量。TCP和UDP都是用该值;

2. 重试超时:只用于UDP, 是一个客户等待其服务器的应答期间每次重传请求的间隔时间;

默认的超时时间可以通过clnt_control(CLGET_TIMEOUT/CLGET_RETRY_TIMEOUT)函数获取。

struct timeval tv;

clnt_control(clnt, CLGET_TIMEOUT, (char *)&tv);

clnt_control(clnt, CLGET_RETRY_TIMEOUT, (char *)&tv);

printf("timeout = %d : timeout = %d\n", tv.tv_sec, tv.tv_usec);

至于这两个值具体是多少,我没有验证过,看到有网友说是:UDP的默认总超时值为-1,重传超时值为5s(unp书上说是15s), TCP的默认总超时值为0(unp书上说是30s),而为什么跟书上不同,他的解释是由于RPC的版本不一样,这一点,有待验证。而这两个值,我们可以通过替换成CLSET_TIMEOUT/CLSET_RETRY_TIMEOUT就可以设置我们需要的timeout值。

(2)如何指定RPC port的范围

在原有的rpc port-mapping service中,sunrpc保留512~1024作为保留的端口号使用,也就是说如果用原来的port-mapping, 就可能会使用到这个范围的端口号,如果要使用1024以上的端口号,那么改如果修改呢?

在svc_register中最后一个参数port,传递port=0进去,进而就不会再调用sunrpc本身自带的pmap_set()函数了,我们可以重写属于我们自己的pmap_set()函数,跟自带的pmap_set()函数的区别在于,自带的函数使用的是socke == 1去调用clntudp_bufcreate(),而我们可以自己先创建好UDP的socket再去调用此函数。这样,就可以使用我们指定的port number了。

8. 关于RPC 接口说明

开发客户端和服务器端的程序时,RPC提供了我们不同层次的开发例程调用接口。不同层次的接口提供了对RPC不同程度控制。一般可分为5个等级的编程接口,接下来我们分别讨论一下各层所提供的功能函数.

(1)简单层例程

简单层是面向普通RPC应用,为了快速开发RPC应用服务而设计的,他提供了如下功能函数。
函数名 功能描述
Rpc_reg( )                 在一特定类型的传输层上注册某个过程,来作为提供服务的RPC程序
Rpc_call( )                 远程调用在指定主机上指定的过程
Rpc_Broadcast( )      向指定类型的所有传输端口上广播一个远程过程调用请求

(2)高层例程
在这一层,程序需要在发出调用请求前先创建一个客户端句柄,或是在侦听请求前先建立一个服务器端句柄。程序在该层可以自由的将自己的应用绑在所有的传输端口上,它提供了如下功能函数。
函数名    功能描述
Clnt_create( )            程序通过这个功能调用,告诉底层RPC服务器的位置及其传输类型
Clnt_create_timed( ) 定义每次尝试连接的超时最大时间
Svc_create( )             在指定类型的传输端口上建立服务器句柄,告诉底层RPC事件过程的相应入口地址
Clnt_call()                     向服务器端发出一个RPC调用请求

(3)中间层例程
中间层向程序提供更为详细的RPC控制接口,而这一层的代码变得更为复杂,但运行也更为有效,它提供了如下功能函数。
函数名        功能描述
Clnt_tp_create( )            在指定的传输端口上建立客户端句柄
Clnt_tp_create_timed( ) 定义最大传输时延
Svc_tp_creaet( )             在指定的传输端口上建立服务句柄
Clnt_call( )                      向服务器端发出RPC调用请求

(4)专家层例程
这层提供了更多的一系列与传输相关的功能调用,它提供了如下功能函数。
函数名    功能描述
Clnt_tli_create( )    在指定的传输端口上建立客户端句柄
Svc_tli_create( )     在指定的传输端口上建立服务句柄
Rpcb_set( )            通过调用rpcbind将RPC服务和网络地址做映射
Rpcb_unset( )       删除rpcb_set( ) 所建的映射关系
Rpcb_getaddr( )   调用rpcbind来犯会指定RPC服务所对应的传输地址
Svc_reg( )             将指定的程序和版本号与相应的时间例程建起关联
Svc_ureg( )           删除有svc_reg( ) 所建的关联
Clnt_call( )            客户端向指定的服务器端发起RPC请求

(5)底层例程
该层提供了所有对传输选项进行控制的调用接口,它提供了如下功能函数。
函数名    功能描述
Clnt_dg_create( )      采用无连接方式向远程过程在客户端建立客户句柄
Svc_dg_create( )    采用无连接方式建立服务句柄
Clnt_vc_create( )    采用面向连接的方式建立客户句柄
Svc_vc_create( )     采用面向连接的方式建立RPC服务句柄
Clnt_call( )             客户端向服务器端发送调用请求

说明:其实这么多接口不需要去记,我们平时常用的就那么几个,而其他的可以在用到时再去查询下用法即可。

注:本文部分资料参考了网络上相关资源,因数目较多,就不一一列举,若涉及到您的版权问题,请与我联系,我会及时删除,谢谢。同时,若你喜欢这篇文章,请在转载时注明出处,谢谢合作!

发布了16 篇原创文章 · 获赞 5 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/ok_wolf/article/details/40987005
RPC