この節では、RPC プログラミングと rpcgen の使用方法に関するさまざまなテクニックを示します。
rpcgen の省略可能な引数には、使用したいネットワークのタイプや特定のネットワーク識別子を指定するためのものがあります。(ネットワーク選択についての詳細は、『Transport Interfaces Programming Guide』を参照してください)
-s フラグを指定すると、指定したタイプのトランスポートからの要求に応答するサーバが作成されます。たとえば、次のコマンド
rpcgen -s datagram_n prot.x
を実行すると、NETPATH
環境変数で指定した非接続型トランスポートすべてに応答するサーバが標準出力に書き出されます。(NETPATH
環境変数が定義されていない場合は、/etc/netconfig で指定した非接続型トランスポートすべてに応答するサーバが標準出力に書き出されます)。コマンドラインでは、-s フラグとネットワークタイプのペアを複数指定できます。
同様に、-n フラグを指定すると、1 つのネットワーク識別子で指定したトランスポートからの要求だけに応答するサーバを作成することができます。
rpcgen で -n フラグを指定して作成したサーバを使用するときは注意が必要です。ネットワーク識別子は各ホストに固有なため、作成されたサーバは別のホストで予測通りに機能しないことがあります。
コマンド行で、C 言語のプリプロセッサシンボルを定義し、値を割り当てることができます。コマンド行の定義文は、たとえば、DEBUG シンボルが定義されているときの条件付きデバッグコードの生成に使用できます。
$ rpcgen -DDEBUG proto.x
手続きがブロードキャスト RPC を通して呼び出され、有効な応答を返せないときは、サーバはクライアントに応答しないでください。その方がネットワークが混雑しません。サーバが応答を返さないようにするには、遠隔手続きの戻り値を NULL にします。rpcgen が生成したサーバプログラムは、NULL を受け取った場合は応答しません。
例 3-23 に、NFS サーバの場合だけ応答する手続きを示します。
void * reply_if_nfsserver() { char notnull; /* *この場所のみで、そのアドレスが使用可能 */ if( access( "/etc/dfs/sharetab", F_OK ) < 0 ) { /* RPC の応答を禁止 */ return( (void *) NULL ); } /* * NULL 以外の値 notnull を指定したので、RPC は応答する */ return( (void *) ¬null ); }
RPC ライブラリルーチンが応答するには、手続きが NULL 以外のポインタ値を返す必要があります
例 3-23 で手続き reply_if_nfsserver() が NULL 以外の値を返すように定義されているならば、戻り値 (¬null) は静的変数を指していなければなりません。
inetd や listen のようなポートモニタは、特定の RPC サービスに対するネットワークアドレスを監視することができます。特定のサービスに対する要求が到着すると、ポートモニタは、サーバプロセスを生成します。サービスを提供したら、サーバは終了できます。この技法はシステム資源を節約するためのものです。rpcgen で生成するサーバ関数 main() は inetd で呼び出すことができます。その方法についての詳細は、「inetd の使用」を参照してください。
サーバプロセスがサービス要求に答えた後、続けて要求が来る場合に備えて一定時間待つことには意味があります。一定時間内に次の呼び出しが起こらなければ、サーバは終了し、inetd のようなポートモニタがサーバのための監視を続けます。サーバが終了しないうちに次の要求が来れば、ポートモニタは新たなプロセスを生成することなく待ち状態のサーバにその要求を送ります。
listen() などのポートモニタの場合は、サーバのための監視を行い、サービス要求が来れば必ず新たなプロセスを生成します。このようなモニタからサーバプロセスを起動する場合は、サーバプロセスはサービス提供後すぐに終了するようにしなければなりません。
rpcgen がデフォルトで生成したサービスは、サービス提供後 120 秒間待ってから終了します。待ち時間を変更するには、-K フラグを使用します。たとえば、次のコマンド
$ rpcgen -K 20 proto.x
では、サーバは 20 秒待ってから終了します。サービス提供後すぐに終了させるには、次のように待ち時間に対して 0 を指定します。
$ rpcgen -K 0 proto.x.
ずっと待ち状態を続けて終了しないようにするには、-K -1 と指定します。
クライアントプログラムはサーバに要求を送った後、デフォルトで 25 秒間応答を待ちます。応答待ちのタイムアウト値は、clnt_control() ルーチンを使用して変更できます。clnt_control() ルーチンの使用方法についての詳細は、「標準インタフェース」および rpc(3N) のマニュアルページを参照してください)。タイムアウト値を変更するときは、ネットワークを往復するのに必要な最低時間以上になるように注意します。例 3-24にclnt_control () の使用方法を示します。
struct timeval tv; CLIENT *clnt; clnt = clnt_create( "somehost", SOMEPROG, SOMEVERS, "visible" ); if (clnt == (CLIENT *)NULL) exit(1); tv.tv_sec = 60; /* * タイムアウト値を 60 秒に変更 */ tv.tv_usec = 0; clnt_control(clnt, CLSET_TIMEOUT, &tv);
クライアント作成ルーチンにはクライアント認証機能はありません。クライアントによっては、サーバに対して自分自身を証明する必要があります。
次の例では、セキュリティレベルが最低限のクライアント認証方法のうち、一般に使用できる方法を示します。上位レベルの DES 認証方法の詳細については、「認証」を参照してください。
CLIENT *clnt; clnt = clnt_create( "somehost", SOMEPROG, SOMEVERS, "visible" ); if (clnt != (CLIENT *)NULL) { /* AUTH_SYS 形式の認証情報を設定 */ clnt->cl_auth = authsys_createdefault(); }
一定のセキュリティレベルを保持しなければならないサーバではクライアント認証情報が必要になります。クライアント認証情報は、第 2 引数でサーバに渡されます。
例 3-26 に、クライアント認証情報をチェックするサーバプログラムを示します。これは、「rpcgen チュートリアル」で説明した printmessage_1() を修正したもので、スーパーユーザにだけコンソールへのメッセージの表示を許可します。
int * printmessage_1(msg, req) char **msg; struct svc_req *req; { static int result; /* 必ず static で宣言 */ FILE *f; struct authsys_parms *aup; aup = (struct authsys_parms *)req->rq_clntcred; if (aup->aup_uid != 0) { result = 0; return (&result) } /* 元のコードと同じ */ }
RPC パッケージで使用するディスパッチテーブルにプログラムからアクセスしたい場合があります。たとえば、サーバディスパッチルーチンで権限を調べてからサービスルーチンを呼び出したり、クライアントライブラリで記憶管理や XDR データ変換の詳細を扱う場合です。
-T オプションを指定して rpcgen を起動すると、プロトコル記述ファイル proto.x で定義した各プログラムごとの RPC ディスパッチテーブルがファイル proto_tbl.i に出力されます。接尾辞.i は index を表します。-t オプションを指定して rpcgen を起動した場合は、ヘッダファイルだけが生成されます。rpcgen を起動するときは、C 形式モード (-N オプション) と同時に -T または -t フラグを指定することはできません。
ディスパッチテーブルの各エントリは struct
rpcgen_tableで、この構造体はヘッダファイル proto.h で次のように定義されています。
struct rpcgen_table { char *(*proc)(); xdrproc_t xdr_arg; unsigned len_arg; xdrproc_t xdr_res; xdrproc_t len_res };
ここで
proc サービスルーチンへのポインタ xdr_arg 入力 (引数) の xdrルーチンへのポインタ len_arg 入力引数の長さ (バイト数) xdr_res 出力 (結果) の xdrルーチンへのポインタ len_res 出力結果の長さ (バイト数)
サンプルプログラム dir.x のディスパッチテーブル dirprog_1_table は、手続き番号がインデックスになっています。変数 dirprog_1_table には、テーブル内のエントリ数が入っています。
ディスパッチテーブルから手続きを探し出すルーチン find_proc() を次に示します。
struct rpcgen_table * find_proc(proc) u_long proc; { if (proc >= dirprog_1_nproc) /* エラー */ else return (&dirprog_1_table[proc]); }
ディスパッチテーブル内の各エントリは対応するサービスルーチンへのポインタです。ところが、サービスルーチンは一般にクライアント側のコードでは定義されていません。未解決の外部参照を起こさないため、また、ディスパッチテーブルに対するソースファイルを 1 つだけ要求にするために RPCGEN_ACTION(proc_ver) で、rpcgen サービスルーチン の初期化を行います。
これを使用して、クライアント側とサーバ側に同一のディスパッチテーブルを持たせることができます。クライアント側プログラムをコンパイルするときは、次の define 文を使用します。
#define RPCGEN_ACTION(routine) 0
サーバ側プログラムを作成するときは、次の define 文を使用します。
#define RPCGEN_ACTION(routine)routine
作成したアプリケーションのテストとデバッグは、簡単に実行できます。最初は、クライアント側とサーバ側の手続きをリンクして全体をシングルプロセスとしてテストします。(最初は、各手続きをそれぞれクライアント側とサーバ側のスケルトンとはリンクしません)。クライアントを作成するRPC ライブラリルーチン (rpc_clnt_create(3N) のマニュアルページを参照) と認証ルーチンの呼び出し部分はコメントにします。この段階では、libnsl をリンクしてはいけません。
これまでに説明したサンプルプログラムの手続きを、次のコマンドでリンクします。
cc rls.c dir_clnt.c dir_proc.c -o rls
RPC と XDR の関数をコメントにすると、手続き呼び出しは通常のローカル関数呼び出しとなり、プログラムは dbxtool のようなローカルデバッガでデバッグ可能になります。プログラムが正しく機能することが確認されたら、クライアント側プログラムを rpcgen が生成したクライアント側のスケルトンとリンクし、サーバ側プログラムを rpcgen が生成したクライアント側のスケルトンとリンクします。
また、Raw PRC モードを使用して XDR ルーチンをテストすることもできます。その方法についての詳細は、「下位レベルの Raw RPC を使用したプログラムテスト」を参照してください。
RPC 呼び出しで発生するエラーには 2 種類あります。1 つは、遠隔手続き呼び出し過程で起こるエラーです。これには、(1) 手続きが実行できない、(2)遠隔サーバが応答しない、(3) 遠隔サーバが引数を復号化できない、などがあります。例 3-26 で考えると、result が NULL の場合は RPC エラーです。エラーの原因を調べるには、clnt_perror() を使用してエラー原因を表示するか、clnt_sperror() を使用してエラー文字列を取り出します。
もう 1 つのエラーは、サーバ自体のエラーです。例 3-26 で考えると、opendir() からエラーが返された場合です。このようなエラーの処理はアプリケーションによって異なるため、プログラマの責任で対応します。
-C オプションを指定した場合はサーバ側ルーチンに _svc という接尾辞が付くため、上の説明がそのまま当てはまらないことに注意してください。