RPC编程初战

原    作: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:
                (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, _xdr_argument, (caddr_t) &argument)) {
                svcerr_decode (transp);
                return;
        }
        result = (*local)((char *)&argument, rqstp);
        if (result != NULL && !svc_sendreply(transp, _xdr_result, result)) {
                svcerr_systemerr (transp);
        }
        if (!svc_freeargs (transp, _xdr_argument, (caddr_t) &argument)) {
                fprintf (stderr, "/nunable to free arguments/n");
                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, "/ncannot create udp service./n");
                exit(1);
        }
        if (!svc_register(transp, RDICTPROG, RDICTVERS, rdictprog_1, IPPROTO_UDP)) {
                fprintf (stderr, "/nunable to register (RDICTPROG, RDICTVERS, udp)./n");
                exit(1);
        }
        transp = svctcp_create(RPC_ANYSOCK, 0, 0);
        if (transp == NULL) {
                fprintf (stderr, "/ncannot create tcp service./n");
                exit(1);
        }
        if (!svc_register(transp, RDICTPROG, RDICTVERS, rdictprog_1, IPPROTO_TCP)) {
                fprintf (stderr, "/nunable to register (RDICTPROG, RDICTVERS, tcp)./n");
                exit(1);
        }
        svc_run ();
        fprintf (stderr, "/nsvc_run returned/n");
        exit (1);
        /* NOTREACHED */
}
[scz@ /home/scz/src]>
rdict_svc.c包含有server在开始时要执行的主程序,包括获得一个协议端口号,
象端口映射器(portmap/rpcbind)注册RPC程序,接着便等待接收RPC调用。它将每
个调用分派给合适的server端的stub接口例程,当被调用的远程过程响应时,
server创建一个RPC应答并将它发送回client。
scz注:虽然rdict_svc.c的注释中建议你不要修改它,但我必须告诉你,如果你不把
       fprintf (stderr, ...)语句中增加适当的/n的话,会很不友好地得到那些
       出错信息。所以我已经增加了不少/n进去,你得到的代码一定没有这些额外的
       /n,这只能说是rpcgen的愚蠢,后面我们会注意到这个问题。
[scz@ /home/scz/src]> gcc -O3 -o rdict_clnt.o -c rdict_clnt.c
[scz@ /home/scz/src]> gcc -O3 -o rdict_svc.o -c rdict_svc.c
[scz@ /home/scz/src]> gcc -O3 -o rdict_xdr.o -c rdict_xdr.c
★ 编写stub接口例程
rpcgen产生的文件并没有构成完整的程序,要求程序员必须编写client和server
端的stub接口例程,在远程程序中的每个远程过程都必须存在一个stub接口例程。
/* rdict_cif.c -- initw, insertw, deletew, lookupw */
#include
#include
#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 ( void )
{
    ret = initw_1( 0, handle );
    return( ret == 0 ? 0 : *ret );
}  /* end of initw */
/* ------------------------------------------------------------------
* 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 );
}  /* end of insertw */
/* ------------------------------------------------------------------
* 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 );
}  /* end of deletew */
/* ------------------------------------------------------------------
* 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 );
}  /* end of lookupw */
client端stub接口例程lookupw所使用参数与client端stub通信例程lookupw_1所使用
参数之间的主要不同在于,后者使用指针。
*******************************************************************************
/* rdict_sif.c -- initw_1_svc, insertw_1_svc, deletew_1_svc, lookupw_1_svc */
#include
#include "rdict.h"
/* Server-side stub interface routines written by programmer */
static int retcode;
/* ------------------------------------------------------------------
* initw_1_svc -- server side interface to remote procedure initw
* ------------------------------------------------------------------ */
int * initw_1_svc ( void * v, struct svc_req * s )
{
    retcode = initw();
    return( &retcode );
}  /* end of initw_1_svc */
/* ------------------------------------------------------------------
* insertw_1_svc -- server side interface to remote procedure insertw
* ------------------------------------------------------------------ */
int * insertw_1_svc ( char ** word, struct svc_req * s )
{
    retcode = insertw( *word );
    return( &retcode );
}  /* end of insertw_1_svc */
/* ------------------------------------------------------------------
* deletew_1_svc -- server side interface to remote procedure deletew
* ------------------------------------------------------------------ */
int * deletew_1_svc ( char ** word, struct svc_req * s )
{
    retcode = deletew( *word );
    return( &retcode );
}  /* end of deletew_1_svc */
/* ------------------------------------------------------------------
* lookupw_1_svc -- server side interface to remote procedure lookupw
* ------------------------------------------------------------------ */
int * lookupw_1_svc ( char ** word, struct svc_req * s )
{
    retcode = lookupw( *word );
    return( &retcode );
}  /* end of lookupw_1_svc */
★ 编译链接client程序
因为新版本使用了RPC,程序需要包含以及"rdict.h",后者包含了
client和server都要使用的常量的定义。client程序还需要声明并初始化一个
句柄(handle),RPC通信例程使用该句柄与server通信。多数客户机使用已经定
义的类型CLIENT声明这个句柄,调用RPC库例程clnt_create来初始化这个句柄。
/* rdict.c -- main, nextin */
#include
#include
#include
#include
#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 );
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 */
    /* 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 );
    }
    /* 上面是分布式版字典程序client端增加的代码,与dict1.c做比较 */
    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 */
比较rdict.c和dict1.c,显然我们只增加了很少的代码。程序中使用RMACHINE指明远程
主机名,为了方便测试,该符号常量已经定义成localhost,意味着client与server
将在同一台主机上运行。测试完毕后程序员可以改变定义。
clnt_create企图向指定远程主机建立连接,失败的话返回NULL。实际中,不一定要立即
退出,可以重试,可以维护一个远程主机表挨个尝试。
[scz@ /home/scz/src]> cat > rdict.c
[scz@ /home/scz/src]> gcc -O3 -o rdict.o -c rdict.c
[scz@ /home/scz/src]> cat > rdict_cif.c
[scz@ /home/scz/src]> gcc -O3 -o rdict_cif.o -c rdict_cif.c
[scz@ /home/scz/src]> gcc -O3 -o rdict rdict.o rdict_clnt.o rdict_xdr.o rdict_cif.o
[scz@ /home/scz/src]> strip rdict
[scz@ /home/scz/src]> ./rdict
Could not contact remote program.
[scz@ /home/scz/src]>
★ 编译链接server程序
[scz@ /home/scz/src]> cat > rdict_sif.c
[scz@ /home/scz/src]> gcc -O3 -o rdict_sif.o -c rdict_sif.c
/* rdict_srp.c -- initw, insertw, deletew, lookupw */
#include
#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 ( 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 > rdict_srp.c
[scz@ /home/scz/src]> gcc -O3 -o rdict_srp.o -c rdict_srp.c
[scz@ /home/scz/src]> gcc -O3 -o rdictd rdict_srp.o rdict_svc.o rdict_xdr.o rdict_sif.o
★ 启动服务器执行客户机
[scz@ /home/scz/src]> ./rdictd &
[1] 553
[scz@ /home/scz/src]> ./rdict
Please input:
II
Dictionary initialized to empty.
i hellow
hellow inserted.
i orld
orld inserted.
l hellow
hellow was found.
d hellow
hellow deleted.
qq
Program quits.
[scz@ /home/scz/src]> kill %1
[scz@ /home/scz/src]>
★ 分离服务器和客户机
修改rdict.c中的 #define RMACHINE "localhost"  /* name of remote machine */
我测试的时候rdictd运行在server上,rdict运行在higgs上,所以改成了
#define RMACHINE "server"  /* name of remote machine */
重新编译生成rdict,然后用sftp上传到higgs上,ssh到higgs,chmod +x rdict,运
行rdict的效果和前面是一样的。至此,一个完整的可以测试使用的真正意义上的RPC
程序完成了,包括client端和server端。
★ rpcinfo的使用以及portmap原理简介(重要)
现在让我们从higgs上来看看server
[scz@ /home/scz/src]> /usr/sbin/rpcinfo
Usage: rpcinfo [ -n portnum ] -u host prognum [ versnum ]
       rpcinfo [ -n portnum ] -t host prognum [ versnum ]
       rpcinfo -p [ host ]
       rpcinfo -b prognum versnum
       rpcinfo -d prognum versnum
