ローカル手続きをリモートプロシージャに変換では、クライアント側とサーバー側のRPC コードの生成について説明しました。rpcgen を使用して、XDR ルーチンを生成することもできます (XDR ルーチンは、ローカルデータ形式と XDR 形式との相互変換を行います)。
次に、RPC サービスのサンプルプログラム全体を示します。これは、リモートディレクトリを一覧表示するもので、rpcgen を使用してスタブルーチンと XDR ルーチンの両方を生成します。
/*
* 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 言語のキーワード struct、union、enum を使用して型を再定義することができます。使用したキーワードは、後にその型の変数を宣言するときには指定しません。たとえば、共用体 foo を定義した場合、union foo ではなく foo で宣言します。
rpcgen でコンパイルすると、RPC の共用体は C 言語の構造体に変換されます。RPC の共用体は、キーワード union を使用して宣言しないでください。
dir.x に対して rpcgen を実行すると、次の 4 つのファイルが出力されます。
ヘッダーファイル
クライアント側のスタブルーチン
サーバー側の骨組み
XDR ルーチンが入った dir_xdr.c というファイル
rpcgen では、.x ファイルで使用されている RPC 言語の各データ型に対して、データ型名の前に XDR ルーチンであることを示すヘッダー xdr_ が付いたルーチン (たとえば、xdr_int) が libnsl で提供されるものとみなします。.x ファイルにデータ型が定義されていると、rpcgen はそれに対するルーチンを生成します。msg.x のように、.x ソースファイルにデータ型が定義されていない場合は、_xdr.c ファイルは生成されません。
.x ソースファイルで、libnsl でサポートされていないデータ型を使用し、.x ファイルではそのデータ型を定義しないこともできます。その場合は、xdr_ ルーチンをユーザーが自分で作成することになります。こうして、ユーザー独自の xdr_ ルーチンを提供することができます。任意のデータ型を引き渡す方法についての詳細は、第 4 章「RPC プログラマインタフェース」を参照してください。次に、サーバー側の READDIR 手続きを示します。
/*
* 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);
}
|
次に、クライアント側の READDIR 手続きを次に示します。
/*
* 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);
}
|
この前のサンプルプログラムと同様に、システム名を local と remote とします。ファイルのコンパイルと実行は、次のコマンドで行います。
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_readdir_res,result); を呼び出しています。
xdr_free() を使用して malloc() で割り当てたメモリーを解放します。xdr_free() を使用してメモリーを解放すると、メモリーリークを生じて失敗します。