分类: LINUX
2014-09-25 15:27:08
概述:
所有关于原理的部分以后再贴,这里直奔程序设
计而去。文章中的程序就是书中的程序,但原文
针对Xinu系统来的,要不就针对Sun RPC来的,
我这里只有Redhat,改动是一定的,在后面的小
节里我会指出改动过的地方。完整地演习过这个
系列,你就不在畏惧RPC。计划在后面的灌水中
讲述RPC远程过程调用发生缓冲区溢出的原理,
不过仅仅是计划,要看时间是否允许。
下面的程序完成一个字典的常规维护工作,代码
很简单,让我们开始。
测试:
RedHat6.0测试,如果在solaris下,应该更容易实现,
因为Sun RPC是事实上的标准,rpcgen是Sun自己的工
具嘛。
目录:
★ 构建一个解决问题的常规应用程序
★ 将该常规程序划分成两部分
★ 创建一个rpcgen规格说明
★ 运行rpcgen
★ rpcgen产生的.h文件
★ rpcgen产生的XDR转换文件
★ rpcgen产生的client代码
★ rpcgen产生的server代码
★ 编写stub接口过程
★ 编译链接client程序
★ 编译链接server程序
★ 启动服务器执行客户机
★ 分离服务器和客户机
★ rpcinfo的使用以及portmap原理简介(重要)
★ RPC程序编译开关
★ RPC编程小结
★ 构建一个解决问题的常规应用程序
下面这个程序很简单,实现一个字典的简单维护工作,不多解释了。
/* dict.c -- main, initw, nextin, insertw, deletew, lookupw */
#include
#include
#include
#include
#define MAXWORD 50 /* maximum length of a command or word */
#define DICTSIZ 100 /* maximum number of entries in dictionary. */
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 );
int initw ( void );
int insertw ( const char * word );
int deletew ( const char * word );
int lookupw ( const char * word );
/* ------------------------------------------------------------------
* main -- insert, delete, or lookup 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 wordlen; /* length of input word */
printf( "Please input:\n" );
while ( 1 )
{
wordlen = nextin( &cmd, word );
if ( wordlen < 0 )
{
exit( 0 );
}
switch ( cmd )
{
case 'I': /* 初始化 */
initw();
printf( "Dictionary initialized to empty.\n" );
break;
case 'i': /* 插入 */
insertw( word );
printf( "%s inserted.\n", word );
break;
case 'd': /* 删除 */
if ( deletew( word ) )
{
printf( "%s deleted.\n", word );
}
else
{
printf( "%s not found.\n", word );
}
break;
case 'l': /* 查询 */
if ( lookupw( word ) )
{
printf( "%s was found.\n", word );
}
else
{
printf( "%s was not found.\n", word );
}
break;
case 'q': /* 退出 */
printf( "Program quits.\n" );
exit( 0 );
break;
default: /* 非法输入 */
printf( "command %c invalid.\n", cmd );
break;
} /* end of switch */
} /* end of while */
return 0;
} /* end of main */
/* ------------------------------------------------------------------
* 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 );
} /* end of while */
if ( ch == EOF )
{
return( -1 );
}
*cmd = ( char )ch;
ch = getc( stdin );
while ( isspace( ch ) )
{
ch = getc( stdin );
} /* end of while */
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 );
} /* end of while */
*word = '\0'; /* 原来的代码这里有问题 */
return i;
} /* end of nextin */
/* ------------------------------------------------------------------
* initw -- initialize the dictionary to contain no words at all
* ------------------------------------------------------------------ */
int initw ( void )
{
nwords = 0;
return 1;
} /* end of initw */
/* ------------------------------------------------------------------
* insertw -- insert a word in the dictionary
* ------------------------------------------------------------------ */
int insertw ( const char * word )
{
strcpy( dict[nwords], word );
nwords++;
return( nwords );
} /* end of insertw */
/* ------------------------------------------------------------------
* 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 );
}
} /* end of for */
return( 0 );
} /* end of deletew */
/* ------------------------------------------------------------------
* 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 );
}
} /* end of for */
return( 0 );
} /* end of lookupw */
[scz@ /home/scz/src]> cat > dict.c
[scz@ /home/scz/src]> gcc -Wall -O3 -o dict dict.c
[scz@ /home/scz/src]> strip dict
[scz@ /home/scz/src]> ./dict
Please input:
II < -- -- -- 原来的例子,怀疑作者并没有实际测试过,这里有点问题
Dictionary initialized to empty.
i word1
word1 inserted.
i word2
word2 inserted.
i word3
word3 inserted.
l word2
word2 was found.
d word2
word2 deleted.
l word2
word2 was not found.
qq < -- -- -- 问题同上,请仔细阅读nextin()函数的代码
Program quits.
[scz@ /home/scz/src]>
现在我们拥有了一个解决的的常规程序,该程序不是分布式的。
★ 将该常规程序划分成两部分
下图是常规程序的函数关系图。
main ---- nextin
|
|
---- insertw
|
|
---- initw
|
|
---- deletew
|
|
---- lookupw
nextin用于读取下一个输入行,需要访问标准输入stdin,应该和main函数放在一起。
原则:执行I/O或者访问了文件句柄的过程不能轻易转移到远程主机上。
lookupw需要访问全部单词数据库,如果执行lookupw的主机和字典所在主机不是同一主机,
则对lookupw的RPC调用就必须将整个字典作为参数传递,这是不可取的。
原则:执行过程的主机应该和过程执行中需访问数据所在主机一致。
于是可以按照如下图示划分远程过程:
client端 server端
发起RPC远程过程调用端 响应RPC远程过程调用端
------------ -------------------------------------
| | RPC调用 | |
| main -|----------------| initw lookupw |
| | | 字典数据结构 |
| nextin | | insertw deletew |
| | | |
------------ -------------------------------------
/* dict1.c -- main, nextin */
#include
#include
#define MAXWORD 50 /* maximum length of a command or word */
/* ------------------------------------------------------------------
* main -- insert, delete, or lookup 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 wordlen; /* length of input word */
printf( "Please input:\n" );
while ( 1 )
{
wordlen = nextin( &cmd, word );
if ( wordlen < 0 )
{
exit( 0 );
}
switch ( cmd )
{
case 'I': /* 初始化 */
initw();
printf( "Dictionary initialized to empty.\n" );
break;
case 'i': /* 插入 */
insertw( word );
printf( "%s inserted.\n", word );
break;
case 'd': /* 删除 */
if ( deletew( word ) )
{
printf( "%s deleted.\n", word );
}
else
{
printf( "%s not found.\n", word );
}
break;
case 'l': /* 查询 */
if ( lookupw( word ) )
{
printf( "%s was found.\n", word );
}
else
{
printf( "%s was not found.\n", word );
}
break;
case 'q': /* 退出 */
printf( "Program quits.\n" );
exit( 0 );
break;
default: /* 非法输入 */
printf( "command %c invalid.\n", cmd );
break;
} /* end of switch */
} /* end of while */
return 0;
} /* end of main */
/* ------------------------------------------------------------------
* 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 );
} /* end of while */
if ( ch == EOF )
{
return( -1 );
}
*cmd = ( char )ch;
ch = getc( stdin );
while ( isspace( ch ) )
{
ch = getc( stdin );
} /* end of while */
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 );
} /* end of while */
*word = '\0';
return i;
} /* end of nextin */
*******************************************************************************
/* dict2.c -- initw, insertw, deletew, lookupw */
#define MAXWORD 50 /* maximum length of a command or word */
#define DICTSIZ 100 /* maximum number of entries in dictionary. */
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 ( void )
{
nwords = 0;
return 1;
} /* end of initw */
/* ------------------------------------------------------------------
* insertw -- insert a word in the dictionary
* ------------------------------------------------------------------ */
int insertw ( const char * word )
{
strcpy( dict[nwords], word );
nwords++;
return( nwords );
} /* end of insertw */
/* ------------------------------------------------------------------
* 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 );
}
} /* end of for */
return( 0 );
} /* end of deletew */
/* ------------------------------------------------------------------
* 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 );
}
} /* end of for */
return( 0 );
} /* end of lookupw */
注意,对于符号常量MAXWORD的定义在两边都出现了。
[scz@ /home/scz/src]> cat > dict1.c
[scz@ /home/scz/src]> cat > dict2.c
[scz@ /home/scz/src]> gcc -O3 -o dict1.o -c dict1.c
[scz@ /home/scz/src]> gcc -O3 -o dict2.o -c dict2.c
此时进行部分编译(-c选项),可以提前修正很多语法错误,避免程序员的注意
力从RPC上移开。这里的两部分代码不构成完整的应用,剩下的代码以后增加。
★ 创建一个rpcgen规格说明
这个规格说明文件包括:
. 声明在client或者(这更常见)server(远程程序)中所使用的常量
. 声明所使用的数据类型(特别是对远程过程的参数)
. 声明远程程序、每个程序中所包含的过程、以及它们的参数类型
RPC使用一些数字来标识远程程序以及在这些程序中的远程过程。在规格说明
文件中的程序声明定义了诸如程序的RPC号、版本号、以及分配给程序中的过
程的编号等等。所有这些声明都必须用RPC编程语言给出,而不是用C。在RPC
中string代表以null结束的字符串,而C用char *表示,必须注意这些细微的
差别。
文件rdict.x给出了一个rpcgen规格说明,包含了字典程序之RPC版的声明。
/* rdict.x */
/* RPC declarations for dictionary program */
const MAXWORD = 50; /* maximum length of a command or word */
const DICTSIZ = 100; /* number of entries in dictionary */
struct example /* unused structure declared here to */
{
int exfield1; /* illustrate how rpcgen builds XDR */
char exfield2; /* routines to convert structures */
};
/* ------------------------------------------------------------------
* RDICTPROG -- remote program that provides insert, delete, and lookup
* ------------------------------------------------------------------ */
program RDICTPROG /* name of remote program ( not used ) */
{
version RDICTVERS /* declaration of version ( see below ) */
{
int INITW ( void ) = 1; /* first procedure in this program */
int INSERTW ( string ) = 2; /* second procedure in this program */
int DELETEW ( string ) = 3; /* third procedure in this program */
int LOOKUPW ( string ) = 4; /* fourth procedure in this program */
} = 1; /* definition of the program version */
} = 0x30090949; /* remote program number ( must be unique ) */
一个rpcgen规格说明文件并没有囊括在最初的程序中的所能找到的所有声明,仅仅
定义了那些在client和server之间要共享的常量和数据类型,或者是那些需要指明
的参数。
按照约定,规格说明文件使用大写名字定义过程和程序,并不绝对要求使用大写,
但这样做有助于避免冲突。
★ 运行rpcgen
[scz@ /home/scz/src]> cat > rdict.x
[scz@ /home/scz/src]> rpcgen rdict.x
[scz@ /home/scz/src]> ls -l rdict*
-rw-r--r-- 1 scz users 1559 Feb 17 17:18 rdict.h
-rw-r--r-- 1 scz users 1138 Feb 17 17:18 rdict.x
-rw-r--r-- 1 scz users 1466 Feb 17 17:18 rdict_clnt.c
-rw-r--r-- 1 scz users 2623 Feb 17 17:18 rdict_svc.c
-rw-r--r-- 1 scz users 297 Feb 17 17:18 rdict_xdr.c
[scz@ /home/scz/src]>
rpcgen将生成四个文件,分别是rdict.h, rdict_clnt.c, rdict_svc.c和rdict_xdr.c。
注意生成的四个文件的名字与rpcgen的规格说明文件名相关。
★ rpcgen产生的.h文件
[scz@ /home/scz/src]> cat rdict.h
/*
* Please do not edit this file.
* It was generated using rpcgen.
*/
#ifndef _RDICT_H_RPCGEN
#define _RDICT_H_RPCGEN
#include
#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 */
[scz@ /home/scz/src]>
该文件包含了在规格说明文件中所声明的所有常量和数据类型的C的合法声明。
此外rpcgen增加了对远程过程的定义。
#define INSERTW 2
extern int * insertw_1(char **, CLIENT *);
extern int * insertw_1_svc(char **, struct svc_req *);
过程名insertw_1取自业已声明过的过程名INSERTW,只是被转换成小写,并附
加了一个下划线和程序的版本号1。insertw_1对应client端的stub通信例程,
client端的stub接口例程需要程序员自己编写。insertw_1_svc对应server端的
stub接口例程,需要程序员自己编写。server端的stub通信例程已经由rpcgen
产生的代码提供了。
scz注:这里与原书中有重要区别,请仔细对比P234(第2版 vol III)开始的
章节。如果照搬,会失败。
server端的stub通信例程调用名为insertw_1_svc的stub接口例程,该调用使
用rpcgen所选择的参数。允许程序员适当设计insertw_1_svc以便能用正确的
参数调用原来常规的insertw。
★ rpcgen产生的XDR转换文件
[scz@ /home/scz/src]> cat 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 long *buf;
if (!xdr_int (xdrs, &objp->exfield1))
return FALSE;
if (!xdr_char (xdrs, &objp->exfield2))
return FALSE;
return TRUE;
}
[scz@ /home/scz/src]>
rpcgen产生了一个含有对一些例程调用的文件,这些例程执行XDR转换,
而这种调用是针对远程程序中所声明的所有数据类型的。
我们的例子中唯一的类型声明被取名为example,它定义了一个结构,
文件rdict_xdr.c含有将结构example在本地数据表示和外部数据表示
之间进行转换所需要的代码,这些代码是由rpcgen自动生成的,它为
结构中的每个字段调用XDR库例程。一旦给出了一个声明,这个被声明
的数据类型就可以用做远程过程的参数。如果某个远程过程确实使用
结构example作为参数,rpcgen将在client和server中生成代码,以便
调用过程xdr_example对数据表示进行转换。
★ rpcgen产生的client代码
[scz@ /home/scz/src]> cat rdict_clnt.c
/*
* Please do not edit this file.
* It was generated using rpcgen.
*/
#include /* 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);
}
[scz@ /home/scz/src]>
rdict_clnt.c是个源程序,它将成为本程序分布式版中client端的tub通信例程。
该文件为调用远程程序中的每个远程过程准备好了一个client端的stub通信例程。
这个文件中的代码很有意思,在系列文章的后续部分我们会回头来研究它们,
尤其是clnt_call()函数的使用。现在暂且就这样放到一边去。
★ rpcgen产生的server代码
[scz@ /home/scz/src]> cat rdict_svc.c
/*
* Please do not edit this file.
* It was generated using rpcgen.
*/
#include "rdict.h"
#include
#include
#include
#include
#include
#include
#include
#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: