SunRPC

情報学類 分散システム					2009年01月13日

                                       筑波大学システム情報工学研究科
                                       コンピュータサイエンス専攻, 電子・情報工学系
                                       新城 靖
                                       <yas@is.tsukuba.ac.jp>

このページは、次の URL にあります。
http://www.coins.tsukuba.ac.jp/~yas/coins/dsys-2008/2009-01-13
あるいは、次のページから手繰っていくこともできます。
http://www.coins.tsukuba.ac.jp/~yas/
http://www.cs.tsukuba.ac.jp/~yas/

■連絡

■SunRPC

SunRPC は、Sun Microsystems社により開発され、仕様やソースコードが 公開された RPC の実装。ONC RPC (Open Network Computing) とも呼ばれる。 RFC にもなっている。

◆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 の要求を解析 して、開発者が定義した手続きを呼び出す。
これらのファイルの内容は、人間が十分読める。

◆手続きの識別

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

◆portmap (portmapper)

クライアント、サーバ、portmapper

図? portmapper の働き

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

各ホストには portmap (portmapper) というサーバがいて、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 に登録する。
bool_t
pmap_set(program, version, protocol, port)
		  u_long program;
		  u_long version;
		  int protocol;
		  u_short port;
クライアントは、呼び出す前に、ポート番号を調べる。
u_short
pmap_getport(address, program, version, protocol)
		      struct sockaddr_in *address;
		      u_long program;
		      u_long version;
		      u_int protocol;

スタブが自動的に呼び出すので、普段は気にすることはない。

portmap 自身も RPC で動いている。portmap の自身のポート番号は、111 と 決まっている。

■RPCを使った分散アプリケーションの例

ゲームのハイスコア(得点の高い人n人の得点と名前)の維持。 集中アプリケーション(12月2日)TCPを使った分散アプリケーション、 をSunRPCを使って書き直したもの。
int get_highscore_client_rpc( char *server,score_record_t records[], int len )
現在のハイスコア(score_record_t型)を最大 len 個だけ取得する。 実際に保存していた数を返す。
int put_score_client_rpc( char *server, int score, char *name )
新たにスコアを追加する。現在のものよりも得点が高ければ追加する。 引数は、得点とユーザ名。
TCP版と違って、ポート番号を取らない。サーバのホスト名は取る。

◆実行例

例題をコピーするには、cp コマンドを使うとよい。最後のディレクトリ名 「.」を忘れないように。
% mkdir hiscore-rpc [←]
% cd hiscore-rpc [←]
% cp ~yas/dsys/highscore/rpc/add-hiscore-rpc.c . [←]
% cp ~yas/dsys/highscore/rpc/highscore_rpc-client.c . [←]
% cp ~yas/dsys/highscore/rpc/highscore_rpc-server.c . [←]
% cp ~yas/dsys/highscore/rpc/highscore_rpc.x . [←]
% cp ~yas/dsys/highscore/rpc/show-hiscore-rpc.c . [←]
% cp ~yas/dsys/highscore/rpc/Makefile . [←]
% ls [←]
Makefile                highscore_rpc-client.c  highscore_rpc.x
add-hiscore-rpc.c       highscore_rpc-server.c  show-hiscore-rpc.c
% make [←]
rpcgen highscore_rpc.x
cc -g -DRPC_SVC_FG   -c -o add-hiscore-rpc.o add-hiscore-rpc.c
cc -g -DRPC_SVC_FG   -c -o highscore_rpc-client.o highscore_rpc-client.c
cc -g -DRPC_SVC_FG   -c -o highscore_rpc_clnt.o highscore_rpc_clnt.c
<警告略>
cc -g -DRPC_SVC_FG   -c -o highscore_rpc_xdr.o highscore_rpc_xdr.c
cc -g -DRPC_SVC_FG add-hiscore-rpc.o highscore_rpc-client.o highscore_rpc_clnt.o highscore_rpc_xdr.o -o add-hiscore-rpc
cc -g -DRPC_SVC_FG   -c -o show-hiscore-rpc.o show-hiscore-rpc.c
cc -g -DRPC_SVC_FG  show-hiscore-rpc.o highscore_rpc-client.o highscore_rpc_clnt.o highscore_rpc_xdr.o -o show-hiscore-rpc
cc -g -DRPC_SVC_FG   -c -o highscore_rpc-server.o highscore_rpc-server.c
cc -g -DRPC_SVC_FG   -c -o highscore_rpc_svc.o highscore_rpc_svc.c
cc -g -DRPC_SVC_FG   -c -o highscore_rpc_clnt.o highscore_rpc_clnt.c
<警告略>
cc -g -DRPC_SVC_FG highscore_rpc-server.o highscore_rpc_svc.o highscore_rpc_xdr.o -o highscore_rpc-server
% ls [←]
Makefile                highscore_rpc-server.c  highscore_rpc_svc.o
add-hiscore-rpc         highscore_rpc-server.o  highscore_rpc_xdr.c
add-hiscore-rpc.c       highscore_rpc.h         highscore_rpc_xdr.o
add-hiscore-rpc.o       highscore_rpc.x         show-hiscore-rpc
highscore_rpc-client.c  highscore_rpc_clnt.c    show-hiscore-rpc.c
highscore_rpc-client.o  highscore_rpc_clnt.o    show-hiscore-rpc.o
highscore_rpc-server    highscore_rpc_svc.c
% []
make コマンドを実行すると、Makefile の記述に従い3つの実行 形式のファイル highscore_rpc-server, add-hiscore-rpc, add-hiscore-rpc が作られる。 自動生成されたファイル highscore_rpc_clnt.chighscore_rpc_svc.c をコンパイルする時に警告(warning)が表示され るが、問題はない。

