情報学類 分散システム					2008年01月08日
                                       筑波大学システム情報工学研究科
                                       コンピュータサイエンス専攻, 電子・情報工学系
                                       新城 靖
                                       <yas@is.tsukuba.ac.jp>
このページは、次の URL にあります。
	http://www.coins.tsukuba.ac.jp/~yas/coins/dsys-2007/2008-01-08
あるいは、次のページから手繰っていくこともできます。
	http://www.coins.tsukuba.ac.jp/~yas/
	http://www.cs.tsukuba.ac.jp/~yas/
rpcgen
というコマンド。
rpcgen コマンドを使うには、次のようなファイルを作成する。

図? rpcgenによるRPCプログラム開発で利用するファイル
name.x
name_client.c
name_server.c
% rpcgen name.x 
次の4つのファイルが生成される。
name.h
name_clnt.c
name_xdr.c
name.x で定義したデータ構造について、
XDR
のための手続き(整列化と非整列化を行なう手続き) 。
クライアント側とサーバ側の両方で使われる。
name_svc.c
/etc/rpc
にある。
SunRPCでは, 最終的にはTCP/IPまたはUDP/IPでデータが送られる。プログラム 番号などTCP/IPまたはUDP/IPのポート番号を対応させる必要がある。
各ホストには
portmap (portmapper)
というサーバがいて、3つ組
portmap の情報は、
rpcinfo
 コマンドで表示できる。
% rpcinfo -p 
   program vers proto   port
    100000    2   tcp    111  portmapper
    100000    2   udp    111  portmapper
    100007    2   tcp   1024  ypbind
    100007    2   udp   1027  ypbind
    100007    1   tcp   1024  ypbind
    100007    1   udp   1027  ypbind
    100005    1   udp    831  mountd
    100005    2   udp    831  mountd
    100005    1   tcp    834  mountd
    100005    2   tcp    834  mountd
    100003    2   udp   2049  nfs
    100001    2   udp   4193  rstatd
    100001    3   udp   4193  rstatd
    100001    4   udp   4193  rstatd
% 
他のホストの情報も調べられる。
% rpcinfo -p hostname 
サーバは、起動時に、portmap に登録する。
bool_t pmap_set(program, version, protocol, port) u_long program; u_long version; int protocol; u_short port;クライアントは、呼び出す前に、ポート番号を調べる。
u_short pmap_getport(address, program, version, protocol) struct sockaddr_in *address; u_long program; u_long version; u_int protocol;
スタブが自動的に呼び出すので、普段は気にすることはない。
portmap 自身も RPC で動いている。portmap の自身のポート番号は、111 と 決まっている。
   1:	struct delist {
   2:	        string del_name<> ;
   3:	        struct delist *del_next ;
   4:	};
   5:	
   6:	union dirlist_res
   7:	switch(int dlr_errno)
   8:	{
   9:	case 0:
  10:	        delist *dlr_head;
  11:	default:
  12:	        void;
  13:	};
  14:	
  15:	program DIRLIST_PROG {
  16:	    version DIRLIST_VERSION {
  17:	        dirlist_res DIRLIST(string) = 11 ;
  18:	    } = 1;
  19:	} = 0x20001003 ;
