SunRPCプログラミング

 並行分散ソフトウェア/並列分散ソフトウェア

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

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

■連絡

1月20日は、4時間目から休講です。3時間目は、講義があります。クイズはあ りませんが、レポートがあります。

■捕捉

整列化(marshaling)について RPC の回復について

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 の Binding

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

portmap

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

図? portmapper の働き

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

ディレクトリ一覧サービス

ディレクトリ一覧サービス(dirlist)。

インタフェース

   1:	struct delist {
   2:	        string del_name<> ;
   3:	        struct delist *del_next ;
   4:	};
   5:	
   6:	union dirlist_res
   7:	switch(int dlr_errno)
   8:	{
   9:	case 0:
  10:	        delist *dlr_head;
  11:	default:
  12:	        void;
  13:	};
  14:	
  15:	program DIRLIST_PROG {
  16:	    version DIRLIST_VERSION {
  17:	        dirlist_res DIRLIST(string) = 11 ;
  18:	    } = 1;
  19:	} = 0x20001003 ;
17行目が手続きの定義。 DIRLIST が手続きの名前(手続き番号を示す定数の定義)。

この手続きの引数は、string 型、結果は、dirlist_res 型。SunRPC では、基本的には引数も結果も1つずつ。複数必要な時には、構造体を 定義する。

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

dirlist_res は、共用体(可変長の構造体、Pascal 可変長レコード)。

dlr_errno の値が 0 の時
delist 型へのポインタ dlr_head
その他
何も送らない(dlr_errnoのみ)

struct delist は、自分自身へのポインタ(del_next)を含むリスト構造。

bin,lib,etc の3つの要素を含む。

図? struct delist の構造

一般的に RPC では、ポインタを含むデータ構造を送ることはできない。 SunRPC では、ポインタの先の1要素を再帰的に送る機能がある。 これにより、木構造や線形リストを送ることができる。

string del_name<> は、文字列型。可変長(<>)で最 大文字数には定められていない(<>の中が空)。

ヘッダファイル

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

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

struct delist {
	char *del_name;
	struct delist *del_next;
};
typedef struct delist delist;

struct dirlist_res {
	int dlr_errno;
	union {
		delist *dlr_head;
	} dirlist_res_u;
};
typedef struct dirlist_res dirlist_res;

#define DIRLIST_PROG 0x20001003
#define DIRLIST_VERSION 1

#if defined(__STDC__) || defined(__cplusplus)
#define DIRLIST 11
extern  dirlist_res * dirlist_1(char **, CLIENT *);
extern  dirlist_res * dirlist_1_svc(char **, struct svc_req *);
extern int dirlist_prog_1_freeresult (SVCXPRT *, xdrproc_t, caddr_t);

#else /* K&R C */
#define DIRLIST 11
extern  dirlist_res * dirlist_1();
extern  dirlist_res * dirlist_1_svc();
extern int dirlist_prog_1_freeresult ();
#endif /* K&R C */

サーバ側のプログラム

[dirlist_server.c]

:	/*
   2:	        dirlist_server.c -- ディレクトリの内容を表示するRPCのプログラム(サーバ側)
   3:	        Created on: 2006/01/18 20:33:31
   4:	*/
   5:	
   6:	#include <sys/types.h>          /* opendir(2) */
   7:	#include <dirent.h>             /* opendir(2) */
   8:	#include <errno.h>              /* errno */
   9:	#include <stdlib.h>             /* malloc() */
  10:	#include <string.h>             /* strlen() */
  11:	#include <stdio.h>              /* snprintf() */
  12:	
  13:	#include "dirlist.h"
  14:	static struct delist *make_delist( DIR *dirp );
  15:	
  16:	dirlist_res *
  17:	dirlist_1_svc(char **argp, struct svc_req *rqstp)
  18:	{
  19:	    static dirlist_res  result;
  20:	    DIR *dirp ;
  21:	    char *dirname ;
  22:	
  23:	        xdr_free((xdrproc_t)xdr_dirlist_res, (char *)&result);
  24:	
  25:	        dirname = *argp ;
  26:	        dirp = opendir( dirname );
  27:	        if( dirp == 0 )
  28:	        {
  29:	            result.dlr_errno = errno ;
  30:	            return( &result );
  31:	        }
  32:	        result.dlr_errno = 0;
  33:	        result.dirlist_res_u.dlr_head = make_delist( dirp );
  34:	        closedir( dirp );
  35:	        return( &result );
  36:	}
  37:	
サーバ側では、手続き dirlist_1_svc() を記述する。 引数と結果は、rpcgen のソースで定義した構造体へのポインタ。

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

opendir(), readdir(), closedir() は、ディレクトリの内容を得るためのラ イブラリ関数。次のようなコードで、"/" の内容が表示される(ls -f /)。

	dirp = opendir( "/" );
	while( (dp = readdir(dirp)) != NULL )
	{
	    printf("%s\n", dp->d_name );
	}
	closedir( dirp );
