Unix/Linux编程:分布式程序的生成(rpcgen的例子)

例子:查找字典

为理解rpgen是如何工作的,让我们考虑一个实现简单数据库功能的应用。该数据库提供四个基本操作:

  • 初始化:初始化数据化,即清除以前存储的所有值
  • 插入:插入一个新的条目
  • 删除:删除一个条目
  • 查找:寻找某个条目

我们假设数据库中每个条目都是单个的单词。因此,该数据库的功能可以看作是一个字典。应用程序插入一组合法的单次,接着使用数据库来检测新单词,以便知道该单词是否在字典中

我们假设应用程序的输入一个文本文件,在文件中,每行包含一个单字母的命令,其后跟随者一个单词。下表列出来这些命令,并且给出了每个命令的含义:
在这里插入图片描述
例如,下面输入含有一系列的命令数据。这些命令的含义是初始化字典、插入计算机厂商名字、删除一些名字,并查找三个名字:
在这里插入图片描述

分布式程序的八个步骤

下图展示了rpcgen的输入要求以及它所生成的输出文件。为创建这些分布式程序所要求的文件,并将这些文件与某个客户和服务器相结合,程序员应该采取如下八个步骤

  1. 构建解决该问题的常规应用程序
  2. 选择一组过程,以便将这些过程转移到远程机器中,通过这种方法将程序分解
  3. 为远程程序编写rpcgen规约,包括远程过程的名字以及其编号,还有对其参数的声明。选择远程程序号和版本号(通常为1)
  4. 运行rpcgen检查该规约,如果合法,便生成四个源代码文件,这些文件将在客户和服务器程序中使用
  5. 为客户端和服务器编写sub接口例程
  6. 编译并链接客户端程序。它由四个主要的文件构成:最初的应用程序(远程过程中被删除了的那个)、客户端的stub(由rpcgen生成)、客户端的接口stub以及XDR过程(由rpcgen生成)。当所有这些文件都被编译和链接到一起后,最终的可执行程序就是客户
  7. 编译并链接服务器程序。它由四个主要的文件构成:由最初的应用程序得来的过程,它们现在构成了远程程序;服务器的stub(由rpcgen生成)、服务器的接口stub以及XDR过程(由rpcgen生成)。当所有这些文件都被编译和链接到一起后,最终的可执行程序就是服务器
  8. 在远程机器上启动服务器,接着在本地机器上启动客户

在这里插入图片描述

步骤1:构建常规应用程序

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

/* dict.c - main, initw, nextin, insertw, deletew, lookupw */

#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>

#define	MAXWORD	50		/* maximum length of a command or word	*/
#define DICTSIZ 100		/* maximum number of entries in diction.*/
char	dict[DICTSIZ][MAXWORD+1];/* storage for a dictionary of words	*/
int	nwords = 0;		/* number of words in the dictionary	*/

int	nextin(char *cmd, char *word), initw(), insertw(const char *word);
int	deletew(const char *word), lookupw(const char *word);

/*------------------------------------------------------------------------
 * main - insert, delete, or look up words in a dictionary as specified
 *------------------------------------------------------------------------
 */
int main(int argc, char* argv[])
{
    char	word[MAXWORD+1]; /* space to hold word from input line	*/
    char	cmd;
    int	wrdlen;		/* length of input word			*/

    while (1) {
        wrdlen = nextin(&cmd, word);
        if (wrdlen < 0)
            exit(0);
        word[wrdlen] = '\0';
        switch (cmd) {
            case 'I':	/* "initialize" */
                initw();
                printf("Dictionary initialized to empty.\n");
                break;
            case 'i':	/* "insert" */
                insertw(word);
                printf("%s inserted.\n",word);
                break;
            case 'd':	/* "delete" */
                if (deletew(word))
                    printf("%s deleted.\n",word);
                else
                    printf("%s not found.\n",word);
                break;
            case 'l':	/* "lookup" */
                if (lookupw(word))
                    printf("%s was found.\n",word);
                else
                    printf("%s was not found.\n",word);
                break;
            case 'q':	/* quit */
                printf("program quits.\n");
                exit(0);
            default:	/* illegal input */
                printf("command %c invalid.\n", cmd);
                break;
        }
    }
}

/*------------------------------------------------------------------------
 * nextin - read a command and (possibly) a word from the next input line
 *------------------------------------------------------------------------
 */
