ONC+ 開発ガイド

単純インタフェース

単純インタフェースでは、その他の RPC ルーチンは不要なため最も簡単に使用できます。しかし、利用できる通信メカニズムの制御は制限されます。このレベルでのプログラム開発は早く、rpcgen コンパイラによって直接サポートされます。大部分のアプリケーションに対しては、rpcgen が提供する機能で十分です。

RPC サービスの中には C の関数としては提供されていないものがありますが、それも RPC プログラムとして使用できます。単純インタフェースライブラリルーチンは、詳細な制御を必要としないプログラムでは RPC 機能を直接使用できます。rnusers() のようなルーチンは、RPC サービスライブラリ librpcsvc にあります。例 4-1 は、RPC ライブラリルーチン rnusers() を呼び出して、遠隔ホスト上のユーザー数を表示します。


例 4-1 rnusers プログラム

#include <rpc/rpc.h>
#include <rpcsvc/rusers.h>
#include <stdio.h>
 
/*
 * rnusers() サービスを
 * 呼び出すプログラム
 */
 
main(argc, argv)
	int argc;
 char **argv;
{
	int num;
 
 if (argc != 2) {
		fprintf(stderr, "usage: %s hostname¥n",
 					argv[0]);
		exit(1);
	}
 if ((num = rnusers(argv[1])) < 0) {
		fprintf(stderr, "error: rnusers¥n");
		exit(1);
	}
	fprintf(stderr, "%d users on %s¥n", num,
						argv[1] );
	exit(0);
} 

例 4-1 のプログラムを次のコマンドを使用してコンパイルします。

cc program.c -lrpcsvc -lnsl

クライアント側

単純インタフェースのクライアント側には、rpc_call() という関数が 1 つだけあります。次の 9 個のパラメータがあります。

int	0 or error code
rpc_call (  
       char				*host		/* サーバーホストの名前 */
       rpcprog_t		prognum 	/* サーバープログラム番号 */
       rpcvers_t		versnum		/* サーバーバージョン番号 */
       rpcproc_t		procnum		/* サーバー手続き番号 */
       xdrproc_t		inproc		/* 引数を符号化する XDR フィルタ */
       char 			*in			/* 引数へのポインタ */
       xdr_proc_t		outproc		/* 結果を復号化するフィルタ */
       char				*out		/* 結果を格納するアドレス */
       char				*nettype	/* トランスポートの選択 */
);

この関数は、host 上で、prognumversumprocnum によって指定する手続きを呼び出します。遠隔手続きに渡される引数は、in パラメータによって指定され、inproc はこの引数を符号化するための XDR フィルタです。out パラメータは、遠隔手続きから戻される結果が置かれるアドレスです。outproc は、結果を復号化してこのアドレスに置く XDR フィルタです。

クライアントプログラムは、サーバーから応答を得るまで rpc_call() のところで停止します。サーバーが呼び出しを受け入れると、0 の値で RPC_SUCCESS を返します。呼び出しが失敗した場合は、0 以外の値が返されます。この値は、clnt_stat で指定される型に型変換されます。これは RPC インクルードファイルの中で定義される列挙型で、clnt_sperrno() 関数により解釈されます。この関数は、このエラーコードに対応する標準 RPC エラーメッセージへのポインタを返します。

この例では、/etc/netconfig に列挙されているすべての選択可能な可視トランスポートが試されます。試行回数を指定するには、下位レベルの RPC ライブラリを使用する必要があります。

複数の引数と複数の結果は、構造体の中で処理されます。

単純インタフェースを使用するために例 4-1 を変更すると、例 4-2 のようになります。


例 4-2 単純インタフェースを使用する rusers プログラム

#include <stdio.h>
#include <utmpx.h>
#include <rpc/rpc.h>
#include <rpcsvc/rusers.h>
 
/*
 *RUSERSPROG RPC プログラムを呼び出すプログラム
 */
 
main(argc, argv)
	int argc;
	char **argv;
{
 unsigned int nusers;
	enum clnt_stat cs;
 
	if (argc != 2) {
		   fprintf(stderr, "usage: rusers hostname¥n");
 	   exit(1);
	}
	if( cs = rpc_call(argv[1], RUSERSPROG,
			 	RUSERSVERS,	RUSERSPROC_NUM, xdr_void,
 				(char *)0, xdr_u_int, (char *)&nusers,
                    "visible")  !=  RPC_SUCCESS )  {
		          clnt_perrno(cs);
 	          exit(1);
	           }
	fprintf(stderr, "%d users on %s¥n", nusers,
						argv[1] );
 exit(0);
}