17行目が手続きの定義。
DIRLIST が手続きの名前(手続き番号を示す定数の定義)。
この手続きの引数は、string 型、結果は、dirlist_res 
型。SunRPC では、基本的には引数も結果も1つずつ。複数必要な時には、構造体を
定義する。
手続き DIRLIST の定義を program 番号と
version 番号の括弧が取り囲んでいる。
dirlist_res は、共用体(可変長の構造体、Pascal 可変長レコード)。
struct delist は、自分自身へのポインタ(del_next)を含むリスト構造。
一般的に RPC では、ポインタを含むデータ構造を送ることはできない。 SunRPC では、ポインタの先の1要素を再帰的に送る機能がある。 これにより、木構造や線形リストを送ることができる。
string del_name<> は、文字列型。可変長(<>)で最 大文字数には定められていない(<>の中が空)。
ヘッダファイルの主要部分は、以下の通りである。
struct delist {
	char *del_name;
	struct delist *del_next;
};
typedef struct delist delist;
struct dirlist_res {
	int dlr_errno;
	union {
		delist *dlr_head;
	} dirlist_res_u;
};
typedef struct dirlist_res dirlist_res;
#define DIRLIST_PROG ((u_long)0x20001003)
#define DIRLIST_VERSION ((u_long)1)
#define DIRLIST ((u_long)11)
extern  dirlist_res * dirlist_1(char **, CLIENT *);
extern  dirlist_res * dirlist_1_svc(char **, struct svc_req *);
:	/*
   2:	        dirlist_server.c -- ディレクトリの内容を表示するRPCのプログラム(サーバ側)
   3:	        Created on: 2006/01/18 20:33:31
   4:	*/
   5:	
   6:	#include <sys/types.h>          /* opendir(2) */
   7:	#include <dirent.h>             /* opendir(2) */
   8:	#include <errno.h>              /* errno */
   9:	#include <stdlib.h>             /* malloc() */
  10:	#include <string.h>             /* strlen() */
  11:	#include <stdio.h>              /* snprintf() */
  12:	
  13:	#include "dirlist.h"
  14:	static struct delist *make_delist( DIR *dirp );
  15:	
  16:	dirlist_res *
  17:	dirlist_1_svc(char **argp, struct svc_req *rqstp)
  18:	{
  19:	    static dirlist_res  result;
  20:	    DIR *dirp ;
  21:	    char *dirname ;
  22:	
  23:	        xdr_free((xdrproc_t)xdr_dirlist_res, (char *)&result);
  24:	
  25:	        dirname = *argp ;
  26:	        dirp = opendir( dirname );
  27:	        if( dirp == 0 )
  28:	        {
  29:	            result.dlr_errno = errno ;
  30:	            return( &result );
  31:	        }
  32:	        result.dlr_errno = 0;
  33:	        result.dirlist_res_u.dlr_head = make_delist( dirp );
  34:	        closedir( dirp );
  35:	        return( &result );
  36:	}
  37:	
サーバ側では、手続き dirlist_1_svc() を記述する。
引数と結果は、rpcgen のソースで定義した構造体へのポインタ。
結果を返す時の構造体へのポインタを返す方法が問題。自動変数(auto 変数) にすると、呼出し側に戻った瞬間に無効になる。よく使われるのは、静的変数 (static 変数)に結果を代入して、返すことだが、マルチスレッドプログラミ ングでは大いに問題になる。
opendir(), readdir(), closedir() は、ディレクトリの内容を得るためのラ イブラリ関数。次のようなコードで、"/" の内容が表示される(ls -f /)。
	dirp = opendir( "/" );
	while( (dp = readdir(dirp)) != NULL )
	{
	    printf("%s\n", dp->d_name );
	}
	closedir( dirp );
readdir() は、make_delist() の中で行われる。
  38:	static struct delist *
  39:	make_delist( DIR *dirp )
  40:	{
  41:	    struct dirent *dp ;
  42:	    struct delist *del ;
  43:	    int namelen;
  44:	        if( (dp = readdir(dirp)) == NULL )
  45:	        {
  46:	            return( 0 );
  47:	        }
  48:	        else
  49:	        {
  50:	            del = malloc(sizeof(struct delist));
  51:	            namelen = strlen( dp->d_name );
  52:	            del->del_name = malloc( namelen+1 );
  53:	            snprintf(del->del_name,namelen+1,"%s",dp->d_name );
  54:	            del->del_next = make_delist( dirp );
  55:	            return( del );
  56:	        }
  57:	}
