通信ソフトウェア/SunRPC

並列分散ソフトウェア

                                       電子・情報工学系
                                       新城 靖
                                       <yas@is.tsukuba.ac.jp>

このページは、次の URL にあります。
http://www.hlla.is.tsukuba.ac.jp/~yas/sie/pdsoft-2003/2003-12-18 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

■SunRPC

■rpcgenコマンド

SunRPC のスタブ生成器は、 rpcgen というコマンド。

◆rpcgenコマンドとファイル

rpcgen コマンドを使うには、次のようなファイルを作成する。

rpcgenによるRPCプログラム開発で利用するファイル

図? rpcgenによるRPCプログラム開発で利用するファイル

name.x
インタフェースを記述。
name_client.c
クライアント側の main プログラム。
name_server.c
サーバ側で、RPC で呼び出されるプログラム。 (main は、rpcgen により自動生成される。)

◆rpcgenコマンドの使い方


% rpcgen name.x [←]
次の4つのファイルが生成される。
name.h
そのRPCのプログラムで使う定数、データ構造、スタブ手続きのインタフェー ス。
name_clnt.c
クライアント側のスタブ。
name_xdr.c
name.x で定義したデータ構造について、 XDR のための手続き(整列化と非整列化を行なう手続き) 。 クライアント側とサーバ側の両方で使われる。
name_svc.c
サーバ側の main 関数とディスパッチ手続き。受け付けた RPC の要求を解析 して、開発者が定義した手続きを呼び出す。
これらのファイルの内容は、人間が十分読めるも。

SGI では、コンパイルとリンクには、特別なオプションを付ける必要はない。

■SunRPC の Binding

◆手続きの識別

SunRPCでは、手続きの識別を、次のような情報で行う

◆portmap

SunRPCでは, 最終的にはTCP/IPまたはUDP/IPでデータが送られる。プログラム 番号などTCP/IPまたはUDP/IPのポート番号を対応させる必要がある。

各ホストには portmap というサーバがいて、3つ組

プログラム番号,バージョン,プロトコル
を、TCP/IPまたはUDP/IPのポート番号へ変換する。

◆rpcinfo

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 と 決まっている。

■加減サービス

加減サービス(sumdiff)。 クライアントは、サーバに次のような整数を送る。
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 [←]
% 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 [←]
% []

◆サーバ側の実行

実行する時には、ウインドウを2つ開いて、それぞれでサーバとクライアント を走らせる。まずサーバ側から先に実行する。

% ./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 
% []

■SunRPC で使えるデータ型

RPCのインタフェースで使えるデータ構造は、C言語とほぼ同じですが、可変 長のデータが扱えるよう拡張されてる部分や、制限されている部分がある。

★ベクトルの加減サービス

◆基本型

int (32ビット), unsigned, char, long, float, double

◆列挙型

ほぼC言語と同等に書けますが、 自動的に typedef されます。 自動的に 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要素しか送られない。 複数要素を送りいた時には、配列を使う。

◆argv

C言語の main の引数と同様の構造を送りたい時には、次のようにする。

typedef string argstr_t<>; 
typedef argstr_t argvt<>;

◆opaque(不定形)

大量のデータをそのまま送るには、 opaque 型 ( 不定形型 ) を使う。これには固定長と可変長がある。
opaque fileblock[512] ;
opaque filedata<> ;

opaque の代わりにchar の配列にすると、文字と見なして1 文字1文字変換が行なわれることになり、非常に遅くなる。場合によっては文 字コードの変換が行なわれる。opaque では、そのような変換は一切 行なわれず、そのままの形で送られる。

◆union共用体

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 の部分が 送られる。

◆bool_t

RPCのインタフェースの定義では、bool_t という型が使える。値は、 TRUEFALSE

■日付時刻サービス

◆日付時刻サービスのインタフェース


date.x:
----------------------------------------------------------------------
   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:	}

----------------------------------------------------------------------

◆コンパイル


% 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 
% []

■XDRによる構造体のファイルへの保存

Sunの技術で、構造体をファイルに保存することができる。

構造体を整列化し、他のプロセスに通信メッセージとして送る代わりにファイ ルに保存する。