int nextin(char *cmd, char *word)
{
    int	i, ch;

    ch = getc(stdin);
    while (isspace(ch))
        ch = getc(stdin);
    if (ch == EOF)
        return -1;
    *cmd = (char) ch;
    ch = getc(stdin);
    while (isspace(ch))
        ch = getc(stdin);
    if (ch == EOF)
        return -1;
    if (ch == '\n')
        return 0;
    i = 0;
    while (!isspace(ch)) {
        if (++i > MAXWORD) {
            printf("error: word too long.\n");
            exit(1);
        }
        *word++ = ch;
        ch = getc(stdin);
    }
    return i;
}

/*------------------------------------------------------------------------
 * initw - initialize the dictionary to contain no words at all
 *------------------------------------------------------------------------
 */
int initw()
{
    nwords = 0;
    return 1;
}

/*------------------------------------------------------------------------
 * insertw - insert a word in the dictionary
 *------------------------------------------------------------------------
 */
int insertw(const char *word)
{
    strcpy(dict[nwords], word);
    nwords++;
    return nwords;
}

/*------------------------------------------------------------------------
 * deletew - delete a word from the dictionary
 *------------------------------------------------------------------------
 */
int deletew(const char *word)
{
    int	i;

    for (i=0 ; i<nwords ; i++)
        if (strcmp(word, dict[i]) == 0) {
            nwords--;
            strcpy(dict[i], dict[nwords]);
            return 1;
        }
    return 0;
}

/*------------------------------------------------------------------------
 * lookupw - look up a word in the dictionary
 *------------------------------------------------------------------------
 */
int lookupw(const char *word)
{
    int	i;

    for (i=0 ; i<nwords ; i++)
        if (strcmp(word, dict[i]) == 0)
            return 1;
    return 0;
}

然后生可执行的二进制文件:

cc -o dict dict.c

步骤2:将程序划分为两个部分

常规程序一旦构建完成并经过测试,就可以将它划分成本地构件和远程构建了。在程序员开始划分程序之前,它们必须具有一个程序过程调用的概念模型。下图展示了最初这个字典应用的过程组织情况
在这里插入图片描述
在考虑哪个过程可以转移到远程机器上时,程序员必须考虑每个过程所需要的设施。比如,过程nextin在每次被调用时要读取下一输入行,并对它进行分析。因为它需要访问程序的标准输入文件,所以nextin必须放在主程序中。即:

执行IO或者访问文件描述符的过程不能轻易转移到远程机器中。

程序员还必须考虑每个过程所要访问的数据所处的位置。例如,过程lookup需要访问全部单词数据库。如果执行lookupw的机器不同于字典所处的机器,对loopupw的RPC调用就必须将整个字典作为参数来传递

将巨大的数据机构作为参数传递给远程过程的效率非常低,因为RPC必须为每个远程过程调用对整个数据结构进行读取和编码。一般来说:

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

也就是说,应该把过程insertw、deletew、initw、loopupw和字典本身放到同一台机器中。

假设程序员决定将字典的存储以及相关的过程转移到另一台远程机器中。为理解这样做的后果,程序员往往要对创建一个分布式程序和数据结构做到心中有数。下图说明了在将数据以及对它的访问过程转移到另一台远程机器之后,这个字典应用程序的新结构。

在这里插入图片描述
现在已经将程序进行了概念划分,下一步就是将源程序分解为本地和远程两个构建。程序员明确每个构建所要使用的常量和数据结构,将每个构件放置到单独的文件中。如下dict1.c 中含有main, nextin:

/* dict1.c - main, nextin */

#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>

#define	MAXWORD	50		/* maximum length of a command or word	*/

int	nextin(char *cmd, char *word), initw(void), insertw(char *),
	deletew(char *), lookupw(char *);

/*------------------------------------------------------------------------
 * main - insert, delete, or look up words in a dictionary as specified
 *------------------------------------------------------------------------
 */
int main(int argc, char *argv[])
{
	char	word[MAXWORD+1]; /* space to hold word from input line	*/
	char	cmd;
	int	wrdlen;		/* length of input word			*/

	while (1) {
		wrdlen = nextin(&cmd, word);
		if (wrdlen < 0)
			exit(0);
		switch (cmd) {
		case 'I':	/* "initialize" */
			initw();
			printf("Dictionary initialized to empty.\n");
			break;
		case 'i':	/* "insert" */
			insertw(word);
			printf("%s inserted.\n",word);
			break;
		case 'd':	/* "delete" */
			if (deletew(word))
				printf("%s deleted.\n",word);
			else
				printf("%s not found.\n",word);
			break;
		case 'l':	/* "lookup" */
			if (lookupw(word))
				printf("%s was found.\n",word);
			else
				printf("%s was not found.\n",word);
			break;
		case 'q':	/* quit */
			printf("program quits.\n");
			exit(0);
		default:	/* illegal input */
			printf("command %c invalid.\n", cmd);
			break;
		}
	}
}