[scz@ /home/scz/src]> /usr/sbin/rpcinfo -p server
   program vers proto   port
    100000    2   tcp    111  rpcbind
    100000    2   udp    111  rpcbind
805898569    1   udp   1029
805898569    1   tcp   1036
[scz@ /home/scz/src]>
0x30090949 = 805898569,现在知道rpcinfo的这个program数字是什么了吧,
就是远程程序号呀。后面的1就是远程程序版本号。tcp表示使用tcp协议作为
底层支持协议。回顾rdict.c中的语句:
handle = clnt_create( RMACHINE, RDICTPROG, RDICTVERS, "tcp" );
第一个参数是远程主机名,第二个参数是远程程序号,第三个参数是远程
程序版本号,第四个参数指定底层支持协议。注意到从higgs上运行rpcinfo
报告出来的结果,说明rdictd同时支持了tcp和udp,此时如果修改rdict.c
中的语句:
handle = clnt_create( RMACHINE, RDICTPROG, RDICTVERS, "udp" );
同样可以达到效果(测试过)。当然,在实际应用中选择tcp还是udp要根据网
络状况具体决定。
rpcinfo报告出来的第四个数据port是什么意思呢,这里简单介绍一下。
因为远程程序号总共32bit,超出了端口的16bit范围,所以不能直接在远程
程序号和端口号之间建立映射,于是产生了动态端口获得的概念。rpc server
通过操作系统获得一个当前空闲的端口号,然后想向端口映射器portmap/rpcbind
注册这种映射关系。当rpc client需要连接某个rpc server的时候,前者只知道
远程程序号和远程程序版本号,于是它向目标主机的知名端口111提供的服务
查询拥有相应远程程序号和远程程序版本号的rpc server当前所注册使用的端口
号,由portmap/rpcbind向rpc client提供这个端口值,当rpc client获得
rpc server所使用的端口值后就可以直接与rpc server通信了。所以,如果上面
rdictd二次启动后用rpcinfo查看,会发现port值改变。从上面简单介绍的理论
中应该明白,rpc server必须向portmap注册,所以如果:
[root@ /home/scz/src]> /etc/rc.d/init.d/portmap status
portmap (pid 539) is running...
[root@ /home/scz/src]> /etc/rc.d/init.d/portmap stop  
Stopping portmap services:                                                     [  OK  ]
[root@ /home/scz/src]>
[scz@ /home/scz/src]> /usr/sbin/rpcinfo -p localhost
rpcinfo: can't contact portmapper: RPC: Remote system error - Connection refused
[scz@ /home/scz/src]> ./rdictd  
Cannot register service: RPC: Unable to receive; errno = Connection refused
unable to register (RDICTPROG, RDICTVERS, udp).
[scz@ /home/scz/src]>
现在你是否对rpc程序执行中的错误提示有了进一步了解呢。如果答案肯定,
那我很高兴。注意这里,如果当初没有修改过rdict_svc.c,最后的错误提示
会让你非常恼火,因为没有回车换行。
通过netconf设置portmap服务在系统启动的时候就开始。如果要临时测试,
就是上面那个办法。有的系统是在/etc/inetd.conf中提供rpcbind服务,
也是111端口,必须注意到这一点。
我这里居然没有rpcinfo的man手册,活见鬼,所以那些命令行选项什么意思
我也不清楚,只能挑我大概知道的给出例子,如果你明白就赶快贴出来好了,
solaris下应该有相应的man手册吧。
下面我们来看看rpcinfo的其他命令行选项:
[scz@ /home/scz/src]> hostname
higgs.*.*.*
[scz@ /home/scz/src]> /usr/sbin/rpcinfo -b 805898569 1
192.168.67.108 server.*.*.*
^C
[scz@ /home/scz/src]>
-b选项似乎是在局域网上探测指定rpc server的存在???
[root@ /home/scz/src]> rpcinfo -d 100000 2      
[root@ /home/scz/src]> rpcinfo -p localhost
No remote programs registered.
[root@ /home/scz/src]> /etc/rc.d/init.d/portmap status
portmap (pid 740) is running...
[root@ /home/scz/src]> ./rdictd &
[root@ /home/scz/src]> jobs
[1]+  Running                 ./rdictd &
[root@ /home/scz/src]> rpcinfo -p localhost
   program vers proto   port