マシンが異なれば、データ型も異なる表現になるため、rpc_call() は RPC 引数の型と RPC 引数へのポインタを必要とします (サーバーから返される結果についても同様)。RUSERSPROC_NUM の場合、戻り値は unsigned int 型であるため、rpc_call() の最初の戻りパラメータは xdr_u_int (unsigned int 用) で、2 番目は &nusers (unsigned int 型の値があるメモリーへのポインタ) です。RUSERSPROC_NUM には引数がないため、rpc_call() の XDR 符号化関数は xdr_void() で、その引数は NULL です。

サーバー側

単純インタフェースを使用するサーバープログラムは、大変理解しやすいものです。これは単に、呼び出される手続きを登録するため rpc_reg() を呼び出し、次に、RPC ライブラリの遠隔手続きディスパッチャである svc_run() を呼び出して、入ってくる要求を待ちます。

rpc_reg() には次の引数があります。

rpc_reg (
      rpcprog_t		prognum			/* サーバープログラム番号 */
      rpcvers_t		versnum			/* サーバーバージョン番号 */
      rpcproc_t		procnum			/* サーバー手続き番号 */
      char				*procname 	/* 遠隔関数の名前 */
      xdrproc_t		inproc			/* 引数を符号化するフィルタ */
      xdrproc_t		outproc			/* 結果を復号化するフィルタ */
      char			 *nettype			/* トランスポートの選択 */
);

svc_run() は RPC 呼び出しメッセージに応えてサービス手続きを起動します。rpc_reg() のディスパッチャは遠隔手続きが登録されたときに指定された XDR フィルタを使用して、遠隔手続きの引数の復号化と、結果の符号化を行います。サーバープログラムについての注意点をいくつか挙げます。

ユーザーが作成する登録ルーチン

rpcgen は汎用のコードジェネレータであるため、ユーザーが自分で書いた方が、効率のよい短いコードにできる場合があります。そのような登録ルーチンの例を次に示します。次の例では、手続きを 1 つ登録してから、svc_run() に入ってサービス要求を待ちます。