/*------------------------------------------------------------------------
 * nextin - read a command and (possibly) a word from the next input line
 *------------------------------------------------------------------------
 */
int nextin(char *cmd, char *word)
{
	int	i, ch;

	ch = getc(stdin);
	while (isspace(ch))
		ch = getc(stdin);
	if (ch == EOF)
		return -1;
	*cmd = (char) ch;
	ch = getc(stdin);
	while (isspace(ch))
		ch = getc(stdin);
	if (ch == EOF)
		return -1;
	if (ch == '\n')
		return 0;
	i = 0;
	while (!isspace(ch)) {
		if (++i > MAXWORD) {
			printf("error: word too long.\n");
			exit(1);
		}
		*word++ = ch;
		ch = getc(stdin);
	}
	return i;
}

对于dict2,除了包含一些来自最初应用程序的一些过程外(它们将成为远程程序的一部分),还包含了对各个过程要共享的全局数据的声明。在这里,文件中并没有包含完整的程序------剩下的代码将在以后加上

/* dict2.c - initw, insertw, deletew, lookupw */

#include <string.h>

#define	MAXWORD	50		/* maximum length of a command or word	*/
#define DICTSIZ 100		/* maximum number of entries in diction.*/
char	dict[DICTSIZ][MAXWORD+1];/* storage for a dictionary of words	*/
int	nwords = 0;		/* number of words in the dictionary	*/

/*------------------------------------------------------------------------
 * initw - initialize the dictionary to contain no words at all
 *------------------------------------------------------------------------
 */
int initw()
{
	nwords = 0;
	return 1;
}

/*------------------------------------------------------------------------
 * insertw - insert a word in the dictionary
 *------------------------------------------------------------------------
 */
int insertw(char *word)
{
	strcpy(dict[nwords], word);
	nwords++;
	return nwords;
}

/*------------------------------------------------------------------------
 * deletew - delete a word from the dictionary
 *------------------------------------------------------------------------
 */
int deletew(char *word)
{
	int	i;

	for (i=0 ; i<nwords ; i++)
		if (strcmp(word, dict[i]) == 0) {
			nwords--;
			strcpy(dict[i], dict[nwords]);
			return 1;
		}
	return 0;
}

/*------------------------------------------------------------------------
 * lookupw - look up a word in the dictionary
 *------------------------------------------------------------------------
 */
int lookupw(char *word)
{
	int	i;

	for (i=0 ; i<nwords ; i++)
		if (strcmp(word, dict[i]) == 0)
			return 1;
	return 0;
}

从实际的观点来看,将应用程序分为两个文件使得分别编译客户和服务器称为可能。编译器检测比如符号常量之类的问题,而链接将检查所有数据结构是否同引用它们的过程结合在一起。在Linux中,命令:

cc -c dict1.c
cc -c dict2.c

产生两个构建的目标文件(并不是完整的程序)。这些构件必须连接在一起以产生可执行的程序,但这不是编译它们的直接原因:编译器检测这两个文件是否语法正确

步骤3:创建rpcgen规约

程序员一旦为某个分布式程序选择了一种结构,就可以准备rpc规约了。从本质上来说,rpcgen规约文件包含了对远程程序的声明以及它所使用的数据结构。

该规约文件包含常量、类型定义以及对客户和服务器程序的声明。更明确的说就是,这个规约文件包含:

  • 声明在客户或者服务器(远程程序)中所使用的常量
  • 声明所使用的数据类型(特别是对远程过程的参数)
  • 声明远程程序、每个程序中所包含的过程以及它们的参数的类型

我们知道rpc使用一些数字来表示远程程序以及在这些程序中的远程过程。在规约文件中的程序声明了比如程序的RPC号、版本号以及分配给程序中的过程的编号等细节

所有这些声明必须用RPC编程语言来给出,而不是用C。尽管它们的区别是微小的,但可能会称为障碍。比如,RPC程序用关键字string代表null结束的字符串,而C用char *来表示

文件rdict.x声明了一个rpcgen规约,如下:

/* dict.x */

/* 字典程序的 RPC 声明  */

const	MAXWORD = 50;		       /* 命令或单词的最大长度 	*/
const	DICTSIZ = 100;		/* 字典中的条目数 	*/


struct example {		/* unused structure declared here to	*/
	int	exfield1;	/* 说明 rpcgen 如何构建 XDR 	*/
	char	exfield2;	/* 转换结构的例程。 	*/
};


