ONC+ 開発ガイド

rpcgen プログラミングテクニック

この節では、RPC プログラミングと rpcgen の使用方法に関するさまざまなテクニックを示します。

ネットワークタイプ / トランスポート選択

rpcgen の省略可能な引数には、使用したいネットワークのタイプや特定のネットワーク識別子を指定するためのものがあります。ネットワーク選択についての詳細は、『Transport Interfaces Programming Guide』を参照してください。

-s フラグを指定すると、指定したタイプのトランスポートからの要求に応答するサーバーが作成されます。たとえば、次のコマンドを実行すると、NETPATH 環境変数で指定した非接続型トランスポートすべてに応答するサーバーが標準出力に書き出されます。

rpcgen -s datagram_n prot.x

NETPATH 環境変数が定義されていない場合は、/etc/netconfig で指定した非接続型トランスポートすべてに応答するサーバーが標準出力に書き出されます。コマンド行では、-s フラグとネットワークタイプのペアを複数指定できます。

同様に、-n フラグを指定すると、1 つのネットワーク識別子で指定したトランスポートからの要求だけに応答するサーバーを作成することができます。


注意 - 注意 -

rpcgen-n フラグを指定して作成したサーバーを使用するときは注意が必要です。ネットワーク識別子は各ホストに固有なため、作成されたサーバーは別のホストで予測通りに機能しないことがあります。


コマンド行の定義文

コマンド行で、C 言語のプリプロセッサシンボルを定義し、値を割り当てることができます。コマンド行の定義文は、たとえば、DEBUG シンボルが定義されているときの条件付きデバッグコードの生成に使用できます。

$ rpcgen -DDEBUG proto.x

ブロードキャスト呼び出しへのサーバーからの応答

手続きがブロードキャスト RPC を通して呼び出され、有効な応答を返せないときは、サーバーはクライアントに応答しないでください。その方がネットワークが混雑しません。サーバーが応答を返さないようにするには、遠隔手続きの戻り値を NULL にします。rpcgen が生成したサーバープログラムは、NULL を受け取った場合は応答しません。

例 3-23 に、NFS サーバーの場合だけ応答する手続きを示します。


例 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 *) &notnull );
}

RPC ライブラリルーチンが応答するには、手続きが NULL 以外のポインタ値を返す必要があります

例 3-23 で手続き reply_if_nfsserver()NULL 以外の値を返すように定義されているならば、戻り値 (&notnull) は静的変数を指していなければなりません。

ポートモニタのサポート

inetdlisten のようなポートモニタは、特定の RPC サービスに対するネットワークアドレスを監視することができます。特定のサービスに対する要求が到着すると、ポートモニタは、サーバープロセスを生成します。サービスを提供したら、サーバーは終了できます。この方法によりシステム資源を節約することができます。rpcgen で生成するサーバー関数 main()inetd で呼び出すことができます。詳細は、「inetd の使用」を参照してください。

サーバープロセスがサービス要求に答えた後、続けて要求が来る場合に備えて一定時間待つことには意味があります。一定時間内に次の呼び出しが起こらなければ、サーバーは終了し、inetd のようなポートモニタがサーバーのための監視を続けます。サーバーが終了しないうちに次の要求が来れば、ポートモニタは新たなプロセスを生成することなく待ち状態のサーバーにその要求を送ります。


注 -

listen() などのポートモニタの場合は、サーバーのための監視を行い、サービス要求が来れば必ず新たなプロセスを生成します。このようなモニタからサーバープロセスを起動する場合は、サーバープロセスはサービス提供後すぐに終了するようにしなければなりません。


rpcgen がデフォルトで生成したサービスは、サービス提供後 120 秒間待ってから終了します。待ち時間を変更するには、-K フラグを使用します。たとえば、次のコマンドでは、サーバーは 20 秒待ってから終了します。

$ rpcgen -K 20 proto.x