readdir() は、make_delist() の中で行われる。
  38:	static struct delist *
  39:	make_delist( DIR *dirp )
  40:	{
  41:	    struct dirent *dp ;
  42:	    struct delist *del ;
  43:	    int namelen;
  44:	        if( (dp = readdir(dirp)) == NULL )
  45:	        {
  46:	            return( 0 );
  47:	        }
  48:	        else
  49:	        {
  50:	            del = malloc(sizeof(struct delist));
  51:	            namelen = strlen( dp->d_name );
  52:	            del->del_name = malloc( namelen+1 );
  53:	            snprintf(del->del_name,namelen+1,"%s",dp->d_name );
  54:	            del->del_next = make_delist( dirp );
  55:	            return( del );
  56:	        }
  57:	}
make_delist() は、再帰呼び出しで、struct delist のリストを作る。

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

クライアント側のプログラム

[dirlist_client.c]
   1:	/*
   2:	        dirlist_client.c -- ディレクトリの内容を表示するRPCのプログラム(クライアント側)
   3:	        Created on: 2006/01/18 20:57:49
   4:	*/
   5:	
   6:	#include <stdlib.h>             /* exit() */
   7:	#include <stdio.h>              /* printf() */
   8:	
   9:	#include "dirlist.h"
  10:	
  11:	main( int argc, char *argv[] )
  12:	{
  13:	        if( argc != 3 )
  14:	        {
  15:	            fprintf(stderr,"usage: %% %s server_host dir\n", argv[0]);
  16:	            exit( 1 );
  17:	        }
  18:	        dirlist( argv[1], argv[2] );
  19:	}
  20:	
  21:	dirlist(char *host, char *dir)
  22:	{
  23:	        CLIENT *clnt;
  24:	        dirlist_res  *result;
  25:	        char *arg;
  26:	
  27:	        clnt = clnt_create (host, DIRLIST_PROG, DIRLIST_VERSION, "tcp");
  28:	        if( clnt == NULL )
  29:	        {
  30:	            clnt_pcreateerror( host );
  31:	            exit( 1 );
  32:	        }
  33:	
  34:	        arg = dir;
  35:	        result = dirlist_1( &arg, clnt );
  36:	        if( result == NULL )
  37:	        {
  38:	            clnt_perror( clnt, "call failed");
  39:	            exit( 1 );
  40:	        }
  41:	        print_dirlist_res( result );
  42:	        xdr_free( (xdrproc_t)xdr_dirlist_res, (char *)result );
  43:	        clnt_destroy( clnt );
  44:	}
  45:	

main() の引数は、サーバが動作しているホスト名とディレクトリ。

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

33行目で呼び出している dirlist_1() が、rpcgen によ り生成されたスタブ。引数は、インタフェースで定義された構造体 dirlistargへのポインタと、CLIENT 構造体へ のポインタ。結果は、インタフェースで定義された構造体 dirlistresへのポインタである。この関数は、rpcgen が生成する *_clnt.c というファイルに含まれてる。

スタブ dirlist_1()は、成功すると内部で malloc() を呼 び出しメモリを確保して、そこにサーバから受け取った応答メッセージを 非整列化(unmarshalling) して保存する。このメモリは、利用し終わると xdr_free()で 解放する。

	free( result );
だと、トップレベルの構造体のメモリしか解放されない。内部に含まれている 構造体へのポインタや文字列のメモリを解放するためには、 xdr_free()を呼び出す。

  46:	print_dirlist_res( dirlist_res *result )
  47:	{
  48:	        printf("errno: %d (%s)\n",
  49:	               result->dlr_errno, strerror(result->dlr_errno));
  50:	        switch( result->dlr_errno )
  51:	        {
  52:	        case 0:
  53:	            print_delist( result->dirlist_res_u.dlr_head );
  54:	            break;
  55:	        default:
  56:	            break;
  57:	        }
  58:	}
  59:	
  60:	print_delist( struct delist *del )
  61:	{
  62:	        if( del == NULL )
  63:	        {
  64:	        }
  65:	        else
  66:	        {
  67:	            printf("%s\n",del->del_name );
  68:	            print_delist( del->del_next );
  69:	        }
  70:	}
print_dirlist_res() は、struct dirlist_res を表示する。 print_delist() は、struct delist を表示する。 構造体が再帰している所で、関数も再帰している。

コンパイル