/*------------------------------------------------------------------------
 * RDICTPROG - 提供插入、删除和查找的远程程序 
 *------------------------------------------------------------------------
 */
program RDICTPROG {		/* 远程程序名称(未使用) 	*/
    version RDICTVERS {		/* 版本声明(见下文) 	*/
	int INITW(void)     = 1;/* 这个程序的第1个程序 	*/ 
	int INSERTW(string) = 2;/* 这个程序的第2个程序 	*/
	int DELETEW(string) = 3;/* 这个程序的第3个程序 	*/
	int LOOKUPW(string) = 4; /* 这个程序的第4个程序 	*/
    } = 1;			        /* 程序版本定义 	*/
} = 0x30090949;			/* 远程程序编号(必须是唯一的) 		*/
			

一个rpcgen规约文化并没有囊括在最初的程序中所能找到的所有声明。它仅仅定义了那些在客户和服务器之间要共享的常量和数据类型,或者是那些需要指明的参数。

这个规约的例子由定义常量MAXWORD和DICTSIZE开始。在最初的应用中,这两个常量都是用C的预处理语句define定义的符号常量。RPC不适用C的符号常量声明,而是要求符号常量用关键字const声明,赋值时使用=

按照约定,规约文件使用大写的名字来定义过程和程序。正如我们下面将看到的,这些名字可以称为在C程序中使用的符号常量(最好大写)

步骤4:运行rpcgen

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

$ ls
rdict.x
$ rpcgen rdict.x 
$ ls
rdict_clnt.c  rdict.h  rdict_svc.c  rdict.x  rdict_xdr.c

rpcgen产生的.h文件

/*
 * Please do not edit this file.
 * It was generated using rpcgen.
 */

#ifndef _RDICT_H_RPCGEN
#define _RDICT_H_RPCGEN

#include <rpc/rpc.h>


#ifdef __cplusplus
extern "C" {
#endif

#define MAXWORD 50
#define DICTSIZ 100

struct example {
	int exfield1;
	char exfield2;
};
typedef struct example example;

#define RDICTPROG 0x30090949
#define RDICTVERS 1

#if defined(__STDC__) || defined(__cplusplus)
#define INITW 1
extern  int * initw_1(void *, CLIENT *);
extern  int * initw_1_svc(void *, struct svc_req *);
#define INSERTW 2
extern  int * insertw_1(char **, CLIENT *);
extern  int * insertw_1_svc(char **, struct svc_req *);
#define DELETEW 3
extern  int * deletew_1(char **, CLIENT *);
extern  int * deletew_1_svc(char **, struct svc_req *);
#define LOOKUPW 4
extern  int * lookupw_1(char **, CLIENT *);
extern  int * lookupw_1_svc(char **, struct svc_req *);
extern int rdictprog_1_freeresult (SVCXPRT *, xdrproc_t, caddr_t);

#else /* K&R C */
#define INITW 1
extern  int * initw_1();
extern  int * initw_1_svc();
#define INSERTW 2
extern  int * insertw_1();
extern  int * insertw_1_svc();
#define DELETEW 3
extern  int * deletew_1();
extern  int * deletew_1_svc();
#define LOOKUPW 4
extern  int * lookupw_1();
extern  int * lookupw_1_svc();
extern int rdictprog_1_freeresult ();
#endif /* K&R C */

/* the xdr functions */

#if defined(__STDC__) || defined(__cplusplus)
extern  bool_t xdr_example (XDR *, example*);

#else /* K&R C */
extern bool_t xdr_example ();

#endif /* K&R C */

#ifdef __cplusplus
}
#endif

#endif /* !_RDICT_H_RPCGEN */

这个文件包含了在规约问中声明的所有常量和数据类型的C的合法声明。另外,rpcgen还增加了对远程过程的定义。

这里需要解释一下的是rdict.h中的外部声明。这个被声明的过程构成了客户端的stub的接口部分。过程名取自业已声明过的过程名,只是将它们映射为小写,并附加上一个下换线和程序的版本号。要理解为什么声明这些接口例程,我们可以回顾stub接口部分的目的:它允许rpggen选择自己的调用约定,而又可以让最初的调用过程保持不变

作为接口stub命名的例子,比如过程insertw。最初的过程将成为服务器的一部分,而且将保持不变。这样,服务器将具有名为insertw的过程,它和最初的应用程序具有相同的参数。为避免命名冲突,服务器必须为接口stub过程使用不同的名字。rpcgen让服务器的通信stub代用名为insertw_1的接口过程。该调用使用rpcgen所选择的参数,而且它允许程序员设计insertw_1以便能够用正确的参数调用insertw。