サービス提供後すぐに終了させるには、次のように待ち時間に対して 0 を指定します。

$ rpcgen -K 0 proto.x.

ずっと待ち状態を続けて終了しないようにするには、-K -1 と指定します。

タイムアウト値の変更

クライアントプログラムはサーバーに要求を送った後、デフォルトで 25 秒間応答を待ちます。応答待ちのタイムアウト値は、clnt_control() ルーチンを使用して変更できます。clnt_control() ルーチンの使用方法についての詳細は、「標準インタフェース」および rpc(3NSL) のマニュアルページを参照してください。タイムアウト値を変更する場合に、ネットワークを往復するのに必要な最短時間より長くなるようにする必要があります。例 3-24clnt_control () の使用方法を示します。


例 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);

クライアントの認証

クライアント作成ルーチンにはクライアント認証機能はありません。クライアントによっては、サーバーに対して自分自身を証明する必要があります。

次の例では、セキュリティレベルが最も低いクライアント認証方法のうち、一般に使用される方法を示します。よりセキュリティレベルが高い認証方法の詳細については、「認証」を参照してください。


例 3-25 AUTH_SYS クライアントの認証

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() を修正したもので、スーパーユーザーにだけコンソールへのメッセージの表示を許可します。


例 3-26 スーパーユーザーだけが使用できる printmsg_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_nproc には、テーブル内のエントリ数が入っています。

ディスパッチテーブルから手続きを探し出すルーチン find_proc() を次に示します。


例 3-27 ディスパッチテーブルの使用方法

struct rpcgen_table *
find_proc(proc)
  rpcproc_t proc;
{
  if (proc >= dirprog_1_nproc)
       /* error */
  else
  return (&dirprog_1_table[proc]);
} 

ディスパッチテーブル内の各エントリは対応するサービスルーチンへのポインタです。ところが、サービスルーチンは一般にクライアント側のコードでは定義されていません。未解決の外部参照を起こさないため、また、ディスパッチテーブルに対するソースファイルを 1 つだけ要求にするために RPCGEN_ACTION(proc_ver) で、rpcgen サービスルーチン の初期化を行います。

これを使用して、クライアント側とサーバー側に同一のディスパッチテーブルを持たせることができます。クライアント側プログラムをコンパイルするときは、次の define 文を使用します。

#define RPCGEN_ACTION(routine) 0

サーバー側プログラムを作成するときは、次の define 文を使用します。

#define RPCGEN_ACTION(routine)routine

rpcgen の 64 ビットの場合の考慮事項

例 3-27 では、proc は タイプ rpcproc_t として宣言されていることに注意してください。正式には、RPC のプログラム、バージョン、手続き、およびポートは、タイプ u_long として宣言されていました。32 ビットマシン上では、u_long の量は 4 バイト (int として) で、64 ビットシステム上では u_long の量は 8 バイトになります。RPC のプログラム、バージョン、手続き、およびポートを宣言できる場合には必ず、u_longlong の代わりに Solaris 7 で導入されたデータタイプ rpcprog_trpcvers_trpc_proc_trpcport_t を使用する必要があります。これらの新しいタイプを使用すると、32 ビットシステムとの下位互換性があるからです。つまり、この新しいデータタイプによって、rpcgen を実行するシステムに関係なく、4 バイトの量が保証されます。プログラム、バージョン、および手続きの u_long バージョンを使用する rpcgen プログラムを引き続き実行すると、32 ビットマシンと 64 ビットマシンでは、異なる結果になる場合があります。そのため、これらを該当する新しいデータタイプに置き換えることをお勧めします。実際、可能な限り longu_long の使用は避けた方が賢明です (この後の注を参照)。

