分散システム
電子・情報工学系
新城 靖
<yas@is.tsukuba.ac.jp>
このページは、次の URL にあります。
http://www.hlla.is.tsukuba.ac.jp/~yas/coins/dsys-1998/1999-01-19
/sunrpc.html
あるいは、次のページから手繰っていくこともできます。
http://www.hlla.is.tsukuba.ac.jp/~yas/coins/
http://www.hlla.is.tsukuba.ac.jp/~yas/index-j.html
(<−>文化的には、TCP/IP上のテキストベースのプロトコルとは対照。)
TCP/IP で提供されているストリームや UDP/IP で提供されているデー タグラムと比較して、RPC には次のような特徴がある。RPCでは、別のアドレス空間の間でデータがやり取りされるので、基本的に 「ポインタ」を受け渡しすることはできない。しかし、SunRPC では、ポイン タの先を再帰的に「コピー」する機能がある。
RPCを実現するための基本命令は、次の3つ。サーバは、get_request() で、クライアントから何か要求メッセージが 届くのを待つ。
クライアントが何かサーバにして欲しい時には、do_operation() で、サーバ に要求メッセージを送り、サーバからの応答メッセージを待つ。
サーバは、要求メッセージを受けとると、処理を行ない、send_reply() でク ライアントに応答メッセージを送る。そして再び次の要求メッセージを get_request() で待つ。
このRPCの3つの命令は、システムコール、または、ライブラリで実現されま す。SunRPCには、get_request() に相当する命令が定義されていない。
RPCでプログラムを作成するときには、3つの基本命令を利用することは、ほ とんどない。 スタブ生成器(stub generator) を使えば、インタフェースの定義から3つの基本命令を呼び出すようなプログ ラムが自動生成される(lex、yacc )。スタブ(stub) は、プロセス間通信を普通の手続き呼出しと全く同じ形式で行なうことができ るようにするためのプログラム。 もともとは木の切株の意味。
スタブの分類
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 -pprogram 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_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 %
![]()