一、构建常规应用程序
要构建这个字典应用例子的分布式版本,第一步要求程序员构造解决问题的常规程序。
最初这个字典应用的过程组织情况
二、将程序划分为两个部分。
在考虑哪一个过程可以转移到远程机器上时,程序员必须考虑每一个过程所需要的设施,比如过程nextin在每一次被调用时要读取下一输入行,并对它进行分析,因为它要访问程序的标准输入文件,所以nextin必须放在主程序中。程序员还必须考虑每个过程所要访问的数据所处的位置。
执行I/0或者访问文件描述符的过程不能轻易地转移到远程机器中。
执行过程的机器应当与放置过程所要访问数据的机器是同一台。将巨大的数据结构传递给远程过程的效率很低。(可是是远程机器,也可是客户主机器)
在考虑了最初的字典应用程序以及每个过程要访问的数据之后,很明显应当把过程insertw,deletew,initw,lookupw和字典本身放在同一台机器中。下图表示了应用程序的划分。
在这个划分的基础上,将程序分解为本地和远程两个构建。
Dict1.c含有主程序和过程nextin
Dict2.c包含了来自最初的应用程序的一些过程,他们将成为远程程序的一部分。另外,它还包含对各个过程要共享的全局数据的声明。
分别编译这两个文件,产生两个构件的目标文件,这些构件必须连接到一起以产生可执行的程序。
三、创建rpcgen规约
程序员一旦为某个分布式程序选择了一种结构,就可以准备rpc规约了,从本质上说,rpcgen规约文件包含了对远程程序的声明以及它所使用的数据结构。
规约文件包含:
声明在客户或者服务器中共享使用的常量
声明客户和服务器之间要共享使用的数据类型
声明远程程序,每个程序中所包含的过程以及它们的参数类型
Rpc使用一些数字来标识远程程序以及在这些程序中的远程过程。在规约文件中的程序声明定义了诸如程序的RPC号、版本号以及分配给程序中的过程的编号。
所有这些声明必须要用RPC编程语言给出,而不是用c语言。
文件rdict.x说明了一个rpcgen规约,它包含了对字典程序之rpc版的声明。
按照约定,规约文件使用大写的名字来定义过程和程序。这些名字成为可以在C程序中使用的符号常量,大写有助于避免冲突。
四、运行rpcgen
程序员运行rpcgen程序来检查语法上的错误。Rpcgen rdict.x
Rpcgen在生成四个输出文件时,使用了输入文件的名字。例如,因为输入文件由rdict开始,所以输出文件将被命名为:rdict.h,rdict_clnt.c,rdict_sve.c和rdict_xdr.c。
Rdict.h包含了在规约文件中所声明的所有常量和数据类型的C的合法声明。另外,rpcgen还增加了对远程过程的定义。
Rdict.h中有外部声明,这个被声明的过程构成了客户端的stub的接口部分。过程名称不变, 只是将它们映射为小写,并附加上一个下划线和程序的版本号。(例如远程过程DELETEW,于是rdict.h含有对过程deletew_1的外部声明。)
这里是ridct.h的部分代码。
Rpcgen产生了含有对一些例程的调用的文件,这些例程执行XDR转换。这种调用是针对远程程序中所声明的所有数据类型的。
对于字典应用程序,rpcgen产生了文件rdict_clnt.c,这是源程序,它将成为本程序分布式版中客户端的通信stub。该文件为远程程序中的每一个过程准备好了通信stub过程。
Rpc跟产生的第四个文件是rdict_svc.c,它包含服务器所需要的代码。该文件包含服务器在开始时要执行的主程序。它包括获得协议端口号,向端口映射器注册RPC程序,接着便等待接收RPC调用。它将每个调用分派给合适的服务器端口stub接口,当被调用的过程响应时,服务器创建rpc应答并将它发回客户。
该文件一旦生成,就可以被编译成为目标代码的形式。
五、编写stub接口过程
Rpcgen产生的文件并没有构成完成的程序,它要求程序员必须编写客户端和服务器端的接口例程,在远程程序中的每一个过程都必须要存在一个接口过程。
这便是在客户端stub接口过程inserw(char *word)。
在服务器端,接口例程接收来自rpcgen所产生的通信stub的调用,并将控制权传递给实现这个指定调用的过程。如同客户端那样,服务器接口例程必须把参数由rpcgen所选择的类型转变为被调用过程中所使用的类型。
六、编译和连接客户程序
对接口例程进行编译,所输出的是.o为后缀的文件。
程序员需要在最初的主程序中加入一点新的细节,因为使用了rpc,程序需要有针对rpc声明的c的include文件,还需要包含rdict.h文件,因为它包含了客户和服务器都要使用的常量的定义。还需要声明并初始化一个句柄(handle),rpc通信例程用该句柄和服务器通信。
在编译完修改之后的主程序代码rdict.c生成rdict.o文件之后,将客户端的所有文件链接到一起,并将最终的可执行客户程序放到文件rdict中。
cc -o rdict rdict.o rdict_clnt.o rdict_xdr.o rdict_cif.o
其中,rdict.o是我们修改之后主程序代码编译之后的.o文件,rdict_clnt.o是rpcgen产生的客户代码编译生成的.o文件,rdict_cif.o是我们自己编写的接口例程编译之后生成的.o文件。
七、编译和链接服务器程序
构成服务器的这些目标程序就可以用如下命令连接成可执行的文件,可执行文件放在rdictd中:
八、启动服务器和执行客户
在客户端程序运行之前,必须先开启服务器程序,不然无法联系远程程序。一定要用命令./rdictd先执行服务器程序。
注:个人理解:到目前为止,把rpc的主要工作原理都理解了,现在做一个个人的理解分析,加深对rpc的印象。
首先,建立常规程序,然后划分程序,在此基础上编写rpc规约。rpc规约是针对远程服务器上的程序和过程制定的以(.x)为后缀的文件。Rpc规约中包含了客户端和服务器共享使用的常量,数据结构和远程程序和过程的声明,它是以数字来标识远程程序和过程的。之后输入命令行(rpcgen 规约文件名.x),运行rpcgen,将生产四个文件,分别为(规约文件名.h,规约文件名_clnt.c,规约文件名_svc.c,规约文件名_xdr.c),其中.h的头文件,是将源规约文件中的变量,数据结构和过程转化为c语言的形式,规约文件名_clnt.c,规约文件名_svc.c是符合rpc规范的客户端,服务器端的源码,对远程程序的每一个过程都有相应的处理。之后,我们要编写客户端和服务器端的接口过程,这里以常规程序中的一个函数做例子。比如insertw(char *word){}函数:在客户端,客户跟在常规程序中一样还是使用void insertw(char *word){},但是这个函数已经到远程服务器上了,无法直接调用。这时候我们就需要一个客户端接口,能够与rpc运行生成的客户端程序进行通信。Rpc的客户端程序参数是间接的,形如char **word,所以必须在我们编写的客户端接口对参数进行转化,然后相应调用rpc客户端程序中的void insertw_1(char **word){}函数。这一过程相当于对服务器发出了请求,rpc的服务器端接收到了相应的请求,调用相应函数void insertw_1_svc(char **word){},在函数中应该把参数还原成char *word,然后调用放在服务器上的void insertw(char *word){}函数,处理完成后,对客户端的请求最返回处理。
整个过程中,程序员需要编写的代码主要有三块:①rpc规约文件②客户端接口过程③服务器端接口过程