SunRPCでは、xdrstdio_create() という関数が用意されています。 ストリーム入出力(FILE *) に対して構造体を読み書きすることができるようになる。

RPCと同様に、構造体にポインタが含まれていた場合、再帰的にファイルに保 存される。異なる機種で読み出すことができる。

■SunRPC の練習問題

★ベクトルの加減サービス

整数の加減サービス は、単純な整数の和と差を計算するものである。これをベクトル(配列)を取る ように変更しなさい。

固定長でもいいですが、可変長に対応していることが望ましい。可変長の時、 長さが違っていた時には、エラーを変えすようにしたした方が望ましい。

★ベクトルのスカラ倍、内積、その他

上の加減サービスと同様に、スカラ倍や内積、その他の手続きを入れなさい。

固定長でもいいですが、可変長に対応していることが望ましい。可変長の時、 長さが違っていた時には、エラーを変えすようにしたした方が望ましい。

★行列の加減サービス

整数の加減サービス は、単純な整数の和と差を計算するものである。これを行列(2次元配列)を取 るように変更しなさい。

固定長ではなく、可変長に対応していることが望ましい。可変長の時、大きさ が違っていた時には、エラーを変えすようにしたした方が望ましい。

ヒント:C言語では、2次元配列は連続番地に置かれることが決まっている。 これを1次元配列としてまとめて送ってしまう方法がある。配列の行の数と列 の数は、別のパラメタで送る。

2次元配列をポインタの配列で表現する方法もある。

★getenv/putenv

putenv() と getenv() を使って、RPCサーバの環境変数を読み書きするような プログラムを作りなさい。

注意:putenv() の引数は、strdup() すること。(同じ名前の環境変数がある と、ゴミが出てきてしまうが、この課題ではよしとする。)

★ rusers クライアント

rusers コマンドと類似のクライアント・プログラムを作りなさい。この課題 では、サーバ側を作る必要はない。インタフェースは、次の場所にある。
/usr/include/rpcsvc/rnusers.x
このインタフェースに適合するようなクライアント側のプログラムを作りなさ い。

以下、rusers コマンドの実行例。

% 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)
% []

★RPC化

今まで自分が作ったプログラムの中から1つ選んで、それをRPCのプログラム に変更しなさい。ただし、次のような条件を持つものとする。

★その他

その他、上の練習問題と同程度の難しさの課題を設定して、解きなさい。

■ヒント

◆結果がうまく返されない

サーバ側で return する res を static にするのは、もちろん、res に含ま れているポインタの先も static は malloc() した領域である必要があります。

malloc() する例は、 日付時刻サービス にあります。

◆クライアント側で bus error や segmentation fault が出る

クライアント側では可変長配列を含む引数を渡す時には、 にあるように、独立に配列の領域 を確保する必要がる。単に送る構造体を宣言しただけでは、その中に含 まれているポインタの先は無効である。普通の変数(クライアント側の引数の先 は auto変数でもよい)の番地を自分でセットするか、malloc() で取る。

◆クライアント側での入出力

この課題は、RPC の課題です。データをファイルやキーボードから与える必要 はない。プログラムの中に固定してよい。

int data1[] = { 1, 2, 3, 4, };

■プログラム番号

SunRPC で使うのプログラム番号は、16進数で 0x20000000 〜 0x40000000 の 間(10進で536879812〜1073741824)は、ユーザが自由に利用できる領域として いる。しかし、重複して使うと、同じプログラム番号のものが1つのホストで 同時に動かなくなる。間違ってシステムが使っているものをセットすると、問 題が起きることがある。

この講義に関連して SunRPC の練習問題で使うプログラム番号は、重ならない ように次のようなルールで決めることにする。

	60iiiiiin
iiiiiiは、学籍番号の下6桁。 nは、0〜9の任意の数(複数のプログラムを作る時に 使える)。

これで足りない人は、この範囲意外で、適当にぶつかりそうにない番号を使う 事。


↑[もどる] ←[12月11日] ・[12月18日] →[12月25日]
Last updated: 2003/12/18 03:33:39
Yasushi Shinjo / <yas@is.tsukuba.ac.jp>