ONC+ 開発ガイド

複雑なデータ構造の引き渡し

「ローカル手続きを遠隔手続きに変換」では、クライアント側とサーバー側のRPC コードの生成について説明しました。rpcgen を使用して、XDR ルーチンを生成することもできます (XDR ルーチンは、ローカルデータ形式と XDR 形式との相互変換を行います)。

遠隔ディレクトリを一覧表示する RPC サービスの全体を 例 3-4 に示します。rpcgen を使用してスタブルーチンと XDR ルーチンの両方を生成します。


例 3-4 RPC 言語で書かれたプロトコル記述ファイル (dir.x)

/*
 * dir.x: 遠隔ディレクトリを一覧表示するサービスのプロトコル
 *
 * rpcgen の機能を説明するためのサンプルプログラム
 */
 
const MAXNAMELEN = 255;						/* ディレクトリエントリの最大長 */
typedef string nametype<MAXNAMELEN>;		/* ディレクトリエントリ */
typedef struct namenode *namelist;			/* リスト形式でリンク */
 
/* ディレクトリリスト内のノード */
struct namenode {
	nametype name;						/* ディレクトリエントリ名 */
	namelist next;						/* 次のエントリ */
};
 
/*
 * READDIR の戻り値
 *
 * どこにでも移植できるアプリケーションにするためには、
 * この例のように UNIX の errno を返さないで、
 * エラーコードリストを設定して使用する方がよい
 * 
 * このプログラムでは、次の共用体を使用して、遠隔呼び出しが
 * 正常終了したか異常終了したかを区別します。
 */
 
union readdir_res switch (int errno) {
	case 0:
		namelist list;		/* 正常終了: 戻り値はディレクトリリスト */
	default:
		void;					/* エラー発生: 戻り値なし */
};
 
/* ディレクトリを一覧表示するプログラムの定義 */
program DIRPROG {
	version DIRVERS {
		readdir_res
		READDIR(nametype) = 1;
	} = 1;
} = 0x20000076; 

上の例の readdir_res のように、RPC 言語のキーワード structunionenum を使用して型を再定義することができます。使用したキーワードは、後にその型の変数を宣言するときには指定しません。たとえば、共用体 foo を定義した場合、union foo ではなく foo で宣言します。

rpcgen でコンパイルすると、RPC の共用体は C 言語の構造体に変換されます。RPC の共用体は、キーワード union を使用して宣言しないでください。

dir.x に対して rpcgen を実行すると、次の 4 つのファイル、(1) ヘッダーファイル、(2) クライアント側のスタブルーチン、(3) サーバー側の骨組み、(4) XDR ルーチンの入った dir_xdr.c というファイルが生成されます。(4) のファイルに入っている XDR ルーチンは、宣言されたデータ型を、ホスト環境のデータ形式から XDR 形式に、またはその逆方向に変換します。

rpcgen では、.x ファイルで使用されている RPC 言語の各データ型に対して、データ型名の前に XDR ルーチンであることを示すヘッダー xdr_ が付いたルーチン (たとえば、xdr_int) が libnsl で提供されるものとみなします。.x ファイルにデータ型が定義されていると、rpcgen はそれに対するルーチンを生成します。msg.x のように、.x ソースファイルにデータ型が定義されていない場合は、_xdr.c ファイルは生成されません。

.x ソースファイルで、libnsl でサポートされていないデータ型を使用し、.x ファイルではそのデータ型を定義しないこともできます。その場合は、xdr_ ルーチンをユーザーが自分で作成することになります。こうして、ユーザー独自の xdr_ ルーチンを提供することができます。任意のデータ型を引き渡す方法についての詳細は、第 4 章「RPC プログラマインタフェース」を参照してください。例 3-5 に、サーバー側の READDIR 手続きを示します。


例 3-5 サーバー側の dir_proc.c の例

/*
 * dir_proc.c: 遠隔手続き readdir 
 */
#include <dirent.h>
#include "dir.h"                /* rpcgen が生成 */
 
extern int errno;
extern char *malloc();
extern char *strdup();
 
readdir_res *
readdir_1(dirname, req)
	nametype *dirname;
	struct svc_req *req;
 