例題を実行するには、クライアント用とサーバ用に端末を2つ開く。 その方法は、make help で表示される。

% make help [←]
Open two terminals for server and client

server:
./highscore-server
(To stop this server, Press ^C)

client:
./add-hiscore-rpc server score name
./show-hiscore-rpc server num

% []

クライアントとサーバは、別のコンピュータで動作させても良い。 以下の例では、サーバをazalea20 で動作させている。 サーバ側:

% ssh azalea20 [←]
% cd hiscore-rpc/ [←]
% ./highscore_rpc-server  [←]
サーバは終了しないので、実験が終わったら ^C で止める。

クライアント側は、もう1つの端末で実行する。 rpcinfo コマンドで、サーバのプログラム番号、プロトコルポート番号 を確認する。

% rpcinfo -p azalea20 [←]
   program vers proto   port
    100000    2   tcp    111  portmapper
    100000    2   udp    111  portmapper
    100024    1   udp   1013  status
    100024    1   tcp   1006  status
    100021    0   udp   1000  nlockmgr
    100021    1   udp   1000  nlockmgr
    100021    3   udp   1000  nlockmgr
    100021    4   udp   1000  nlockmgr
    100021    0   tcp   1005  nlockmgr
    100021    1   tcp   1005  nlockmgr
    100021    3   tcp   1005  nlockmgr
    100021    4   tcp   1005  nlockmgr
 537000100    1   udp  49202
 537000100    1   tcp  50471
% []
537000100 は、16進数で0x2001f8a4。

クライアントを実行する。

% ./add-hiscore-rpc  [←]
Usage: % ./add-hiscore-rpc server score "User Name"
% ./add-hiscore-rpc azalea20 10 "Yasushi Shinjo" [←]
% ./add-hiscore-rpc azalea20 20 "Kazuhiko Kato" [←]
% ./show-hiscore-rpc  [←]
Usage: % ./show-hiscore-rpc host n
% ./show-hiscore-rpc azalea20 10 [←]
2 hiscore record(s) received
        20 Kazuhiko Kato
        10 Yasushi Shinjo
% []
実験が終了したら、サーバを ^C コマンドで削除する。
% ./highscore_rpc-server  [←]
^C
% []

◆インタフェース

RPC では、手続き呼び出しの形でインターネットを記述する。 ネットワーク上を流れるデータを意識する必要はあまりない。

