並行分散ソフトウェア/並列分散ソフトウェア
電子・情報工学系
新城 靖
<yas@is.tsukuba.ac.jp>
このページは、次の URL にあります。
http://www.cs.tsukuba.ac.jp/~yas/sie/pdsoft-2005/2006-01-13
sunrpc.html
あるいは、次のページから手繰っていくこともできます。
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 と 決まっている。
a,bサーバは、次ような結果をクライアントに返す。
a+b,a-b
sumdiff.x:
1: struct sumdiffarg {
2: int a ;
3: int b ;
4: };
5:
6: struct sumdiffres {
7: int sum ;
8: int diff ;
9: };
10:
11: program SUMDIFFPROG {
12: version SUMDIFFVERSION {
13: sumdiffres SUMDIFF(sumdiffarg) = 1 ;
14: } = 1 ;
15: } = 0x20001001 ;
13行目が手続きの定義。
SUMDIFF が手続きの名前(手続き番号を示す定数の定義)。
この手続きの引数は、sumdiffarg 型、結果は、sumdiffres
型。SunRPC 4.0 では、引数も結果も1つずつ。複数必要な時には、構造体を
定義する。
手続き SUMDIFF の定義を program 番号とversion
番号の括弧が取り囲んでいる。
sumdiff_server.c:
1: #include <rpc/rpc.h>
2: #include "sumdiff.h"
3:
4: sumdiffres *
5: sumdiff_1( sumdiffarg *arg )
6: {
7: static sumdiffres res ;
8: res.sum = arg->a + arg->b ;
9: res.diff = arg->a - arg->b ;
10: return( &res );
11: }
引数と結果は、rpcgen のソースで定義した構造体へのポインタ。
結果を返す時の構造体へのポインタを返す方法が問題。自動変数(auto 変数)
にすると、呼出し側に戻った瞬間に無効になる。よく使われるのは、静的変数
(static 変数)に結果を代入して、返すことだが、マルチスレッドプログラミ
ングでは大いに問題になる。
自動生成されるサーバのmainプログラムでは、 ポートマッパ(port mapper) へのプログラム番号とバージョン番号の登録される。
sumdiff_clnt.c
1: #include <stdio.h>
2: #include <rpc/rpc.h>
3: #include "sumdiff.h"
4:
5: main( int argc, char *argv[] )
6: {
7: CLIENT *cl;
8: sumdiffarg argument ;
9: sumdiffres *result ;
10: char *server_name;
11: int a,b ;
12:
13: if( argc != 4 ) {
14: fprintf(stderr, "usage: %s server num1 num2\n", argv[0]);
15: exit(1);
16: }
17: server_name = argv[1];
18: a = atoi( argv[2] );
19: b = atoi( argv[3] );
20: cl = clnt_create( server_name, SUMDIFFPROG, SUMDIFFVERSION, "tcp" );
21: if( cl == NULL ) {
22: clnt_pcreateerror( server_name );
23: exit( 1 );
24: }
25: argument.a = a ;
26: argument.b = b ;
27: result = sumdiff_1( &argument, cl );
28: if( result == NULL ) {
29: clnt_perror( cl, server_name );
30: exit( 1 );
31: }
32: printf("sumdiff( %d,%d ) returns %d,%d \n",
33: argument.a, argument.b, result->sum, result->diff );
34: xdr_free( xdr_sumdiffres, (char *)result );
35: }
main() の引数は、サーバが動作しているホスト名とサーバに渡す引数となる 2つの整数。
clnt_create() は、CLIENT 構造体を確保する。
引数は、サーバのホスト名、
プログラム番号、
バージョン番号、
通信に使うプロトコルである。
27行目で呼び出している sumdiff_1() が、rpcgen により生
成されたスタブ。引数は、インタフェースで定義された構造体
sumdiffargへのポインタと、CLIENT 構造体へのポインタ。
結果は、インタフェースで定義された構造体sumdiffresへのポインタである。
この関数は、rpcgen が生成する *_clnt.c というファ
イルに含まれてる。
スタブ sumdiff_1()は、成功すると内部で malloc() を呼
び出しメモリを確保して、そこにサーバから受け取った応答メッセージを
非整列化(unmarshalling)
して保存する。このメモリは、利用しおわるとxdr_free()で解放する。
今の場合は、
free( result );でも動くが、内部に構造体へのポインタや文字列を含んでいた時には、それら の領域も解放するためには、
xdr_free()を呼び出す。
% rpcgen sumdiff.x実行する時には、ウインドウを2つ開いて、それぞれでサーバとクライアント を走らせる。まずサーバ側から先に実行する。% cc -c sumdiff_svc.c
% cc -c sumdiff_server.c
% cc -c sumdiff_xdr.c
% cc -o sumdiff_server sumdiff_svc.o sumdiff_server.o sumdiff_xdr.o
% cc -c sumdiff_client.c
% cc -c sumdiff_clnt.c
% cc -o sumdiff_client sumdiff_client.o sumdiff_clnt.o sumdiff_xdr.o
%
![]()
% ./sumdiff_serverサーバ側は普通止まないので、止めたい時には、![]()
^C を押す。
ホスト名には、localhost が便利。
% ./sumdiff_clientusage: ./sumdiff_client server num1 num2 % ./sumdiff_client localhost 30 20
sumdiff( 30,20 ) returns 50,10 % ./sumdiff_client localhost 100 200
sumdiff( 100,200 ) returns 300,-100 %
![]()
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 。
1:
2: /*
3: date.x -- 日付時刻サービス
8: Start: 1999/01/26 02:16:33
9: */
10:
11: struct ds_timeval
12: {
13: long clock ;
14: };
15:
16: struct ds_datestr
17: {
18: string str<> ;
19: };
20:
21: program DATE_PROG {
22: version DATE_VERSION {
23: ds_timeval GETTIMEVAL(void) = 11 ;
24: ds_datestr GETDATESTR(void) = 12 ;
25: } = 1 ;
26: } = 0x20001002 ;
string1つでも構造体にした方がわかりやすい。
date_server.c:
1:
2: /*
3: date_server.x -- 日付時刻サービス/サーバ側の手続き
4: このファイルは、次の場所にあります。
8: Start: 1999/01/26 02:16:33
9: */
10:
11: #include <rpc/rpc.h>
12: #include "date.h"
13:
14: ds_timeval *
15: gettimeval_1()
16: {
17: static ds_timeval res ;
18: res.clock = time( 0 ); /* BSD: gettimeofday() */
19: return( &res );
20: }
21:
22: ds_datestr *
23: getdatestr_1()
24: {
25: static ds_datestr res ;
26: char buf[100] ;
27: time_t now ;
28: struct tm *tm_now ;
29: int len ;
30: if( res.str )
31: {
32: free( res.str );
33: res.str = 0 ;
34: }
35: now = time( 0 );
36: tm_now = localtime( &now );
37: len = strftime( buf, 100, "%a %b %d %H:%M:%S %Z %Y", tm_now );
38: if( len == 0 )
39: return( 0 );
40: res.str = (char *)malloc( len+1 );
41: if( res.str == 0 )
42: return( 0 );
43: strncpy( res.str, buf, len );
44: res.str[len] = 0 ;
45: return( &res );
46: }
malloc() してリターンして、次に回って来た時に free() する。
date_client.c
1:
2: /*
3: date_server.x -- 日付時刻サービス/クライアント側の手続き
9: */
10:
11: #include <stdio.h>
12: #include <rpc/rpc.h>
13: #include "date.h"
14:
15: main( int argc, char *argv[] )
16: {
17: CLIENT *cl;
18: char dummy ;
19: ds_datestr *result ;
20: char *server_name;
21:
22: if( argc != 2 ) {
23: fprintf(stderr, "usage: %s server\n", argv[0]);
24: exit(1);
25: }
26: server_name = argv[1];
27: cl = clnt_create( server_name, DATE_PROG, DATE_VERSION, "tcp" );
28: if( cl == NULL ) {
29: clnt_pcreateerror( server_name );
30: exit( 1 );
31: }
32: result = getdatestr_1( &dummy, cl );
33: if( result == NULL ) {
34: clnt_perror( cl, server_name );
35: exit( 1 );
36: }
37: printf("getdatestr() returns %s \n",
38: result->str );
39: xdr_free( xdr_ds_datestr, (char *)result );
40: }
% makerpcgen date.x cc -g -c date_client.c -o date_client.o cc -g -c date_clnt.c -o date_clnt.o cc -g -c date_xdr.c -o date_xdr.o cc -o date_client date_client.o date_clnt.o date_xdr.o cc -g -c date_server.c -o date_server.o cc -g -c date_svc.c -o date_svc.o cc -o date_server date_server.o date_svc.o date_xdr.o %
![]()
% ./date_serverサーバ側は止まらないので、止めたい時には、![]()
^C を押す。
ホスト名には、localhost が便利。
% ./date_client localhostgetdatestr() returns Tue Jan 26 03:05:24 JST 1999 %
![]()
Sunの技術で、構造体をファイルに保存することができる。
構造体を整列化し、他のプロセスに通信メッセージとして送る代わりにファイ ルに保存する。
SunRPCでは、xdrstdio_create() という関数が用意されている。 ストリーム入出力(FILE *) に対して構造体を読み書きすることができるようになる。
RPCと同様に、構造体にポインタが含まれていた場合、再帰的にファイルに保 存される。異なる機種で読み出すことができる。
固定長でもよいが、可変長に対応していることが望ましい。可変長の時、 長さが違っていた時には、エラーを変えすようにしたした方が望ましい。
上の加減サービスと同様に、スカラ倍や内積、その他の手続きを入れなさい。固定長でもよいが、可変長に対応していることが望ましい。可変長の時、 長さが違っていた時には、エラーを変えすようにしたした方が望ましい。
整数の加減サービス は、単純な整数の和と差を計算するものである。これを行列(2次元配列)を取 るように変更しなさい。固定長ではなく、可変長に対応していることが望ましい。可変長の時、大きさ が違っていた時には、エラーを変えすようにしたした方が望ましい。
ヒント:C言語では、2次元配列は連続番地に置かれることが決まっている。 これを1次元配列としてまとめて送ってしまう方法がある。配列の行の数と列 の数は、別のパラメタで送る。
2次元配列をポインタの配列で表現する方法もある。
putenv() と getenv() を使って、RPCサーバの環境変数を読み書きするような プログラムを作りなさい。
注意:putenv() の引数は、strdup() すること。(同じ名前の環境変数がある と、ゴミが出てきてしまうが、この課題ではよしとする。)
rusers コマンドと類似のクライアント・プログラムを作りなさい。この課題 では、サーバ側を作る必要はない。インタフェースは、次の場所にある。
/usr/include/rpcsvc/rnusers.xこのインタフェースに適合するようなクライアント側のプログラムを作りなさ い。
以下、rusers コマンドの実行例。
% who今まで自分が作ったプログラムの中から1つ選んで、それをRPCのプログラム に変更しなさい。ただし、次のような条件を持つものとする。kirane ttyq0 2月 9日 00時01分 yas ttyq1 2月 9日 00時37分 % rusers localhost
localhost kirane yas % rusers -l localhost
kirane localhost:ttyq0 Feb 9 00:01 8 (greenwich.softla) yas localhost:ttyq1 Feb 9 00:37 (hlla.hlla.is.tsu) %
![]()
malloc() する例は、 日付時刻サービス にある。
◆クライアント側で bus error や segmentation fault が出る
クライアント側では可変長配列を含む引数を渡す時には、 例にあるように、独立に配列の領域 を確保する必要がる。単に送る構造体を宣言しただけでは、その中に含 まれているポインタの先は無効である。普通の変数(クライアント側の引数の先 は auto変数でもよい)の番地を自分でセットするか、malloc() で取る。 この課題は、RPC の課題である。データをファイルやキーボードから与える必要 はない。プログラムの中に固定してよい。
int data1[] = { 1, 2, 3, 4, };
SunRPC で使うのプログラム番号は、16進数で 0x20000000 〜 0x40000000 の
間(10進で536879812〜1073741824)は、ユーザが自由に利用できる領域として
いる。しかし、重複して使うと、同じプログラム番号のものが1つのホストで
同時に動かなくなる。間違ってシステムが使っているものをセットすると、問
題が起きることがある。