rpcgen产生的XDR转换文件

rpcgen产生了含有一些例程的调用的文件,这些例程执行XDR转换,而且这种调用是针对远程程序中所声明的所有数据类型的。比如,rdict_xdr.c含有一些对转换例程的调用,这是为了转换字典程序中所声明的那些数据结构。

/*
 * Please do not edit this file.
 * It was generated using rpcgen.
 */

#include "rdict.h"

bool_t xdr_example (XDR *xdrs, example *objp)
{
	register int32_t *buf;

	 if (!xdr_int (xdrs, &objp->exfield1))
		 return FALSE;
	 if (!xdr_char (xdrs, &objp->exfield2))
		 return FALSE;
	return TRUE;
}

从上面代码中可以看出,出现在规约文件里的唯一的类型声明被取名为example。它定义了一个结构,该结构含有一个整数字段和一个字符字段。文件rdict_xdr.c含有将结果example在本地数据表示和外部数据表示之间进行转换所需要的代码。这些代码是由rpcgen字段生成的,它为结构中的每个字段调用XDR库中的例程。一旦给出了声明,这个被声明的类型就可以用作给远程过程的参数。如果某个远程过程用结构example作为参数,rpcgen将在客户和服务器中生成代码,以便调用过程xdr_example对数据进行转换

rpcgen产生的客户代码

rdict_clnt.c将成为本程序分布式版中客户端的通信stub:

/*
 * Please do not edit this file.
 * It was generated using rpcgen.
 */

#include <memory.h> /* for memset */
#include "rdict.h"

/* Default timeout can be changed using clnt_control() */
static struct timeval TIMEOUT = { 25, 0 };

int *
initw_1(void *argp, CLIENT *clnt)
{
	static int clnt_res;

	memset((char *)&clnt_res, 0, sizeof(clnt_res));
	if (clnt_call (clnt, INITW,
		(xdrproc_t) xdr_void, (caddr_t) argp,
		(xdrproc_t) xdr_int, (caddr_t) &clnt_res,
		TIMEOUT) != RPC_SUCCESS) {
		return (NULL);
	}
	return (&clnt_res);
}

int *
insertw_1(char **argp, CLIENT *clnt)
{
	static int clnt_res;

	memset((char *)&clnt_res, 0, sizeof(clnt_res));
	if (clnt_call (clnt, INSERTW,
		(xdrproc_t) xdr_wrapstring, (caddr_t) argp,
		(xdrproc_t) xdr_int, (caddr_t) &clnt_res,
		TIMEOUT) != RPC_SUCCESS) {
		return (NULL);
	}
	return (&clnt_res);
}

int *
deletew_1(char **argp, CLIENT *clnt)
{
	static int clnt_res;

	memset((char *)&clnt_res, 0, sizeof(clnt_res));
	if (clnt_call (clnt, DELETEW,
		(xdrproc_t) xdr_wrapstring, (caddr_t) argp,
		(xdrproc_t) xdr_int, (caddr_t) &clnt_res,
		TIMEOUT) != RPC_SUCCESS) {
		return (NULL);
	}
	return (&clnt_res);
}

int *
lookupw_1(char **argp, CLIENT *clnt)
{
	static int clnt_res;

	memset((char *)&clnt_res, 0, sizeof(clnt_res));
	if (clnt_call (clnt, LOOKUPW,
		(xdrproc_t) xdr_wrapstring, (caddr_t) argp,
		(xdrproc_t) xdr_int, (caddr_t) &clnt_res,
		TIMEOUT) != RPC_SUCCESS) {
		return (NULL);
	}
	return (&clnt_res);
}

该文件为远程程序中的每个过程准备好了通信stub过程。就像在服务器中一样,选择过程名字时有意避免了冲突

rpcgen产生的服务器代码

/*
 * Please do not edit this file.
 * It was generated using rpcgen.
 */

#include "rdict.h"
#include <stdio.h>
#include <stdlib.h>
#include <rpc/pmap_clnt.h>
#include <string.h>
#include <memory.h>
#include <sys/socket.h>
#include <netinet/in.h>

#ifndef SIG_PF
#define SIG_PF void(*)(int)
#endif