[highscore_rpc.x]

   1:	
   2:	/*
   3:	        highscore_rpc.h -- Hiscore Protocol over SunRPC
   4:	        Created on: 2008/12/23 14:19:17
   5:	        ~yas/dsys/highscore/rpc/highscore_rpc.x
   6:	*/
   7:	
   8:	const HIGHSCORE_MAX_RECORDS     = 10;
   9:	const HIGHSCORE_NAME_LEN        = 28;
  10:	
  11:	struct score_record {
  12:	        int      score;
  13:	        opaque   name[HIGHSCORE_NAME_LEN];
  14:	};
  15:	typedef struct score_record score_record_t;
  16:	
  17:	struct get_highscore_result_t {
  18:	        score_record records<HIGHSCORE_MAX_RECORDS>;
  19:	};
  20:	
  21:	program HIGHSCORE_RPC_PROG {
  22:	    version HIGHSCORE_RPC_VERSION {
  23:	        get_highscore_result_t GET_HIGHSCORE(int) = 1 ;
  24:	        int PUT_SCORE(score_record_t)             = 2 ;
  25:	    } = 1;
  26:	} = 0x2001f8a4 ;
  27:	
  28:	#ifdef RPC_HDR
  29:	%extern int get_highscore_client_rpc( char *server,
  30:	%                                    score_record_t records[], int len );
  31:	%extern int put_score_client_rpc(char *server, int score, char *name);
  32:	#endif
23-24行目が手続きの定義。 GET_HIGHSCORE PUT_SCORE が手続きの名前(手続き番号を示す定数の定義)。

GET_HIGHSCOREの引数は、int 型、結果は、 get_highscore_result_t 型。SunRPC では、基本的には引数も結果も1つずつ。複数必要な時には、構造体を 定義する。

手続きの定義を program 番号と version 番号の括弧が取り囲んでいる。

const は、定数の定義。 % は、そのまま生成されるファイルにコピーされることを意味する。 この場合は、RPC_HDR で取り囲み、.h にだけコピーされる。

このプログラムには、2種類の配列が使われている。

固定長配列 []
ネットワーク上をそのバイト数のデータが流れる。
可変長の配列 <>
実際に存在する要素分のデータだけが流れる。 可変長では、(<>)の中に要素数の最大も指定できる。
opaque (不定形)は、バイトを意味する。char の場合、 「文字」としてコード変換がおそなわれるので遅くなる。

◆ヘッダファイル

インタフェース定義から、rpcgen コマンドによりヘッダファイル、クライア ント側スタブ、サーバ側スタブ、XDR によるマーシャリングを行うプログラム が生成される。

ヘッダファイルの主要部分は、以下の通りである。

[highscore_rpc.h]

  13:	#define HIGHSCORE_MAX_RECORDS 10
  14:	#define HIGHSCORE_NAME_LEN 28

  16:	struct score_record {
  17:	        int score;
  18:	        char name[HIGHSCORE_NAME_LEN];
  19:	};
  20:	typedef struct score_record score_record;
  30:	typedef score_record score_record_t;
  40:	struct get_highscore_result_t {
  41:	        struct {
  42:	                u_int records_len;
  43:	                score_record *records_val;
  44:	        } records;
  45:	};
  46:	typedef struct get_highscore_result_t get_highscore_result_t;

  55:	extern int get_highscore_client_rpc( char *server,
  56:	                                     score_record_t records[], int len );
  57:	extern int put_score_client_rpc(char *server, int score, char *name);

  59:	#define HIGHSCORE_RPC_PROG ((u_long)0x2001f8a4)
  60:	#define HIGHSCORE_RPC_VERSION ((u_long)1)

  71:	#define GET_HIGHSCORE ((u_long)1)
  72:	extern  get_highscore_result_t * get_highscore_1(int *, CLIENT *);
  73:	extern  get_highscore_result_t * get_highscore_1_svc(int *, struct svc_req *);
  74:	#define PUT_SCORE ((u_long)2)
  75:	extern  int * put_score_1(score_record_t *, CLIENT *);
  76:	extern  int * put_score_1_svc(score_record_t *, struct svc_req *);
元のインタフェース記述とよく似ている。 可変長の配列が、構造体として表現されている。 opaque は、char として出力されている。

◆add-hiscore-rpc.c

put_score_client_rpc() 関数を呼び出す簡単なプログラム。

