並行分散ソフトウェア/並列分散ソフトウェア
電子・情報工学系
新城 靖
<yas@is.tsukuba.ac.jp>
このページは、次の URL にあります。
http://www.cs.tsukuba.ac.jp/~yas/sie/pdsoft-2005/2006-01-20
あるいは、次のページから手繰っていくこともできます。
http://www.cs.tsukuba.ac.jp/~yas/sie/
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
SunRPCでは、手続きの識別を、次のような情報で行う
/etc/rpc
にある。
SunRPCでは, 最終的にはTCP/IPまたはUDP/IPでデータが送られる。プログラム 番号などTCP/IPまたはUDP/IPのポート番号を対応させる必要がある。
各ホストには
portmap
というサーバがいて、3つ組
<プログラム番号,バージョン,プロトコル>を、TCP/IPまたはUDP/IPのポート番号へ変換する。
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 に登録する。![]()
pmap_set(prognum, versnum, protocol, port) u_long prognum, versnum, protocol; u_short port;クライアントは、呼び出す前に、ポート番号を調べる。
u_short pmap_getport(addr, prognum, versnum, 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 0x20001003
#define DIRLIST_VERSION 1
#if defined(__STDC__) || defined(__cplusplus)
#define DIRLIST 11
extern dirlist_res * dirlist_1(char **, CLIENT *);
extern dirlist_res * dirlist_1_svc(char **, struct svc_req *);
extern int dirlist_prog_1_freeresult (SVCXPRT *, xdrproc_t, caddr_t);
#else /* K&R C */
#define DIRLIST 11
extern dirlist_res * dirlist_1();
extern dirlist_res * dirlist_1_svc();
extern int dirlist_prog_1_freeresult ();
#endif /* K&R C */
: /*
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.cs.tsukuba.ac.jp/~yas/sie/cdsoft-2005/2006-01-20/ex/dirlist.xSolaris, Linux では、-lnsl が必要。Solaris, MacOSX では、-DDEBUG をつ けてコンパイルとfork() しないでフォアグランドで動くサーバができる。% wget http://www.cs.tsukuba.ac.jp/~yas/sie/cdsoft-2005/2006-01-20/ex/dirlist_server.c
% wget http://www.cs.tsukuba.ac.jp/~yas/sie/cdsoft-2005/2006-01-20/ex/dirlist_client.c
% wget http://www.cs.tsukuba.ac.jp/~yas/sie/cdsoft-2005/2006-01-20/ex/Makefile
% emacs Makefile
% make
rpcgen dirlist.x % make
gcc -g -DDEBUG -c -o dirlist_clnt.o dirlist_clnt.c gcc -g -DDEBUG -c -o dirlist_client.o dirlist_client.c 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 -lnsl 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 -lnsl %
![]()
% ./dirlist_serverサーバ側は自動的には止まらないので、止めたい時には、![]()
^C を押す。
サーバを動かしているホストの別の端末で rpcinfo -p を実行すると、プログ ラム番号536875011 (0x20001003) のプログラムが表示される。
% rpcinfo -pprogram vers proto port 100000 2 tcp 111 portmapper 100000 2 udp 111 portmapper 536875011 1 udp 59692 536875011 1 tcp 50138 %
![]()
MacOSX 10.4 では、portmapp が動作していないことがある。rpcinfo -p で何 も表示されない時には、root で 次のようにして実行する。
launchctl start com.apple.portmap
% ./dirlist_client localhost /usrerrno: 0 (Success) . .. lost+found X11R6 bin dict <中略> tmp %
![]()
整数: char, short, int (32ビット), long, long long (hyper, 64ビット)
各 unsigned。
浮動小数点: float, double, (quadruple 4倍精度)
ほぼC言語と同等に書ける。自動的に typedef される。 ほぼC言語と同等に書れる。自動的に typedef される。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と同様に、構造体にポインタが含まれていた場合、再帰的にファイルに保 存される。異なる機種で読み出すことができる。
注意:putenv() の引数は、strdup() すること。(同じ名前の環境変数がある と、ゴミが出てきてしまうが、この課題では問題ないとする。)
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 PUT(keyvalue_t) = 11 ;
int GETVALUE(key_t) = 12 ;
keyarray_t GETKEYS(void) = 13 ;
} = 1 ;
} = 0x20051001 ;
このクライアントは、4つの引数を取るものとする。
% myypmatch host domain map key![]()
const YPMAXRECORD = 1024;
const YPMAXDOMAIN = 64;
const YPMAXMAP = 64;
...
typedef string domainname<YPMAXDOMAIN>;
typedef string mapname<YPMAXMAP>;
typedef opaque keydat<YPMAXRECORD>;
...
struct ypreq_key {
domainname domain;
mapname map;
keydat key;
};
...
program YPPROG {
version YPVERS {
...
ypresp_val
YPPROC_MATCH(ypreq_key) = 3;
...
} = 2;
} = 100004;
keydat は、不定形(任意のバイナリ)である。
keydat_val には、文字列の先頭番地、keydat_len には、文字列の長さ(0を
含まない)を指定する。
ヒント:stat() システムコールや lstat() システムコールでファイルの属性 を得る。RPC では、単に string を返すのではなく、属性も返す。 返す属性は、自分で .x ファイルに定義して、struct stat からコピーする。
struct stat にある属性のうち、数個を送ればよい。全てを送らなくてもよい。