| 
原  作:Douglas E. Comer & David L. Stevens      << Internetworking With TCP/IP Vol III >> 整理修改:scz < mailto: cloudsky@263.net > 概述:   所有关于原理的部分以后再贴,这里直奔程序设   计而去。文章中的程序就是书中的程序,但原文   针对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:         |