情報学類 分散システム 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に、連絡しなさい。
レポートを再提出する時には、どの部分を修正したのかが簡単にわかるように 説明しなさい。
提出したレポートは、講義が終るまで保存しなさい。