static void
rdictprog_1(struct svc_req *rqstp, register SVCXPRT *transp)
{
	union {
		char *insertw_1_arg;
		char *deletew_1_arg;
		char *lookupw_1_arg;
	} argument;
	char *result;
	xdrproc_t _xdr_argument, _xdr_result;
	char *(*local)(char *, struct svc_req *);

	switch (rqstp->rq_proc) {
	case NULLPROC:
		(void) svc_sendreply (transp, (xdrproc_t) xdr_void, (char *)NULL);
		return;

	case INITW:
		_xdr_argument = (xdrproc_t) xdr_void;
		_xdr_result = (xdrproc_t) xdr_int;
		local = (char *(*)(char *, struct svc_req *)) initw_1_svc;
		break;

	case INSERTW:
		_xdr_argument = (xdrproc_t) xdr_wrapstring;
		_xdr_result = (xdrproc_t) xdr_int;
		local = (char *(*)(char *, struct svc_req *)) insertw_1_svc;
		break;

	case DELETEW:
		_xdr_argument = (xdrproc_t) xdr_wrapstring;
		_xdr_result = (xdrproc_t) xdr_int;
		local = (char *(*)(char *, struct svc_req *)) deletew_1_svc;
		break;

	case LOOKUPW:
		_xdr_argument = (xdrproc_t) xdr_wrapstring;
		_xdr_result = (xdrproc_t) xdr_int;
		local = (char *(*)(char *, struct svc_req *)) lookupw_1_svc;
		break;

	default:
		svcerr_noproc (transp);
		return;
	}
	memset ((char *)&argument, 0, sizeof (argument));
	if (!svc_getargs (transp, (xdrproc_t) _xdr_argument, (caddr_t) &argument)) {
		svcerr_decode (transp);
		return;
	}
	result = (*local)((char *)&argument, rqstp);
	if (result != NULL && !svc_sendreply(transp, (xdrproc_t) _xdr_result, result)) {
		svcerr_systemerr (transp);
	}
	if (!svc_freeargs (transp, (xdrproc_t) _xdr_argument, (caddr_t) &argument)) {
		fprintf (stderr, "%s", "unable to free arguments");
		exit (1);
	}
	return;
}

int main (int argc, char **argv)
{
	register SVCXPRT *transp;

	pmap_unset (RDICTPROG, RDICTVERS);

	transp = svcudp_create(RPC_ANYSOCK);
	if (transp == NULL) {
		fprintf (stderr, "%s", "cannot create udp service.");
		exit(1);
	}
	if (!svc_register(transp, RDICTPROG, RDICTVERS, rdictprog_1, IPPROTO_UDP)) {
		fprintf (stderr, "%s", "unable to register (RDICTPROG, RDICTVERS, udp).");
		exit(1);
	}

	transp = svctcp_create(RPC_ANYSOCK, 0, 0);
	if (transp == NULL) {
		fprintf (stderr, "%s", "cannot create tcp service.");
		exit(1);
	}
	if (!svc_register(transp, RDICTPROG, RDICTVERS, rdictprog_1, IPPROTO_TCP)) {
		fprintf (stderr, "%s", "unable to register (RDICTPROG, RDICTVERS, tcp).");
		exit(1);
	}

	svc_run ();
	fprintf (stderr, "%s", "svc_run returned");
	exit (1);
	/* NOTREACHED */
}

rdict_svc.c含有服务器所需要的代码。该文件包含服务器在开始执行时要执行的主程序。它包括获得协议端口号、向端口服务器注册RPC程序,接着就等待接收RPC调用。它将每个调用分派给合适的服务器端的stub接口。当被调用的过程响应时,服务器创建RPC应答并将它返回给客户。

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

cc -c rdict_clnt.c
cc -c rdict_svc.c
cc -c rdict_xdr.c

将生成rdict_clnt.o、cc -c rdict_svc.o、rdict_xdr.o文件

步骤5:编写stub接口过程

rpcgen产生的文件并没有构成完整的程序。它还要求程序员必须编写客户端和服务器端的接口例程。在远程调用中的每一个远程过程都必须存在一个接口过程

客户端接口例程

在客户端,原来的主应用程序控制着处理的进行。它调用接口过程,而它所使用的过程名和参数类型与最初的那个程序(非分布式版)调用原过程所使用的完全一样,在分布式版本中,原来这些过程已经成为远程的了。每个接口过程都必须要将它的参数转换为rpcgen所使用的形式,还必须接着调用相应客户端的通信过程。

常规的过程参数和通信sub所使用的参数间的不同在于:rpcgen生成的所有过程的参数都使用间接方式。比如,如果最初的过程有一个整数参数,那么,通信stub中该过程的相应参数必须是指向整数的指针;如果原来是char*,那么在通信sub应该是char**

如下:

/* rdict_cif.c - initw, insertw, deletew, lookupw */

#include <rpc/rpc.h>

#include <stdio.h>

#define RPC_CLNT
#include "rdict.h"

/* Client-side stub interface routines written by programmer */