[add-hiscore-rpc.c]

   1:	
   2:	/*
   3:	        add-hiscore-rpc.c -- The main function of put_score_client_rpc().
   4:	        Created on: 2009/01/06 18:07:37
   5:	        ~yas/dsys/highscore/rpc/add-hiscore-rpc.c
   6:	*/
   7:	
   8:	#include <stdio.h>      /* stderr, fprintf() */
   9:	#include <stdlib.h>     /* strtol() */
  10:	#include "highscore_rpc.h"
  11:	
  12:	void usage( char *comname ) {
  13:	        fprintf(stderr,"Usage: %% %s server score \"User Name\"\n", comname);
  14:	        exit( 1 );
  15:	}
  16:	
  17:	main( int argc, char *argv[], char *envp[] ) {
  18:	    int score, portno ;
  19:	    char *name, *server ;
  20:	        if( argc != 4 )
  21:	            usage( argv[0] );
  22:	        server = argv[1];
  23:	        score  = strtol( argv[2], 0, 10);
  24:	        name   = argv[3];
  25:	        put_score_client_rpc( server, score, name );
  26:	}
TCP版との違いは、ポート番号が不要な点のみ。

◆show-hiscore-rpc.c

get_hiscore_client_rpc() 関数を呼び出す簡単なプログラム。

[show-hiscore-rpc.c]

   1:	
   2:	/*
   3:	        show-hiscore-rpc.c -- The main function for get_highscore_client_rpc().
   4:	        Created on: 2009/01/09 01:21:49
   5:	        ~yas/dsys/highscore/rpc/show-hiscore-rpc.c
   6:	*/
   7:	
   8:	#include <stdio.h>      /* stderr, fprintf() */
   9:	#include <stdlib.h>     /* strtol() */
  10:	#include "highscore_rpc.h"
  11:	
  12:	static void show_score( char *host, int top );
  13:	
  14:	static void usage( char *comname ) {
  15:	        fprintf(stderr,"Usage: %% %s host n\n", comname);
  16:	        exit( 1 );
  17:	}
  18:	
  19:	main( int argc, char *argv[], char *envp[] ) {
  20:	    int top ;
  21:	    char *name, *server ;
  22:	        if( argc != 3 )
  23:	            usage( argv[0] );
  24:	        server = argv[1];
  25:	        top = strtol( argv[2], 0, 10);
  26:	        show_score( server, top );
  27:	}
  28:	
  29:	static void show_score( char *server, int top ) {
  30:	    score_record_t records[HIGHSCORE_MAX_RECORDS];
  31:	    int n, i ;
  32:	        if( top > HIGHSCORE_MAX_RECORDS ) {
  33:	            fprintf(stderr,"Warning: top too large: %d\n",top);
  34:	            top = HIGHSCORE_MAX_RECORDS;
  35:	        }
  36:	        n = get_highscore_client_rpc( server, records, top );
  37:	        if( n >= 0 ) {
  38:	            printf("%d hiscore record(s) received\n", n );
  39:	            for( i=0; i<n; i++ ) {
  40:	                printf("%10d %s\n", records[i].score, records[i].name );
  41:	            }
  42:	        }
  43:	        else {
  44:	            printf("error: %d\n", n );
  45:	        }
  46:	}
TCP版との違いは、ポート番号が不要な点のみ。

◆hiscore-rpcクライアント

[highscore_rpc-client.c]
   1:	
   2:	/*
   3:	        highscore_rpc-client.c -- The hiscore client using SunRPC.
   4:	        Created on: 2009/01/06 18:10:11
   5:	        ~yas/dsys/highscore/rpc/highscore_rpc-client.c
   6:	*/
   7:	
   8:	#include <stdio.h>      /* stderr, fprintf() */
   9:	#include <string.h>     /* memset() */
  10:	#include "highscore_rpc.h"
  11:	
TCP版と異なり、Socket API を利用しない。

