並列分散ソフトウェア 電子・情報工学系 新城 靖 <yas@is.tsukuba.ac.jp>
このページは、次の URL にあります。
http://www.hlla.is.tsukuba.ac.jp/~yas/sie/pdsoft-2001/2002-01-10
sunrpc.html
あるいは、次のページから手繰っていくこともできます。
http://www.hlla.is.tsukuba.ac.jp/~yas/sie/
http://www.is.tsukuba.ac.jp/~yas/index-j.html
http://www.hlla.is.tsukuba.ac.jp/~yas/index-j.html
rpcgen
というコマンド。
rpcgen
コマンドを使うには、次のようなファイルを作成する。
図? rpcgenによるRPCプログラム開発で利用するファイル
name.x
name_client.c
name_server.c
次の4つのファイルが生成される。% rpcgen name.x
name.h
name_clnt.c
name_xdr.c
name.x
で定義したデータ構造について、
XDR
のための手続き(整列化と非整列化を行なう手続き) 。
クライアント側とサーバ側の両方で使われる。
name_svc.c
SGI では、コンパイルとリンクには、特別なオプションを付ける必要はない。
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 % ----------------------------------------------------------------------
サーバは、起動時に、portmap に登録する。% rpcinfo -p hostname
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()
を呼び出す。
実行する時には、ウインドウを2つ開いて、それぞれでサーバとクライアント を走らせる。まずサーバ側から先に実行する。% rpcgen sumdiff.x % 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_client usage: ./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
。
date.x: ---------------------------------------------------------------------- 1: 2: /* 3: date.x -- 日付時刻サービス 4: このファイルは、次の場所にあります。 5: ~yas/dsys/rpc-date/date.x 6: cp コマンドでコピーできます。 7: $Header: /home/lab2/OS/yas/dsys/rpc-date/RCS/date.x,v 1.4 1999/01/25 18:00:46 yas Exp $ 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: このファイルは、次の場所にあります。 5: ~yas/dsys/rpc-date/date_server.c 6: cp コマンドでコピーできます。 7: $Header: /home/lab2/OS/yas/dsys/rpc-date/RCS/date_server.c,v 1.3 1999/01/25 17:59:03 yas Exp $ 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 -- 日付時刻サービス/クライアント側の手続き 4: このファイルは、次の場所にあります。 5: ~yas/dsys/rpc-date/date_server.c 6: cp コマンドでコピーできます。 7: $Header: /home/lab2/OS/yas/dsys/rpc-date/RCS/date_client.c,v 1.3 1999/01/25 17:59:03 yas Exp $ 8: Start: 1999/01/26 02:16:33 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: } ----------------------------------------------------------------------
% make rpcgen 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 localhost getdatestr() 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 コマンドと類似のクライアント・プログラムを作りなさい。この課題 では、サーバ側を作る必要はない。インタフェースは、次の場所にある。
このインタフェースに適合するようなクライアント側のプログラムを作りなさ い。~yas/dsys/sunrpc-4.0/rpcsvc/rnusers.x
以下、rusers コマンドの実行例。
今まで自分が作ったプログラムの中から1つ選んで、それをRPCのプログラム に変更しなさい。ただし、次のような条件を持つものとする。% who 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つのホストで 同時に動かなくなる。間違ってシステムが使っているものをセットすると、問 題が起きることがある。この講義に関連して SunRPC の練習問題で使うプログラム番号は、重ならない ように次のようなルールで決めることにする。
60iiiiiiniiiiii
は、6桁の学籍番号。n
は、0〜9の任意の数(複数のプログラムを作る時に 使える)。これで足りない人は、この範囲意外で、適当にぶつかりそうにない番号を使う 事。
↑[もどる] ←[12月20日] ・[1月10日] →[12月13日]
Last updated: 2002/01/09 23:43:45
Yasushi Shinjo / <yas@is.tsukuba.ac.jp>