extern	CLIENT	*handle;		/* handle for remote procedure	*/
static	int	*ret;			/* tmp storage for return code	*/

/*------------------------------------------------------------------------
 * initw - client interface routine that calls initw_1
 *------------------------------------------------------------------------
 */
int initw()
{
	ret = initw_1(0, handle);
	return ret==0 ? 0 : *ret;
}

/*------------------------------------------------------------------------
 * insertw - client interface routine that calls insertw_1
 *------------------------------------------------------------------------
 */
int insertw(char *word)
{
	char	**arg;			/* pointer to argument */
	arg = &word;
	ret = insertw_1(arg, handle);
	return ret==0 ? 0 : *ret;
}

/*------------------------------------------------------------------------
 * deletew - client interface routine that calls deletew_1
 *------------------------------------------------------------------------
 */
int deletew(char *word)
{
	char	**arg;			/* pointer to argument */

	arg = &word;
	ret = deletew_1(arg, handle);
	return ret==0 ? 0 : *ret;
}

/*------------------------------------------------------------------------
 * lookupw - client interface routine that calls lookupw_1
 *------------------------------------------------------------------------
 */
int lookupw(char *word)
{
	char	**arg;			/* pointer to argument */

	arg = &word;
	ret = lookupw_1(arg, handle);
	return ret==0 ? 0 : *ret;
}

服务器端接口例程

在服务器端,接口例程接收来自rpcgen所产生的通信sub的调用,并将控制权传递给实现这个指定调用的过程。如同客户端那样,服务器接口例程必须把参数由rpcgen所选择的类型转换为被调用过程的类型。在大多数场合,这种差异在于一种间接方式------rpcgen传递的是指向对象的指针,而不是对象的本身。为转换一个参数,接口过程只需要只有C的间接运算符(*),如下:

/* rdict_sif.c - init_1, insert_1, delete_1, lookup_1_svc */

#include <rpc/rpc.h>

#define	RPC_SVC
#include "rdict.h"

/* Server-side stub inteface routines written by hand */

static	int retcode;

int	initw(void), insertw(char *), deletew(char *), lookupw(char *);

/*------------------------------------------------------------------------
 * insertw_1_svc -  server side interface to remote procedure insertw
 *------------------------------------------------------------------------
 */
int	*
insertw_1_svc(char **w, struct svc_req *rqstp)
{
	retcode = insertw(*(char **)w);
	return &retcode;
}

/*------------------------------------------------------------------------
 * initw_1_svc -  server side interface to remote procedure initw
 *------------------------------------------------------------------------
 */
int	*
initw_1_svc(void *w, struct svc_req *rqstp)
{
	retcode = initw();
	return &retcode;
}

/*------------------------------------------------------------------------
 * deletew_1_svc -  server side interface to remote procedure deletew
 *------------------------------------------------------------------------
 */
int	*
deletew_1_svc(char **w, struct svc_req *rqstp)
{
	retcode = deletew(*(char **)w);
	return &retcode;
}

/*------------------------------------------------------------------------
 * lookupw_1_svc -  server side interface to remote procedure lookupw
 *------------------------------------------------------------------------
 */
int	*
lookupw_1_svc(char **w, struct svc_req *rqstp)
{
	retcode = lookupw(*(char**)w);
	return &retcode;
}

步骤6:编译和链接客户程序

在客户接口例程编写完成并放入到源文件后,它们就可以被编译了。比如,文件rdict_cif.c 包含了字典例子中的所有接口例程。在Linux中,编译出结果所需要的命令是:

cc -c rdict_cif.c

编译器产生输出文件rdict_cif.o。为完成客户端,程序员需要在最初的主程序中加入一点新的细节,如下:

/* rdict.c - main, nextin */

#include <rpc/rpc.h>

#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>

#include "rdict.h"

#define	MAXWORD	50		/* maximum length of a command or word	*/

#define	RMACHINE	"localhost"	/* name of remote machine	*/
CLIENT	*handle;			/* handle for remote procedure	*/

int	nextin(char *cmd, char *word), initw(), insertw(char *word);
int	deletew(char *word), lookupw(char *word);

/*------------------------------------------------------------------------
 * main - insert, delete, or look up words in a dictionary as specified
 *------------------------------------------------------------------------
 */
