「ローカル手続きを遠隔手続きに変換」では、クライアント側とサーバー側のRPC コードの生成について説明しました。rpcgen を使用して、XDR ルーチンを生成することもできます (XDR ルーチンは、ローカルデータ形式と XDR 形式との相互変換を行います)。
遠隔ディレクトリを一覧表示する RPC サービスの全体を 例 3-4 に示します。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 つのファイル、(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 手続きを示します。
/* * 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 手続きを示します。
/* * 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_free(xdr_readdir_res,result); |
xdr_free() を使用して malloc() で割り当てたメモリーを解放します。xdr_free() を使用してメモリーを解放すると、メモリーリークを生じて失敗します。