网页功能: 加入收藏 设为首页 网站搜索  
RPC/XDR/NFS系列之----RPC编程初战
发表日期:2003-08-27作者:[] 出处:  

原  作: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:

       

我来说两句】 【加入收藏】 【返加顶部】 【打印本页】 【关闭窗口
中搜索 RPC/XDR/NFS系列之----RPC编程初战
本类热点文章
  利用鼠标键盘钩子截获密码
  利用鼠标键盘钩子截获密码
  如何将多个文件捆绑成一个可执行文件
  如何将多个文件捆绑成一个可执行文件
  内核级HOOK的几种实现与应用
  内核级HOOK的几种实现与应用
  书写基于内核的linux键盘纪录器
  书写基于内核的linux键盘纪录器
  CIH病毒原码
  CIH病毒原码
  编写进程/线程监视器
  编写进程/线程监视器
最新分类信息我要发布 
最新招聘信息

关于我们 / 合作推广 / 给我留言 / 版权举报 / 意见建议 / 广告投放  
Copyright ©2003-2024 Lihuasoft.net webmaster(at)lihuasoft.net
网站编程QQ群   京ICP备05001064号 页面生成时间:0.00424