ローカル手続きをリモートプロシージャに変換では、クライアント側とサーバー側の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() を使用してメモリーを解放すると、メモリーリークを生じて失敗します。