使用rpcgen构建分布式程序的例子.docx

上传人:b****4 文档编号:3505201 上传时间:2022-11-23 格式:DOCX 页数:19 大小:117.76KB
下载 相关 举报
使用rpcgen构建分布式程序的例子.docx_第1页
第1页 / 共19页
使用rpcgen构建分布式程序的例子.docx_第2页
第2页 / 共19页
使用rpcgen构建分布式程序的例子.docx_第3页
第3页 / 共19页
使用rpcgen构建分布式程序的例子.docx_第4页
第4页 / 共19页
使用rpcgen构建分布式程序的例子.docx_第5页
第5页 / 共19页
点击查看更多>>
下载资源
资源描述

使用rpcgen构建分布式程序的例子.docx

《使用rpcgen构建分布式程序的例子.docx》由会员分享,可在线阅读,更多相关《使用rpcgen构建分布式程序的例子.docx(19页珍藏版)》请在冰豆网上搜索。

使用rpcgen构建分布式程序的例子.docx

使用rpcgen构建分布式程序的例子

分布式系统的重要不言而喻,下面是使用rpcgen工具构建一个分布式程序的例子,其实主要就是卷3上面的那个例子,就是下面这本书.

另外还参考了<分布式系统原理与范例>这本书,即AndrewS.Tanenbaum写的这本,这本书是学习分布式的必读书之一.相当不错.

整个文章分为三部分,第一部分是对RPC(远程过程调用)的一个介绍,第二部分是对使用rpcgen构建分布式程序的一个介绍,第三部分是使用rpcgen构建分布式程序的详细步骤.

可以先按照第三部分的步骤自己先试试,如果有兴趣再回过头来看它的介绍.

另外,我写的这个只是一个入门,如果以后有机会再详细地讨论一下内部的机理.事情总是一步一步来的.

一.RPC概念

1.1介绍

在中间件的实现中,引入了远程过程调用RPC(RemoteProcedureCall)的概念。

同时,许多分布式系统是基于进程间的显式消息交换的,然而消息的发送和接收过程无法隐藏通信的存在,而通信的隐藏对于在分布式系统中实现访问透明性是极为重要的。

因此这个问题在很长一段时间内都没有找到合适的解决办法,后来Birrel和Nelson在1984年的一篇论文中引入了一套与传统方法截然不同的通信处理手段。

他们认为应该允许程序调用位于其它机器上的进程。

当机器A上的进程调用B上的进程时,A上的调用进程被挂起,而B上的被调用进程开始执行。

调用方可以通过使用参数将信息传送给被调用方,然后可以通过传回的结果得到信息。

编程人员看不到任何消息传递过程。

这种方法就称为远程过程调用RPC。

目前,RPC作为一种广泛使用的技术,已成为许多分布式系统的基础。

1.2构建分布式程序的两种模式

在设计分布式应用时,程序员可以使用下列两种方法之一:

面向通信的设计

由通信协议开始。

设计报文格式和语法,指明对每个传入报文将如何反应以及如何产生每个外发报文,以此来设计客户和服务器各构件。

面向应用的设计

由应用开始。

设计常规的应用程序来解决问题。

构建并测试可在单台机器上运行的常规程序的工作版本。

将这个程序划分成两个或多个程序片,加入通信协议以允许每片程序在单独的计算机上执行。

远程过程调用模型使用面向应用的方法,它强调的是所要解决的问题而不是所需要的通信。

利用远程过程调用,程序员首先设计一个解决问题的常规程序,接着将其划分成若赶干片,这些程序片运行在两台或更多的计算机上。

程序员可遵循良好的设计原则,以便使代码模块化并且可维护。

在理想的情况下,远程过程调用提供的不只是抽象上的概念。

它允许程序员在将一个程序划分成若干片之前,先构建,编译和测试一个解决该问题的常规程序的版本,以便确保能够正确解决问题。

不但如此,因为RPC以方法调用为边界划分程序,所以将程序划分为本地部分和远程部分并不会引起程序结构的很大变化。

实际上,将某些过程从一个程序转移到远程机器上时,有可能不需要改变。

1.3常规过程调用的概念性模型

如下图所示,为常规的程序调用。

本地过程调用.jpg

1.4远程过程调用模型

远程过程调用模型使用了和常规程序一样的过程抽象,但是它允许一个过程的边界跨越两台计算机。

如下图所示。

远程过程调用.jpg

1.5常规过程调用的执行和返回

程序从一个主程序开始执行,并一直继续下去,直到遇到一个过程调用。