make_delist() は、再帰呼び出しで、struct delist のリストを作る。
自動生成されるサーバのmainプログラムでは、 ポートマッパ(port mapper) へのプログラム番号とバージョン番号の登録される。
   1:	/*
   2:	        dirlist_client.c -- ディレクトリの内容を表示するRPCのプログラム(クライアント側)
   3:	        Created on: 2006/01/18 20:57:49
   4:	*/
   5:	
   6:	#include <stdlib.h>             /* exit() */
   7:	#include <stdio.h>              /* printf() */
   8:	
   9:	#include "dirlist.h"
  10:	
  11:	main( int argc, char *argv[] )
  12:	{
  13:	        if( argc != 3 )
  14:	        {
  15:	            fprintf(stderr,"usage: %% %s server_host dir\n", argv[0]);
  16:	            exit( 1 );
  17:	        }
  18:	        dirlist( argv[1], argv[2] );
  19:	}
  20:	
  21:	dirlist(char *host, char *dir)
  22:	{
  23:	        CLIENT *clnt;
  24:	        dirlist_res  *result;
  25:	        char *arg;
  26:	
  27:	        clnt = clnt_create (host, DIRLIST_PROG, DIRLIST_VERSION, "tcp");
  28:	        if( clnt == NULL )
  29:	        {
  30:	            clnt_pcreateerror( host );
  31:	            exit( 1 );
  32:	        }
  33:	
  34:	        arg = dir;
  35:	        result = dirlist_1( &arg, clnt );
  36:	        if( result == NULL )
  37:	        {
  38:	            clnt_perror( clnt, "call failed");
  39:	            exit( 1 );
  40:	        }
  41:	        print_dirlist_res( result );
  42:	        xdr_free( (xdrproc_t)xdr_dirlist_res, (char *)result );
  43:	        clnt_destroy( clnt );
  44:	}
  45:	
main() の引数は、サーバが動作しているホスト名とディレクトリ。
clnt_create() は、CLIENT 構造体を確保する。
引数は、サーバのホスト名、
プログラム番号、
バージョン番号、
通信に使うプロトコルである。
33行目で呼び出している dirlist_1() が、rpcgen によ
り生成されたスタブ。引数は、インタフェースで定義された構造体
dirlistargへのポインタと、CLIENT 構造体へ
のポインタ。結果は、インタフェースで定義された構造体
dirlistresへのポインタである。この関数は、rpcgen 
が生成する *_clnt.c というファイルに含まれてる。
スタブ dirlist_1()は、成功すると内部で malloc() を呼
び出しメモリを確保して、そこにサーバから受け取った応答メッセージを
非整列化(unmarshalling)
して保存する。このメモリは、利用し終わると xdr_free()で
解放する。
	free( result );
だと、トップレベルの構造体のメモリしか解放されない。内部に含まれている
構造体へのポインタや文字列のメモリを解放するためには、
xdr_free()を呼び出す。
  46:	print_dirlist_res( dirlist_res *result )
  47:	{
  48:	        printf("errno: %d (%s)\n",
  49:	               result->dlr_errno, strerror(result->dlr_errno));
  50:	        switch( result->dlr_errno )
  51:	        {
  52:	        case 0:
  53:	            print_delist( result->dirlist_res_u.dlr_head );
  54:	            break;
  55:	        default:
  56:	            break;
  57:	        }
  58:	}
  59:	
  60:	print_delist( struct delist *del )
  61:	{
  62:	        if( del == NULL )
  63:	        {
  64:	        }
  65:	        else
  66:	        {
  67:	            printf("%s\n",del->del_name );
  68:	            print_delist( del->del_next );
  69:	        }
  70:	}