◆get_highscore_client_rpc()

  12:	int get_highscore_client_rpc( char *server,
  13:	                              score_record_t records[], int len ) {
  14:	    CLIENT *clnt;
  15:	    get_highscore_result_t *result;
  16:	    int *arg, n, i;
  17:	        clnt = clnt_create(server,HIGHSCORE_RPC_PROG,
  18:	                           HIGHSCORE_RPC_VERSION, "tcp");
  19:	        if( clnt == NULL ) {
  20:	            clnt_pcreateerror( server );
  21:	            goto error0;
  22:	        }
  23:	        arg = &len;
  24:	        result = get_highscore_1( arg,clnt );
  25:	        if( result == NULL ) {
  26:	            clnt_perror( clnt, "call failed:" );
  27:	            goto error1;
  28:	        }
  29:	        n = result->records.records_len ;
  30:	        if( n > len ) {
  31:	            fprintf(stderr,"received message too large: %d > %d\n", n, len );
  32:	            goto error1;
  33:	        }
  34:	        for( i=0 ; i<n; i++ ) {
  35:	            records[i] = result->records.records_val[i];
  36:	        }
  37:	        xdr_free( (xdrproc_t)xdr_get_highscore_result_t, (char *)result );
  38:	        clnt_destroy( clnt );
  39:	        return( n );
  40:	
  41:	error1: clnt_destroy( clnt );
  42:	error0: return( -1 );
  43:	}
  44:	
この関数の中心部分は、自動生成されたスタブ get_highscore_1() を呼び出す部分である。 引数は、インタフェースで定義された型 intへのポインタと、 CLIENT 構造体へのポインタ、 結果は、インタフェースで定義された構造体 get_highscore_result_tへのポインタである。

スタブget_highscore_1() は、rpcgen が生成する *_clnqt.c というファイルに含まれてる。これは、引数をマーシャリ ングして要求メッセージを組み立て、サーバに送信する。サーバから応答メッ セージを受け取り、アンマーシャリングして、結果として返す。アンマーシャ リングの時、結果を保持するメモリを、malloc() で確保している。

clnt_create() は、CLIENT 構造体を確保する。 引数は、サーバのホスト名、 プログラム番号バージョン番号、 通信に使うプロトコルである。 使い終わった構造体は、clnt_destroy() で解放する。

結果 result は、可変長の配列である。 result->records.records_len には、可変長配列の要素数、 result->records.records_val[i] には、可変長配列のi番目の要素 が含まれている。 ループして、その可変長配列から引数で与えられた配列にコピーしている。

xdr_free() で、アンマーシャリングの時に確保したメモリを解放している。 内部では、free() が呼ばれている。

TCP版と異なり、マーシャリング、メッセージの送受信のコードが含まれていない。

◆put_score_client_rpc()

  45:	int put_score_client_rpc( char *server, int score, char *name ) {
  46:	    CLIENT *clnt;
  47:	    score_record_t arg;
  48:	    int *result, ok;
  49:	        clnt = clnt_create( server,HIGHSCORE_RPC_PROG,
  50:	                            HIGHSCORE_RPC_VERSION, "tcp");
  51:	        if( clnt == NULL ) {
  52:	            clnt_pcreateerror( server );
  53:	            goto error0;
  54:	        }
  55:	        arg.score = score;
  56:	        memset( arg.name, 0, HIGHSCORE_NAME_LEN );
  57:	        snprintf( arg.name, HIGHSCORE_NAME_LEN, "%s", name );
  58:	        result = put_score_1( &arg, clnt );
  59:	        if( result == NULL ) {
  60:	            clnt_perror( clnt, "call failed:" );
  61:	            goto error1;
  62:	        }
  63:	        ok = *result;
  64:	        xdr_free( (xdrproc_t)xdr_int, (char *)result );
  65:	        clnt_destroy( clnt );
  66:	        return( ok );
  67:	
  68:	error1: clnt_destroy( clnt );
  69:	error0: return( -1 );
  70:	}
この関数の中心部分は、自動生成されたスタブ put_score_1() を呼び出す部分である。

◆サーバ側のプログラム