{
	DIR *dirp;
	struct dirent *d;
	namelist nl;
	namelist *nlp;
	static readdir_res res; /* 必ず static で宣言 */
	
	/* ディレクトリのオープン */
	dirp = opendir(*dirname);
	if (dirp == (DIR *)NULL) {
		res.errno = errno;
		return (&res);
	}
	/* 直前の戻り値の領域解放 */
	xdr_free(xdr_readdir_res, &res);
/*
 * ディレクトリエントリをすべて取り出す。ここで割り当てたメモリーは、
 * 次に readdir_1 が呼び出されたときに xdr_free で解放
 */
	nlp = &res.readdir_res_u.list;
	while (d = readdir(dirp)) {
		nl = *nlp = (namenode *) 
							malloc(sizeof(namenode));
		if (nl == (namenode *) NULL) {
			res.errno = EAGAIN;
			closedir(dirp);
			return(&res);
		}
		nl->name = strdup(d->d_name);
		nlp = &nl->next;
	}
	*nlp = (namelist)NULL;
	/* 結果を返す */
	res.errno = 0;
	closedir(dirp);
	return (&res);
}

例 3-6 に、クライアント側のREADDIR 手続きを示します。


例 3-6 クライアント側のプログラム (rls.c)

/*
 * rls.c: クライアント側の遠隔ディレクトリリスト
 */
 
#include <stdio.h>
#include "dir.h"                       /* rpcgen が生成 */
 
extern int errno;
 
main(argc, argv)
	int argc;
	char *argv[];
{
	CLIENT *clnt;
	char *server;
	char *dir;
	readdir_res *result;
	namelist nl;
 
	if (argc != 3) {
		fprintf(stderr, "usage: %s host 
					directory¥n",argv[0]);
		exit(1);
	}
	server = argv[1];
	dir = argv[2];
/*
 * コマンドラインで指定したサーバーの MESSAGEPROG の呼び出しで使用する
 * クライアント「ハンドル」を作成
 */
	cl = clnt_create(server, DIRPROG,
								DIRVERS, "tcp");
	if (clnt == (CLIENT *)NULL) {
		clnt_pcreateerror(server);
		exit(1);
	}
	result = readdir_1(&dir, clnt);
	if (result == (readdir_res *)NULL) {
		clnt_perror(clnt, server);
		exit(1);
	}
/*
 * 遠隔手続き呼び出しは正常終了
 */
	if (result->errno != 0) {
   /*
    * 遠隔システム上のエラー。エラーメッセージを表示して終了
    */
		errno = result->errno;
		perror(dir);
		exit(1);
	}
/*
 * ディレクトリリストの取り出しに成功。ディレクトリリストを表示
 */
	for (nl = result->readdir_res_u.list;
								nl != NULL;
		nl = nl->next) {
		printf("%s¥n", nl->name);
	}
	xdr_free(xdr_readdir_res, result);
	clnt_destroy(cl);
	exit(0);
}

この前のサンプルプログラムと同様に、システム名を localremote とします。ファイルのコンパイルと実行は、次のコマンドで行います。

remote$ rpcgen dir.x
remote$ cc -c dir_xdr.c
remote$ cc rls.c dir_clnt.c dir_xdr.o -o rls -lnsl
remote$ cc dir_svc.c dir_proc.c dir_xdr.o -o dir_svc -lnsl
remote$ dir_svc

local システムに rls() をインストールすると、次のように remote システム上の/usr/share/lib の内容を表示できます。

local$ rls remote /usr/share/libascii
eqnchar
greek
kbd
marg8
tabclr
tabs
tabs4
local$ 

rpcgen が生成したクライアント側のコードは、RPC 呼び出しの戻り値のために割り当てたメモリーを解放しないので、必要がなくなったら xdr_free() を呼び出してメモリーを解放してください。xdr_free() の呼び出しは free() ルーチンの呼び出しに似ていますが、XDR ルーチンの戻り値のアドレスを引き渡す点が異なります。この例では、ディレクトリリストを表示した後で次のように xdr_free() を呼び出しています。

xdr_free(xdr_readdir_res,result); 


注 -

xdr_free() を使用して malloc() で割り当てたメモリーを解放します。xdr_free() を使用してメモリーを解放すると、メモリーリークを生じて失敗します。