这个调用使程序的执行转入到某个指定的代码处继续执行。

常规过程调用的执行流程如下图所示:

常规过程调用.jpg

1.6分布式系统的过程模型

在分布式系统中,其中的某个过程有可能在另外的机器上,因此,其调用过程模型如下图如示:

分布式中的过程调用.jpg

1.7客户-服务器和RPC之间的对比

远程过程调用允许程序员以一种他所熟悉的环境来思考客户和服务器的交互,如同常规的过程调用,远程过程调用把控制权传递给被调用的进程。

也像常规过程调用一样,在调用进程中,系统把调用过程的执行挂起。

而只允许被调用过程执行。

当远程程序发出响应时,这对应于在常规过程调用中执行return。

控制权返回给调用者,被调用过程停止执行。

嵌套的过程调用的想法也可应用到远程过程调用。

远程过程调用也许要调用另一个远程过程。

如上图所示。

二.分布式程序的生成原理

RPC的实现包括一个工具,它自动地生成实现分布式程序所需要的大多数代码。

这个工具叫做rpcgen,它读取一个规约文件作为输入,生成C的源文件作为输出。

规范文件包含常量,全局数据类型,全局数据以及远程过程(包括过程参数和结果类型)的声明。

rpcgen产生的代码包含了实现客户和服务器程序所需要的大部分源代码。

具体地说,rpcgen包括参数整理,发送RPC报文,把传入调用分派到正确的过程,发送应答,在参数和结构的外部表示和本地数据表示之间进行转换。

rpcgen的输出与应用程序和程序员编写的少数文件相结合后,便产生出了完整的客户和服务器程序。

rpcgen读取输入文件,该文件包含有对远程过程的说明。

它产生四个输出文件,每个文件都包含有源代码,如果输入的文件(即规约文件)具有名字q.x,则输出的文件如下所示:

q.h:

常量和类型的声明

q_xdr.cXDR过程调用

q_clnt.c客户端的通信接口

q_svc.c服务器端的通信接口

具体步骤在第三部分。

三.分布式程序的生成步骤

在这里,举的例子就是DouglasE.Comer书上的那个例子。

这个例子重点在于解释rpcgen如何工作的。

3.1查找字典

在该例子中,考虑实现一个简单的数据库功能的程序。

该数据库提供四个基本的操作:

初始化,插入一个新的条目,删除一个条目,查找一个条目。

假设每个条目都是一个单词。

因此该数据库的功能就可以看作是一个字典。

应用程序插入一组单词,接着使用数据库来检查新单词,以便知道这个单词是否在该字典中。

3.2构建程序的八个步骤

(1)构建解决该问题的常规应用程序

要构建这个字典应用例子的分布式版本,第一步要求程序员构造解决该问题的常规程序。

文件dict.c包含了该常规程序:

CODE:

/*dict.c*/

#include

#include

#include

#include

#defineMAXWORD50 /*每个单词的长度限制*/

#defineDICTSIZ100   /*字典一共可以存放多少个单词*/

chardict[DICTSIZ][MAXWORD+1];

intnwords=0;

/*

  *nextin():

读入命令与单词

  */

intnextin(char*cmd,char*word)

{

inti,j;

charbuf[100]={0};

if(fgets(buf,sizeof(buf)-1,stdin)==NULL)

return-1;

for(i=0;i<100;i++)

if(buf[i]!

='')

break;

if(i==100)

return-1;

*cmd=buf[i];

while

(1)

{

if(i==100)

return-1;

i++;

if(buf[i]=='')

continue;

elseif(buf[i]=='\n')

return0;

else

break;

}

j=0;

while(buf[i]!

='\n')

{

*word++=buf[i];

i++;

j++;

}

returnj;

}

/*

  *initw():

初始化这个字典

  */

intinitw()

{

nwords=0;

return1;

}

/*

  *insertw(constchar*word):

向这个字典中加入单词

  */

intinsertw(constchar*word)

{

strcpy(dict[nwords],word);

nwords++;

returnnwords;

}

/*

  *deletew(constchar*word):

从这个字典中删除单词

  */

intdeletew(constchar*word)