805898569    1   udp    966
805898569    1   tcp    968
[root@ /home/scz/src]> kill %1
[1]+  Terminated              ./rdictd
[root@ /home/scz/src]> jobs
[root@ /home/scz/src]> rpcinfo -p localhost
   program vers proto   port
805898569    1   udp    966
805898569    1   tcp    968
[root@ /home/scz/src]> rpcinfo -d 805898569 1
[root@ /home/scz/src]> rpcinfo -p localhost
No remote programs registered.
[root@ /home/scz/src]>
-d选项看来是反注册用的。注意到rdictd虽然被杀,但动态端口注册信息依
旧存在,需要额外地用rpcinfo -d反注册一下,同样先反注册并不会导致
rdictd终止,这两者没有捆绑。rpcbind被反注册掉不影响什么,至少目前
测试结果如此,也许rpcbind本身注册一次仅仅是保持某种一致性,因为它
使用的是固定的周知端口111,根本不用注册的。
[scz@ /home/scz/src]> ./rdictd &
[1] 798
[scz@ /home/scz/src]> rpcinfo -p localhost
   program vers proto   port
805898569    1   udp   1043
805898569    1   tcp   1054
[scz@ /home/scz/src]>
[scz@ /home/scz/src]> hostname
higgs.*.*.*
[scz@ /home/scz/src]> /usr/sbin/rpcinfo -t server 805898569
program 805898569 version 1 ready and waiting
[scz@ /home/scz/src]> /usr/sbin/rpcinfo -n 1043 -u server 805898569 1
program 805898569 version 1 ready and waiting
[scz@ /home/scz/src]>
上面的例子已经足够清楚,就不多解释了。完全可以写个shell script对某一
网段用rpcinfo扫描某一特定rpc server的存在,当然有很多现成rpc scan工具
存在。这些意味着什么,攻击的前奏。
scz注:你可以在同一台主机上两次执行./rdictd &,分别在本机和异地机上
       检查rpc信息,看看有什么混帐现象。多做测试是个好习惯哟。
★ RPC程序编译开关
[root@ /usr/lib]> ls librpc*
librpcsvc.a  librpcsvc_p.a
[root@ /usr/lib]>
我们前面的例子程序中并没有使用-lrpcsvc或者-lrpcsvc_p开关,后者应该是
多线程版本的链接开关。如果你在编译过程中出现问题,尝试这两个开关,我
也不知道什么时候应该使用它们。sigh
此外必须注意#include
★ RPC编程小结
所有的原理都没有怎么讲述,留待后续系列讲述。这里对RPC编程做一个
简单的小结:
    . 构建一个解决问题的常规程序。
    . 根据某种原则划分开,其中一部分是准备迁移到远程主机上作为远
      程过程调用的。
    . 编写rpcgen规格说明文件
    . 运行rpcgen
    . 为rpc client和rpc server编写各自的stub接口例程
    . 编译连接rpc client和rpc server
rpcgen虽然解决了很多问题,但并非一定要使用rpcgen进行rpc编程。
尤其当一个rpc server是另外一个rpc server的rpc client的时候,
rpcgen的局限性就暴露出来了。
后记:
    正好要做点和RPC/NFS有关的协议分析,于是就
    把上面那本书重新翻出来,照猫画老虎地先测试
    一个完整的RPC程序,给自己一点信心也给大家
    一个免除重复劳动的便宜捡。有任何技术性问题,
    可以< mailto: cloudsky@263.net >,反正我什么
    都不知道,你要教训我正好嘛,:-)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值