[highscore_rpc-server.c]

   1:	
   2:	/*
   3:	        highscore_rpc-server.c -- The hiscore server using SunRPC.
   4:	        Created on: 2009/01/09 01:31:49
   5:	        ~yas/dsys/highscore/rpc/highscore_rpc-server.c
   6:	*/
   7:	
   8:	#include <stdio.h>      /* stderr, fprintf() */
   9:	#include <stdlib.h>     /* strtol() */
  10:	#include <string.h>     /* memcpy() */
  11:	#include "highscore_rpc.h"
  12:	
  13:	/* Hiscore data in memory */
  14:	static score_record_t hiscore_records[HIGHSCORE_MAX_RECORDS];
  15:	static int            hiscore_nelements;
  16:	
  17:	static void hiscore_server( int portno );
  18:	static void hiscore_request_reply( int com );
  19:	static int insert_score( score_record_t records[], int len, int nelement,
  20:	                         int score, char *user );
  21:	static int find_posision( score_record_t records[], int len, int nelement,
  22:	                          int score );
  23:	
  24:	get_highscore_result_t *
  25:	get_highscore_1_svc( int *argp, struct svc_req *rqstp ) {
  26:	    static get_highscore_result_t  result;
  27:	    int len,i,n ; 
  28:	        xdr_free( (xdrproc_t)xdr_get_highscore_result_t, (char *)&result );
  29:	        len = *argp;
  30:	        if( len > HIGHSCORE_MAX_RECORDS )
  31:	            len = HIGHSCORE_MAX_RECORDS;
  32:	        n = len < hiscore_nelements ? len : hiscore_nelements ;
  33:	        /* return n and hiscore_records[0..n-1] to client. */
  34:	        result.records.records_len = n ;
  35:	        result.records.records_val = malloc( sizeof(score_record_t)*n );
  36:	        if( result.records.records_val == NULL )
  37:	        {
  38:	            fprintf( stderr,"highscore_rpc-server: no memory\n" );
  39:	            result.records.records_len = 0;
  40:	            goto error0;
  41:	        }
  42:	        for( i=0 ; i<n ; i++ ) {
  43:	            result.records.records_val[i] = hiscore_records[i];
  44:	        }
  45:	error0: return( &result );
  46:	}
  47:	
  48:	int *put_score_1_svc( score_record_t *argp, struct svc_req *rqstp ) {
  49:	    static int result;
  50:	/*      xdr_free( (xdrproc_t)xdr_int, (char *)&result ); *//* Not needed. */
  51:	        hiscore_nelements =
  52:	            insert_score( hiscore_records, HIGHSCORE_MAX_RECORDS,
  53:	                          hiscore_nelements, argp->score, argp->name );
  54:	        return( &result );
  55:	}
  56:	
  57:	static int insert_score( score_record_t records[], int len, int nelement,
  58:	                         int score, char *user ) {
...
  75:	static int find_posision( score_record_t records[], int len, int nelement,
  76:	                          int score ) {
...
サーバ側では、手続き get_highscore_1_svc()put_score_1_svc() を記述する。 引数と結果は、rpcgen のソースで定義した構造体へのポインタ。

結果を返す時の構造体へのポインタを返す方法が問題。自動変数(auto 変数) にすると、呼出し側に戻った瞬間に無効になる。よく使われるのは、静的変数 (static 変数)に結果を代入して、返すことだが、マルチスレッドプログラミ ングでは問題になる。

malloc() で動的にメモリを確保している。このメモリは、 xdr_free() で解放される。

サーバ側の main() 関数は、rpcgen により 自動生成される。このmain()では、 ポートマッパ(port mapper) へのプログラム番号とバージョン番号の登録される。

■SunRPC で使えるデータ型

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

◆基本型

◆列挙型

ほぼ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要素しか送られない。 複数要素を送りたい時には、配列を使う。

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

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

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

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

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

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

■練習問題

RPCのプログラムの開発は、次のような手順で行うことが多い。 ヒント: インタフェース記述やクライアント側のプログラムは、例題をプロト タイプとする方法がある。つまり、まずは、例題をコピーし、プログラム番号 を書き換え、不要な部分を削除する。その後、必要な部分を書き足す。

練習問題(1) カウンタ

次のような手続きを持つカウンタを RPC サーバとして実現しなさい。

練習問題(2) 整数のスタック

次のようなインタフェースを持つハッシュ表を RPC で実現しなさい。

[intstack.x]

enum intstack_error {
	INTSTACK_OK = 0,
	INTSTACK_OVERFLOW = 1,
	INTSTACK_UNDERFLOW = 2
};

struct pop_result {
    int data;
    int error;
};

program INTSTACK_PROG { 
   version INTSTACK_VERSION {
       intstack_error	PUSH(int)  = 11 ;
       pop_result	POP(void)  = 12 ;
   } = 1 ;
} = 0x20051002 ;

練習問題(3) 整数のスタック(演算付き)

練習問題(2) で、次のような手続きを追加しなさい。
add()
スタックから要素を2つ取り出し、加算を行い、結果をスタックに保存する。
sub()
スタックから要素を2つ取り出し、減算を行い、結果をスタックに保存する。
negate()
スタックから要素を1つ取り出し、符号を反転し、結果をスタックに保存する。
スタック・アンダーフローが起きたときには、エラーを返しなさい。

練習問題(4) 整数の待ち行列

練習問題(2) を参考にし て、待ち行列(queue)を作成しなさい。

練習問題(5) hiscore_rpclongサービス

hiscore_rpcを拡張 して、ハイスコアが登録された日時を保存・表示できるようにしなさい。

練習問題(6) ハッシュ表

次のようなインタフェースを持つハッシュ表を RPC で実現しなさい。

[hashtable.x]

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        INIT(void)       = 10 ; 
       int        PUT(keyvalue_t)  = 11 ; 
       int        GETVALUE(key_t)  = 12 ; 
   } = 1 ;
} = 0x20051001 ;
サーバ側では、hcreate(), hsearch() 等のライブラリ関数を用いてもよい。