{

inti;

for(i=0;i

{

if(strcmp(word,dict[i])==0)

{

nwords--;

strcpy(dict[i],dict[nwords]);

return1;

}

}

return0;

}

/*

  *lookupw(constchar*word):

在字典中查找某个单词

  */

intlookupw(constchar*word)

{

inti;

for(i=0;i

{

if(strcmp(word,dict[i])==0)

return1;

}

return0;

}

intmain(intargc,char**argv)

{

charword[MAXWORD+1];

charcmd;

intwrdlen;

while

(1)

{

wrdlen=nextin(&cmd,word);

if(wrdlen<0)

exit(0);

word[wrdlen]='\0';

switch(cmd)

{

case'I':

initw();

printf("Dictionaryinitializedtoempty.\n");

break;

case'i':

insertw(word);

printf("%sinserted.\n",word);

break;

case'd':

if(deletew(word))

printf("%sdeleted.\n",word);

else

printf("%snotfound.\n",word);

break;

case'l':

if(lookupw(word))

printf("%sisfound.\n",word);

else

printf("%sisnotfound.\n",word);

break;

case'q':

printf("programquits.\n");

exit(0);

default:

printf("command%cinvalid.\n",cmd);

break;

}

}

return0;

}

该dict.c程序使用了一个二维数组来存储单词。

全局变量nwords记录了任何时刻字典中单词的数量。

主程序包含一个循环,在每次循环中读取并处理输入文件中的一行。

该程序就是普通的常规程序,编译并运行:

gccdict.c–odict

./dict

可以如下来实验

QUOTE:

I

ihello

iworld

lhello

dhello

lhello

q

(2)将程序划分成两部分

常规程序一旦构建完成并经过测试,就可以将它划分成本地构件和远程构件了。

下图为这个程序的过程调用情况:

程序组织情况.jpg

在考虑哪个过程可以转移到远程机器上时,程序员必须考虑每个过程所需要的资源。

例如nextin在每次被调用时要读取下一个输入行,并对它进行分析。

因为它需要访问程序的标准输入,所以nextin必须放在主程序的机器中。

简单来说就是执行I/O或者访问文件描述符的过程不能轻易地转移到远程机器中。

同时还要考虑每个过程所要访问的数据所处的位置。

例如,lookupw需要访问全部单词数据库,如果执行lookupw的机器不同于字典所处的机器,对lookupw的RPC调用就必须将整个字典作为参数来传递。

将巨大的数据结构作为参数传递给远程过程的效率非常低。

一般来说执行过程的机器应当与放置过程所要访问数据的机器是同一台,将巨大的数据结构传递给远程过程的效率是很低的。

对于本应用来说,应当把过程insertw,deletew,initw,lookupw和字典本身放在同一台机器中。

下图说明了这种划分方式:

划分程序.jpg

相当于将dict.c划分成了两个文件dict1.c和dict2.c。

这里的两个程序只是一个示例,不需要写出来,但是后面将要编写与这两个程序相似的另外两个程序。

文件dict1.c包含主程序和过程nextin:

CODE:

/*dict1.c*/

#include

#include

#include

#include

#defineMAXWORD50

chardict[DICTSIZ][MAXWORD+1];

intnwords=0;

intnextin(char*cmd,char*word)

{

inti,j;

charbuf[100]={0};

if(fgets(buf,sizeof(buf)-1,stdin)==NULL)

return-1;

for(i=0;i<100;i++)

if(buf[i]!

='')

break;

if(i==100)

return-1;

*cmd=buf[i];

while

(1)

{

if(i==100)

return-1;

i++;

if(buf[i]=='')

continue;

elseif(buf[i]=='\n')

return0;

else

break;

}

j=0;

while(buf[i]!

='\n')

{

*word++=buf[i];

i++;

j++;

}

returnj;

}

intmain(intargc,char**argv)

{

charword[MAXWORD+1];

charcmd;

intwrdlen;

while

(1)

{

wrdlen=nextin(&cmd,word);

if(wrdlen<0)

exit(0);

word[wrdlen]='\0';

switch(cmd)

{

case'I':

initw();

printf("Dictionaryinitializedtoempty.\n");

break;

case'i':

insertw(word);

printf("%sinserted.\n",word);

break;

case'd':

if(deletew(word))

printf("%sdeleted.\n",word);

else

printf("%snotfound.\n",word);

break;

case'l':

if(lookupw(word))

printf("%sisfound.\n",word);

else

printf("%sisnotfound.\n",word);

break;

case'q':

printf("programquits.\n");

exit(0);

default:

printf("command%cinvalid.\n",cmd);

break;

}

}

return0;

}文件dict2.c包含了来自最初的应用程序的一些函数。

它们将成为远程程序的一部分。

另外,还包含对各个函数要共享的全局数据的声明。

CODE:

/*dict2.c*/

#include

#defineMAXWORD50

#defineDICTSIZ100

chardict[DICTSIZ][MAXWORD+1];

intnwords=0;

intinitw()

{

nwords=0;

return1;

}

intinsertw(char*word)

{

strcpy(dict[nwords],word);

nwords++;

returnnwords;

}

intdeletew(char*word)

{

inti;

for(i=0;i

{

if(strcmp(word,dict[i])==0)

{

nwords--;

strcpy(dict[i],dict[nwords]);

return1;

}

}

return0;

}

intlookupw(char*word)

{

inti;

for(i=0;i

if(strcmp(word,dict[i])==0)

return1;

return0;

}注意,对符号常量MAXWORD的定义在两个构件中都出现了,因为它们都要声明用于存储字的变量,然而,只有在文件dict2.c中才含有用于存储字典的数据结构的声明,因为只有远程过程才包含字典的数据结构。

此时

gcc–cdict1.c

gcc–cdict2.c

编译一下检查是否有语法错误。

(3)创建rpcgen规约

程序员一旦为某个分布式程序选择了一种结构,就可以准备rpc规约了。

从本质上说,rpcgen规约文件包含了对远程程序的声明以及它所使用的数据结构。

该规约文件包含常量,类型定义,以及对客户和服务器程序的声明。

即:

声明在客户或服务器中所使用的常量

声明所使用的数据类型

声明远程程序,每个程序中所包含的过程以及它们的参数类型

所有这些声明必须用RPC编程语言来给出。

对于该例子的规约如下,rdict.x:

CODE:

/*rdict.x*/

constMAXWORD=50;

constDICTSIZ=100;

structexample

{

intexfield1;

charexfield2;

};

programRDICTPROG/*远程程序的名称*/

{

versionRDICTVERS/*版本*/

{

intINITW(void)=1;    /*第一个函数*/

intINSERTW(string)=2;    /*第二个函数*/

intDELETEW(string)=3;    /*第三个函数*/

intLOOKUPW(string)=4;    /*第四个函数*/

}=1;/*该程序的版本号*/

}=0x30090949;/*远程程序标识,必须唯一*/(4)运行rpcgen

在完成了规约后,程序员运行rpcgen来检查语法错误,并且生成四个代码文件:

rdict.hrdict_clnt.crdict_svc.c和rdict_xdr.c。

输入命令:

rpcgenrdict.x

rdict.h为rpcgen产生的.h文件,即头文件。

rdict_xdr.c为rpcgen产生的XDR转换文件,XDR即ExternalDataRepresentation。

是数据传输的一个规范。

rdict_clnt.c为rpcgen产生的客户端的代码。

rdict_svc.c为rpcgen产生的服务器端的代码。

这些文件一旦生成,就可以被编译成目标代码的形式。

gcc–crdict_clnt.c

gcc–crdict_svc.c

gcc–crdict_xdr.c

到了这一步,需要程序员再编写接口过程。

程序员需要编写四个程序,分别为客户端程序与客户端的接口程序,服务器程序与服务器端的接口程序。

如下图所示,图中阴影部分为需要程序员自己编写的代码:

程序框架.jpg

(5)编写接口过程

rpcgen产生的文件并没有构成完整的程序,它还要求程序员必须编写客户端和服务器端的接口。

客户端接口rdict_cif.c程序如下所示:

CODE:

/*rdict_cif.c*/

#include

#include

#defineRPC_CLNT

#include"rdict.h"

externCLIENT*handle;

staticint*ret;

intinitw()

{

ret=initw_1(0,handle);

returnret==NULL?

0:

*ret;

}

intinsertw(char*word)

{

char**arg;

arg=&word;

ret=insertw_1(arg,handle);

returnret==NULL?

0:

*ret;

}

intdeletew(char*word)

{

char**arg;

arg=&word;

ret=deletew_1(arg,handle);

returnret==NULL?

0:

*ret;

}

intlookupw(char*word)

{

char**arg;

arg=&word;

ret=lookupw_1(arg,handle);

returnret==NULL?

0:

*ret;

}服务器端接口例程rdict_sif.c如下:

CODE:

/*rdict_sif.c*/

#include

#defineRPC_SVC

#include"rdict.h"

staticintretcode;

intinitw(void),insertw(char*),deletew(char*),lookupw(char*);

int*insertw_1_svc(char**w,structsvc_req*rqstp)

{

retcode=insertw(*(char**)w);

return&retcode

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 表格模板 > 合同协议

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1