发信人: hlzhan(Unfair) 
整理人: hlzhan(2001-10-06 11:02:18), 站内信件
 | 
 
 
RPC/XDR/NFS系列之----RPC编程初战  
 转载:xundi(xundi)  
 来源:整理修改:scz < mailto: [email protected] >  
   
 原    作:Douglas E. Comer & David L. Stevens  
           << Internetworking With TCP/IP Vol III >>  
 整理修改:scz < mailto: [email protected] >  
 概述:  
     所有关于原理的部分以后再贴,这里直奔程序设  
     计而去。文章中的程序就是书中的程序,但原文  
     针对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对数据表示进行转换。  
 
 
  | 
 
 
 |