int
main(int argc, char *argv[])
{
	char	word[MAXWORD+1]; /* space to hold word from input line	*/
	char	cmd;
	int	wrdlen;		/* length of input word			*/

      /* set up connection for remote procedure call  */
 
       handle = clnt_create(RMACHINE, RDICTPROG, RDICTVERS, "tcp");
       if (handle == 0) {
               printf("Could not contact remote program.\n");
               exit(1);
       }

	while (1) {
		wrdlen = nextin(&cmd, word);
		if (wrdlen < 0)
			exit(0);
		word[wrdlen] = '\0';
		switch (cmd) {
		case 'I':	/* "initialize" */
			initw();
			printf("Dictionary initialized to empty.\n");
			break;
		case 'i':	/* "insert" */
			insertw(word);
			printf("%s inserted.\n",word);
			break;
		case 'd':	/* "delete" */
			if (deletew(word))
				printf("%s deleted.\n",word);
			else
				printf("%s not found.\n",word);
			break;
		case 'l':	/* "lookup" */
			if (lookupw(word))
				printf("%s was found.\n",word);
			else
				printf("%s was not found.\n",word);
			break;
		case 'q':	/* quit */
			printf("program quits.\n");
			exit(0);
		default:	/* illegal input */
			printf("command %c invalid.\n", cmd);
			break;
		}
	}
}

/*------------------------------------------------------------------------
 * nextin - read a command and (possibly) a word from the next input line
 *------------------------------------------------------------------------
 */
int
nextin(char *cmd, char *word)
{
	int	i, ch;

	ch = getc(stdin);
	while (isspace(ch))
		ch = getc(stdin);
	if (ch == EOF)
		return -1;
	*cmd = (char) ch;
	ch = getc(stdin);
	while (isspace(ch))
		ch = getc(stdin);
	if (ch == EOF)
		return -1;
	if (ch == '\n')
		return 0;
	i = 0;
	while (!isspace(ch)) {
		if (++i > MAXWORD) {
			printf("error: word too long.\n");
			exit(1);
		}
		*word++ = ch;
		ch = getc(stdin);
	}
	return i;
}

对比dict1.c,可以看出我们只加入了很少的代码。这个例子使用符号常量RMACHINE来指定远程机器中的域名。

在这里插入图片描述
clnt_create尝试向某台指定的远程机器建立连接。如果连接的尝试失败,clnt_create将返回NULL,然后整个程序就会退出。

客户程序声明并初始化了一个句柄handle,RPC通信例程用该句柄和服务器通信。

编译方法:

cc -c rdict.c

在rdict.c的目标被编译出来后,构成客户的所有文件可以被链接成可执行的程序。Linux cc 命令为.o文件调用的构成

cc -o rdict rdict.o rdict_clnt.o rdict_xdr.o rdict_cif.o

步骤7:编译和连接服务器程序

rpcgen所生成的输出包含了服务器所需要的大多数代码。程序员提供两个附加的文件:服务器接口例程(rdict_sif.c)和调用过程本身。对于字段例子来说,远程过程的代码(最终版本)来自最初的应用:

/* rdict_srp.c - initw, insertw, deletew, lookupw */

#include <rpc/rpc.h>

#include <string.h>

#include "rdict.h"

/* Server-side remote procedures and the global data they use */

char	dict[DICTSIZ][MAXWORD+1];/* storage for a dictionary of words	*/
int	nwords = 0;		/* number of words in the dictionary	*/

/*------------------------------------------------------------------------
 * initw - initialize the dictionary to contain no words at all
 *------------------------------------------------------------------------
 */
int
initw()
{
	nwords = 0;
	return 1;
}

/*------------------------------------------------------------------------
 * insertw - insert  a word in the dictionary
 *------------------------------------------------------------------------
 */
int
insertw(char *word)
{
	strcpy(dict[nwords], word);
	nwords++;
	return nwords;
}

/*------------------------------------------------------------------------
 * deletew - delete  a word from the dictionary
 *------------------------------------------------------------------------
 */
int
deletew(char *word)
{
	int	i;

	for (i=0 ; i<nwords ; i++)
		if (strcmp(word, dict[i]) == 0) {
			nwords--;
			strcpy(dict[i], dict[nwords]);
			return 1;
		}
	return 0;
}

/*------------------------------------------------------------------------
 * lookupw - look up a word in the dictionary
 *------------------------------------------------------------------------
 */
int
lookupw(char *word)
{
	int	i;

	for (i=0 ; i<nwords ; i++)
		if (strcmp(word, dict[i]) == 0)
			return 1;
	return 0;
}

编译方法:

cc -c rdict_srp.c

链接:

cc -o rdictd rdict_srv.o rdict_sif.o rdict_xdr.o rdict_srp.o

步骤8:启动服务器和执行客户

在客户试图联系服务器之前,服务必须开始执行。否则,客户将会显示:

could not contact remote program

并停止执行。在Linux中,后台启动服务器命令:

./rdictd &
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值