% wget http://www.cs.tsukuba.ac.jp/~yas/sie/cdsoft-2005/2006-01-20/ex/dirlist.x [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/sie/cdsoft-2005/2006-01-20/ex/dirlist_server.c [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/sie/cdsoft-2005/2006-01-20/ex/dirlist_client.c [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/sie/cdsoft-2005/2006-01-20/ex/Makefile [←]
% emacs Makefile [←]
% make [←]
rpcgen dirlist.x
% make [←]
gcc -g -DDEBUG    -c -o dirlist_clnt.o dirlist_clnt.c
gcc -g -DDEBUG    -c -o dirlist_client.o dirlist_client.c
gcc -g -DDEBUG    -c -o dirlist_xdr.o dirlist_xdr.c
gcc -g -DDEBUG     -o dirlist_client  dirlist_clnt.o dirlist_client.o dirlist_xdr.o -lnsl 
gcc -g -DDEBUG    -c -o dirlist_svc.o dirlist_svc.c
gcc -g -DDEBUG    -c -o dirlist_server.o dirlist_server.c
gcc -g -DDEBUG     -o dirlist_server  dirlist_svc.o dirlist_server.o dirlist_xdr.o -lnsl
% []
Solaris, Linux では、-lnsl が必要。Solaris, MacOSX では、-DDEBUG をつ けてコンパイルとfork() しないでフォアグランドで動くサーバができる。

サーバ側の実行

実行する時には、ウインドウを2つ開いて、それぞれでサーバとクライアント を走らせる。まずサーバ側から先に実行する。
% ./dirlist_server [←]
サーバ側は自動的には止まらないので、止めたい時には、^C を押す。

サーバを動かしているホストの別の端末で rpcinfo -p を実行すると、プログ ラム番号536875011 (0x20001003) のプログラムが表示される。

% rpcinfo -p  [←]
   program vers proto   port
    100000    2   tcp    111  portmapper
    100000    2   udp    111  portmapper
 536875011    1   udp  59692
 536875011    1   tcp  50138
% []

MacOSX 10.4 では、portmapp が動作していないことがある。rpcinfo -p で何 も表示されない時には、root で 次のようにして実行する。

launchctl start com.apple.portmap

クライアント側の実行

サーバと同じホストで動かす時には、ホスト名には、localhost を使うと便利。
% ./dirlist_client localhost /usr [←]
errno: 0 (Success)
.
..
lost+found
X11R6
bin
dict
<中略>
tmp
% []

SunRPC で使えるデータ型

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

基本型

整数: char, short, int (32ビット), long, long long (hyper, 64ビット)

各 unsigned。

浮動小数点: float, double, (quadruple 4倍精度)

列挙型

ほぼ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と同様に、構造体にポインタが含まれていた場合、再帰的にファイルに保 存される。異なる機種で読み出すことができる。

練習問題

★練習問題(19) getenv/putenv

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

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

★練習問題(20) ハッシュ表

次のようなインタフェースを持つハッシュ表を 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        PUT(keyvalue_t)  = 11 ; 
       int        GETVALUE(key_t)  = 12 ; 
       keyarray_t GETKEYS(void)    = 13 ; 
   } = 1 ;
} = 0x20051001 ;

★練習問題(21) カウンタ

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

★練習問題(22) ypmatchクライアント

ypmatch コマンドと類似のNISサーバ(YPサーバ)に対するクライアントを作成 しなさい。この課題では、クライアントのみを開発する。サーバについては、 既存の NIS サーバを使いなさい(coins では、orchid-a などで動作している( 2006年3月1日まで))。

このクライアントは、4つの引数を取るものとする。

% myypmatch host domain map key [←]
host
サーバが動いているホスト
domain
NIS で使うドメイン名。domainname コマンドで表示できる。 DNS のドメイン名とは異なるものである(一致していることもある)。
map
表の名前。hosts.byname や passwd.byname など。
key
検索したいもの。hosts.byname ならホスト名、 passwd.byname ならユーザ名。
このクライアントは、/usr/include/rpcsvc/yp.x で 定義されている ypproc_match_2() を呼ぶ。
const YPMAXRECORD = 1024;
const YPMAXDOMAIN = 64;
const YPMAXMAP = 64;
...
typedef string domainname<YPMAXDOMAIN>;
typedef string mapname<YPMAXMAP>;
typedef opaque keydat<YPMAXRECORD>;
...
struct ypreq_key {
	domainname domain;
	mapname map;
	keydat key;
};
...
program YPPROG {
	version YPVERS {
...
		ypresp_val
		YPPROC_MATCH(ypreq_key) = 3;
...
	} = 2;
} = 100004;
keydat は、不定形(任意のバイナリ)である。 keydat_val には、文字列の先頭番地、keydat_len には、文字列の長さ(0を 含まない)を指定する。

★練習問題(23) その他の NIS の手続き

ypmatchクライアント と同様に他のNIS の手続きを実 行しなさい。次のものが簡単と思われる。

★練習問題(24) dirlistlongサービス

ディレクトリ一覧サービス(dirlist)を拡張 して、ls -l の用に、型、モード、サイズ、最終更新時刻などを表示できるよ うにしなさい。

ヒント:stat() システムコールや lstat() システムコールでファイルの属性 を得る。RPC では、単に string を返すのではなく、属性も返す。 返す属性は、自分で .x ファイルに定義して、struct stat からコピーする。

struct stat にある属性のうち、数個を送ればよい。全てを送らなくてもよい。


↑[もどる] ←[1月13日] ・[1月20日] →[1月27日]
Last updated: 2006/01/22 19:27:34
Yasushi Shinjo / <yas@is.tsukuba.ac.jp>