練習問題(7) getenv/putenv

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

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

練習問題(8) NFS mountクライアント

/usr/include/rpcsvc/mount.x にある手続きのうち、次のどれかを実行するク ライアント側のプログラムを書きなさい。 サーバとしては、orchid-fs1 、または、orchid-fs2 を用いなさい。

練習問題(9) NFSクライアント

/usr/include/rpcsvc/nfs_prot.x にある手続きのうち、次のどれかを実行するク ライアント側のプログラムを書きなさい。 サーバとしては、orchid-fs1 を用いなさい。 ただし、この実験では、ディレクトリのファイル・ハンドルとして、次のどれ かを使ってもよい。 ファイルのファイル・ハンドルとしては、次のどれかを使ってもよい。

練習問題(10) SunRPC自由課題

その他、上記の練習問題と同程度以上の複雑さを持つSunRPC のサーバ、および、 クライアントを作成しなさい。自分自身が今までに作成したプログラムを、 RPC で実行可能にしてもよい。プログラムの中には、RPC にするものに適して いないものがある。この課題を選択する時には、事前に教官に相談しなさい。

■課題

上の練習問題のうち、1つを選択して提出しなさい。 締切りは 2009年1月25日 日曜日 23:59:59 とする。 (注意:再提出になることがある。)

◆レポートの提出方法

まず、次のような「テキストファイル」を作成する。漢字コードとしては、 JIS、Shift-JIS、EUC を受け付ける。(PDF, RTF, ワープロの文書ファイルで は受付ない。テキストでも Unicode (UTF) は、受け付けない。ZIP や tar, gzip 等で固めたり圧縮しないように。)

----------------------------------------------------------------------
学籍番号: 200604321
名前: 漢字の名前
課題番号:M
練習問題番号:N
題名:subject

<内容>
----------------------------------------------------------------------
本文の先頭に学籍番号と名前(漢字の名前がある人は、漢字で)を書きなさい。 課題番号としては、「1」 と記述しなさい。 練習問題番号とは「★練習問題」に続いて表示されている番号である。 題名には、電子メールの Subject: と同様に、内容に即したものをつける。

<内容>は、日本語(または英語)で書きなさい。文章には、述語を付ける。 体言止めは、使ってはならない。単にプログラムを含めるのではなく、「以下 に○○のプログラムを示す」と書くこと。<内容>には、プログラムだけでな く、実行結果(入力と出力)と説明をつける。

問題を難しい方に変えてた場合、または、最初から難しい問題を解いた場合に は、<内容>の部分で主張しなさい。

作成したファイルを、次のページから投稿する。

分散システム/ レポート提出ページ

上で書いた課題番号、題名を繰り返し指定する。さらに、WWW ブラウザの機能 を使って作成したレポートのファイルを選択する。 最後に、「提出」ボタンを押す。

提出されたレポートは、次のボタンで表示できます。

もし、提出に失敗したり、提出には成功しても確認画面に現れない場合には、 新城(yas@is.tsukuba.ac.jp)かTAに、連絡しなさい。

レポートを再提出する時には、どの部分を修正したのかが簡単にわかるように 説明しなさい。

提出したレポートは、講義が終るまで保存しなさい。


Last updated: 2009/01/20 15:04:11
Yasushi Shinjo / <yas@is.tsukuba.ac.jp>