print_dirlist_res() は、struct dirlist_res を表示する。
print_delist() は、struct delist を表示する。
構造体が再帰している所で、関数も再帰している。
% wget http://www.coins.tsukuba.ac.jp/~yas/coins/dsys-2007/2008-01-08/ex/dirlist.x 
% wget http://www.coins.tsukuba.ac.jp/~yas/coins/dsys-2007/2008-01-08/ex/dirlist_server.c 
% wget http://www.coins.tsukuba.ac.jp/~yas/coins/dsys-2007/2008-01-08/ex/dirlist_client.c 
% wget http://www.coins.tsukuba.ac.jp/~yas/coins/dsys-2007/2008-01-08/ex/Makefile 
% emacs Makefile 
% make 
rpcgen  dirlist.x
gcc -g -DDEBUG    -c -o dirlist_clnt.o dirlist_clnt.c
gcc -g -DDEBUG    -c -o dirlist_client.o dirlist_client.c
rpcgen  dirlist.x
gcc -g -DDEBUG    -c -o dirlist_xdr.o dirlist_xdr.c
gcc -g -DDEBUG     -o dirlist_client  dirlist_clnt.o dirlist_client.o dirlist_xdr.o  
gcc -g -DDEBUG    -c -o dirlist_svc.o dirlist_svc.c
gcc -g -DDEBUG    -c -o dirlist_server.o dirlist_server.c
gcc -g -DDEBUG     -o dirlist_server  dirlist_svc.o dirlist_server.o dirlist_xdr.o 
% 
Solaris, Linux では、-lnsl が必要。Solaris, MacOSX では、-DDEBUG をつ
けてコンパイルとfork() しないでフォアグランドで動くサーバができる。
% ./dirlist_server 
サーバ側は自動的には止まらないので、止めたい時には、^C を押す。
サーバを動かしているホストの別の端末で rpcinfo -p を実行すると、プログ ラム番号536875011 (0x20001003) のプログラムが表示される。
% rpcinfo -p 
   program vers proto   port
    100000    2   tcp    111  portmapper
    100000    2   udp    111  portmapper
    100024    1   udp   1013  status
    100024    1   tcp    974  status
    100021    0   udp   1000  nlockmgr
    100021    1   udp   1000  nlockmgr
    100021    3   udp   1000  nlockmgr
    100021    4   udp   1000  nlockmgr
    100021    0   tcp    973  nlockmgr
    100021    1   tcp    973  nlockmgr
    100021    3   tcp    973  nlockmgr
    100021    4   tcp    973  nlockmgr
 536875011    1   udp  53972
 536875011    1   tcp  60723