#include <stdio.h>
 #include <rpc/rpc.h>
 #include <rpcsvc/rusers.h>
 void *rusers();
 
 main()
 {
 	if(rpc_reg(RUSERSPROG, RUSERSVERS,
						RUSERSPROC_NUM, rusers,
 					xdr_void, xdr_u_int,
						"visible") == -1) {
 		fprintf(stderr, "Couldn't Register¥n");
 		exit(1);
 	}
 	svc_run();     /* この関数は値を戻さない */
 	fprintf(stderr, "Error: svc_run
						returned!¥n");
 	exit(1);
 }

rpc_reg() は、異なるプログラム、バージョン、手続きを登録するごとに何度でも呼び出すことができます。

任意のデータ型の引き渡し

遠隔手続きへ渡すデータ型と遠隔手続きから受け取るデータ型は、事前に定義した型あるいはプログラマが定義する型の任意のものが可能です。RPC では、データを XDR (external data representation: 外部データ表現) 形式という標準データ形式に変換してからトランスポートに送信するため、個々のマシンに固有のバイト順序や構造体のデータレイアウトに関係なく、任意のデータ構造を扱うことができます。マシン固有のデータ形式から XDR 形式に変換することをシリアライズといい、反対に XDR 形式からマシン固有のデータ形式に変換することをデシリアライズといいます。

rpc_call()rpc_reg() の引数で変換ルーチンを指定するときは、 xdr_u_int() のような XDR プリミティブを指定することも、引数として渡された構造体全体を処理するようなユーザーが作成した変換ルーチンを指定することもできます。引数の変換ルーチンは 2 つの引数を取ります。1 つは変換結果へのポインタで、もう 1 つは XDR ハンドルへのポインタです。

表 4-1 XDR プリミティブタイプのルーチン

XDR プリミティブ・ルーチン 

xdr_int()

xdr_netobj()

xdr_u_long()

xdr_enum()

xdr_long()

xdr_float()

xdr_u_int()

xdr_bool()

xdr_short()

xdr_double()

xdr_u_short()

xdr_wrapstring()

xdr_char()

xdr_quadruple()

xdr_u_char()

xdr_void()

xdr_hyper()

xdr_u_hyper()

 

 

int_types.h 内にある固定幅の整数タイプに慣れている ANSI C プログラマにとって都合がよいように、ルーチン xdr_char()xdr_short()xdr_int()xdr_hyper() (および、それぞれの符号なしバージョン) には、表 4-2 で示すように、ANSI C を連想させる名前の付いた同等の関数があります。

表 4-2 プリミティブタイプの等価関数

関数 

Equivalent 

xdr_char()

xdr_int8_t()

xdr_u_char()

xdr_u_int8_t()

xdr_short()

xdr_int16_t()

xdr_u_short()

xdr_u_int16_t()

xdr_int()

xdr_int32_t()

xdr_u_int()

xdr_u_int32_t()

xdr_hyper()

xdr_int64_t()

xdr_u_hyper()

xdr_u_int64_t()

xdr_wrapstring() から呼び出す xdr_string() はプリミティブではなく、3 つ以上の引数を取ります。

ユーザーが作成する変換ルーチンの例を次に示します。手続きに渡す引数は次の構造体に入れます。

struct simple {
 	int a;
 	short b;
 } simple;

この構造体で渡された引数を変換する XDR ルーチン xdr_simple() は、例 4-3 に示すようになります。


例 4-3 xdr_simple() ルーチン

#include <rpc/rpc.h>
#include "simple.h"
 
bool_t
xdr_simple(xdrsp, simplep)
	XDR *xdrsp;
	struct simple *simplep;
{
 if (!xdr_int(xdrsp, &simplep->a))
		return (FALSE);
 if (!xdr_short(xdrsp, &simplep->b))
		return (FALSE);
 return (TRUE);
}

rpcgen でも、同じ機能を持つ変換ルーチンを自動生成できます。

XDR ルーチンは、データ変換に成功した場合はゼロ以外の値 (C では TRUE) を返し、失敗した場合はゼロを返します。XDR についての詳細は、付録 C 「XDR プロトコル仕様」 を参照してください。

表 4-3 XDR ブロック構築ルーティン
 基本のルーチン

xdr_array()

xdr_bytes()

xdr_reference()

xdr_vector()

xdr_union()

xdr_pointer()

xdr_string()

xdr_opaque()

 

たとえば、可変長の整数配列を送るときは、配列へのポインタと配列サイズを次のような構造体にパックします。

struct varintarr {
 	int *data;
 	int arrlnth;
 } arr;

この配列を変換するルーチン xdr_varintarr()例 4-4 に示すようになります。


例 4-4 変換ルーチン xdr_varintarr()

bool_t
xdr_varintarr(xdrsp, arrp)
 XDR *xdrsp;
	struct varintarr *arrp;
{
 return(xdr_array(xdrsp, (caddr_t)&arrp->data,
		(u_int *)&arrp->arrlnth, MAXLEN,
		sizeof(int), xdr_int));
} 

xdr_array() に渡す引数は、XDR ハンドル、配列へのポインタ、配列サイズへのポインタ、配列サイズの最大値、配列要素のサイズ、配列要素を変換する XDR ルーチンへのポインタです。配列サイズが前もってわかっている場合は、例 4-5 のように xdr_vector() を使用します。


例 4-5 変換ルーチン xdr_vector()

int intarr[SIZE];
 
bool_t
xdr_intarr(xdrsp, intarr)
	XDR *xdrsp;
	int intarr[];
{
 return (xdr_vector(xdrsp, intarr, SIZE,
				sizeof(int),
xdr_int));
} 

XDR ルーチンでシリアライズすると、データが 4 バイトの倍数になるように変換されます。たとえば、文字配列を変換すると、各文字が 32 ビットを占有するようになります。xdr_bytes() は、文字をパックするルーチンで、xdr_array() の最初の 4 つの引数と同様の引数を取ります。

NULL で終わる文字列は xdr_string() で変換します。このルーチンは、長さの引数がない xdr_bytes() ルーチンのようなものです。文字列をシリアライズするときは strlen() で長さを取り出し、デシリアライズするときは NULL で終わる文字列を生成します。

例 4-6 では、組み込み関数 xdr_string()xdr_reference() を呼び出して、文字列へのポインタと、前の例で示した構造体 simple へのポインタを変換します。


例 4-6 変換ルーチン xdr_reference()

struct finalexample {
	char *string;
 struct simple *simplep;
} finalexample;
 
bool_t
xdr_finalexample(xdrsp, finalp)
	XDR *xdrsp;
	struct finalexample *finalp;
{
 if (!xdr_string(xdrsp, &finalp->string,
			MAXSTRLEN))
 	return (FALSE);
	if (!xdr_reference( xdrsp, &finalp->simplep,
 		sizeof(struct simple), xdr_simple))
		return (FALSE);
 return (TRUE);
 
}

ここで、xdr_reference() の代わりに xdr_simple() を呼び出してもよいことに注意してください。