Solaris 7 以降の rpcgen を起動する場合、rpcgen によって作成されたソースファイルには XDR ルーチンが組み込まれていて、このソースファイルは、そのコードを 32 ビットマシンと 64 ビットマシン上のどちらで実行するかによって、異なるインラインマクロが使用されます。特に、IXDR_GETLONG()IXDR_PUTLONG() の代わりに、IXDR_GET_INT32()IXDR_PUT_INT32() マクロが使用されます。たとえば、rpcgen ソースファイル foo.x に以下のコードが組み込まれている場合を考えます。

struct foo {
        char      c;
        int       i1;
        int       i2;
        int       i3;
        long      l;
        short     s;
};
この場合生成されるファイル foo_xdr.c ファイルでは、次のように適切なインラインマクロが使用されているかどうか確認されます。
#if defined(_LP64) || defined(_KERNEL)
        register int *buf;
#else
        register long *buf;
#endif
 
. . .
 
#if defined(_LP64) || defined(_KERNEL)
                        IXDR_PUT_INT32(buf, objp->i1);
                        IXDR_PUT_INT32(buf, objp->i2);
                        IXDR_PUT_INT32(buf, objp->i3);
                        IXDR_PUT_INT32(buf, objp->l);
                        IXDR_PUT_SHORT(buf, objp->s);
#else
                        IXDR_PUT_LONG(buf, objp->i1);
                        IXDR_PUT_LONG(buf, objp->i2);
                        IXDR_PUT_LONG(buf, objp->i3);
                        IXDR_PUT_LONG(buf, objp->l);
                        IXDR_PUT_SHORT(buf, objp->s);
#endif
このコードにより、 bufint または long のどちらかになるように宣言されますが、これはマシンが 64 ビットであるか、または 32 ビットであるかによって決まるということに注意してください。


注 -

現在は、RPC を通じて転送されるデータタイプのサイズは、4 バイトの量 (32 ビット) に制限されています。8 バイトの long は、アプリケーションが 64 ビットのアーキテクチャを最大限に使用できるようにする場合に提供されます。ただし、プログラマは、int のために long や、x_putlong() などの long を使用する関数の使用は、可能な限り避ける必要があります。上述したように、RPC プログラム、バージョン、手続きおよびポートにはそれぞれ専用のタイプがあります。 それは、データ値が INT32_MININT32_MAX の間にない場合、xdr_long() は失敗し、また、IXDR_GET_LONG()IXDR_PUT_LONG() などのインラインマクロが使用されると、そのデータは切り捨てられるからです (u_long の場合も同様) 。xdr_long(3NSL) のマニュアルページも参照してください。


rpcgen の IPv6 の場合の考慮事項

IPv6 トランスポートをサポートするのは、TI-RPC だけです。現在または将来的に、IPv6 を使用してアプリケーションを実行する予定がある場合は、下位互換スイッチは使用する必要はありません。IPv4 と IPv6 のどちらを選択するかは、 /etc/netconfig 内の関連エントリのそれぞれの順序によって決まります。

アプリケーションのデバッグ

作成したアプリケーションのテストとデバッグは、簡単に実行できます。最初は、クライアント側とサーバー側の手続きをリンクして全体をシングルプロセスとしてテストします。最初は、各手続きをそれぞれクライアント側とサーバー側のスケルトンとはリンクしません。クライアントを作成するRPC ライブラリルーチン (rpc_clnt_create(3NSL) のマニュアルページを参照) と認証ルーチンの呼び出し部分はコメントにします。この段階では、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で考えると、resultNULL の場合は RPC エラーです。エラーの原因を調べるには、clnt_perror() を使用してエラーの原因を表示するか、clnt_sperror() を使用してエラー文字列を取り出します。

もう 1 つのエラーは、サーバー自体のエラーです。例 3-26で考えると、opendir() からエラーが返された場合です。このようなエラーの処理はアプリケーションによって異なるため、プログラマの責任で対応します。

-C オプションを指定した場合はサーバー側ルーチンに _svc という接尾辞が付くため、上の説明がそのまま当てはまらないことに注意してください。