% 
MacOSX 10.4 では、portmapp が動作していないことがある。rpcinfo -p で何 も表示されない時には、root で 次のようにして実行する。
launchctl start com.apple.portmap
% ./dirlist_client localhost /usr 
errno: 0 (Unknown error: 0)
.
..
.DS_Store
bin
epkg
include
info
lib
libexec
local
local3
sbin
share
standalone
X11R6
% 
RPCのインタフェースの定義では、定数を記述することができる。
const LSIZE = 80 ;これは、
rpcgen により、C言語のプリプロセッサのマクロ
#defineに置き換えられる。
RPCのインタフェースの定義では、配列を定義することができる。配列には、
固定長と可変長の2種類あり、固定長は、C言語と同じ。可変長は、次のよう
に[]の代わりに<>とする。
int varray<>;
<> の中には、最大長を書くこともできる。これは、
rpcgen により、次のような構造体に置き換えられる。
struct {
	u_int varray_len;
	int *varray_val;
} varray;
typedef struct varray varray ;
C言語でプログラムを作成する時には、この varray_len に要素数を、
varray_val に、配列の先頭のポインタをセットする。
int a1[10] ; varray v1 ; v1.varray_len = 10 ; v1.varray_val = &a1[0] ;文字列を送るには、特殊な
string 型を使う。
string s<> ;これは、C言語では、
char * になるが、文字列のつもりで次のよう
に書いても、文字列は送られない。
char *s ;これも、
rpcgen により、やはり char * にコンパイルされる
が、この場合、ポインタの先の1文字しか送られない。C言語の文字列を送り
たい時には、必ず string を使うこと。
SunRPC ではポインタ型も送ること ができるが、ポインタの先の1要素しか送られない。 複数要素を送りたい時には、配列を使う。
C言語の main の引数と同様の構造を送りたい時には、次のようにする。
typedef string argstr_t<>; typedef argstr_t argvt<>;大量のデータをそのまま送るには、
opaque
型
(
不定形型
)
を使う。これには固定長と可変長がある。
opaque fileblock[512] ; opaque filedata<> ;
opaque の代わりにchar の配列にすると、文字と見なして1
文字1文字変換が行なわれることになり、非常に遅くなる。場合によっては文
字コードの変換が行なわれる。opaque では、そのような変換は一切
行なわれず、そのままの形で送られる。
RPCのインタフェースの定義では、共用体(可変長の構造体)が書ける。
union int_result_t
switch( int status )
{
case OK:
	int	data ;
default:
	void;
};
これは、次のようにコンパイルされる。
struct int_result_t {
        int status;
        union {
                int data;
        } int_result_t_u;
};
status という値がOK の時だけdata が有効になる。
すなわち、その時だけ実際にネットワークにたいしてdata の部分が
送られる。
RPCのインタフェースの定義では、
bool_t という型が使える。値は、
TRUE か FALSE 。
Sunの技術で、構造体をファイルに保存することができる。
構造体を整列化し、他のプロセスに通信メッセージとして送る代わりにファイ ルに保存する。
SunRPCでは、xdrstdio_create() という関数が用意されている。 ストリーム入出力(FILE *) に対して構造体を読み書きすることができるようになる。
RPCと同様に、構造体にポインタが含まれていた場合、再帰的にファイルに保 存される。異なる機種で読み出すことができる。
.x ファイルに記述する。
enum intstack_error {
	INTSTACK_OK = 0,
	INTSTACK_OVERFLOW = 1,
	INTSTACK_UNDERFLOW = 2
};
struct pop_result {
    int data;
    int error;
};
program INTSTACK_PROG { 
   version INTSTACK_VERSION {
       intstack_error	PUSH(int)  = 11 ;
       pop_result	POP(void)  = 12 ;
   } = 1 ;
} = 0x20051002 ;
#define STACKSIZE 10 int stack[STACKSIZE]; int sp=STACKSIZE; // 後ろから使う時。前から使う方法もある。
ヒント:stat() システムコールや lstat() システムコールでファイルの属性 を得る。RPC では、単に string を返すのではなく、属性も返す。 返す属性は、自分で .x ファイルに定義して、struct stat からコピーする。 サーバ側で文字列に変換する方法も考えられるが、クライアント側で printf() 等で文字列にすることが望ましい。
struct stat にある属性のうち、数個を送ればよい。全てを送らなくてもよい。
typedef string key_t<256>;
struct keyvalue_t { 
   key_t key; 
   int   value ;
};
typedef key_t  keyarray_t<>;
program HASHTABLE_PROG { 
   version HASHTABLE_VERSION {
       int        INIT(void)       = 10 ; 
       int        PUT(keyvalue_t)  = 11 ; 
       int        GETVALUE(key_t)  = 12 ; 
   } = 1 ;
} = 0x20051001 ;
サーバ側では、hcreate(), hsearch() 等のライブラリ関数を用いてもよい。
注意:putenv() の引数は、strdup() すること。(同じ名前の環境変数がある と、ゴミが出てきてしまうが、この課題では問題ないとする。)
まず、次のような「テキストファイル」を作成する。漢字コードとしては、 JIS、Shift-JIS、EUC を受け付ける。(PDF, RTF, ワープロの文書ファイルで は受付ない。テキストでも Unicode (UTF) は、受け付けない。ZIP や tar, gzip 等で固めたり圧縮しないように。)
---------------------------------------------------------------------- 学籍番号: 200504321 名前: 漢字の名前 課題番号:M 練習問題番号:N 題名:subject <内容> ----------------------------------------------------------------------本文の先頭に学籍番号と名前(漢字の名前がある人は、漢字で)を書きなさい。 課題番号としては、「1」 と記述しなさい。 練習問題番号とは「★練習問題」に続いて表示されている番号である。 題名には、電子メールの Subject: と同様に、内容に即したものをつける。
<内容>は、日本語(または英語)で書きなさい。文章には、述語を付ける。 体言止めは、使ってはならない。単にプログラムを含めるのではなく、「以下 に○○のプログラムを示す」と書くこと。<内容>には、プログラムだけでな く、実行結果(入力と出力)と説明をつける。
問題を難しい方に変えてた場合、または、最初から難しい問題を解いた場合に は、<内容>の部分で主張しなさい。
作成したファイルを、次のページから投稿する。
上で書いた課題番号、題名を繰り返し指定する。さらに、WWW ブラウザの機能 を使って作成したレポートのファイルを選択する。 最後に、「提出」ボタンを押す。
提出されたレポートは、次のボタンで表示できます。
もし、提出に失敗したり、提出には成功しても確認画面に現れない場合には、 新城(yas@is.tsukuba.ac.jp)かTAに、連絡しなさい。
レポートを再提出する時には、どの部分を修正したのかが簡単にわかるように 説明しなさい。
提出したレポートは、講義が終るまで保存しなさい。