単純インタフェースでは、その他の RPC ルーチンは不要なため最も簡単に使用できるレベルです。しかし、利用できる通信メカニズムの制御は制限されます。このレベルでのプログラム開発は、早く行うことができ、rpcgen コンパイラによって直接サポートされます。大部分のアプリケーションに対しては、rpcgen が提供する機能で十分でしょう。
RPC サービスの中には C の関数としては提供されていないものがありますが、それも RPC プログラムとして使用できます。単純インタフェースライブラリルーチンは、詳細な制御を必要としないプログラムでは RPC 機能を直接使用できます。rusers() のようなルーチンは、RPC サービスライブラリ librpcsvc にあります。例 4-1は、RPC ライブラリルーチン rusers() を呼び出して、遠隔ホスト上のユーザ数を表示します。
#include <rpc/rpc.h> #include <rpcsvc/rusers.h> #include <stdio.h> /* * a program that calls the * rusers() service */ 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: rusers¥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 /* サーバホストの名前 */ u_long prognum /* サーバプログラム番号 */ u_long versnum /* サーババージョン番号 */ xdrproc_t inproc /* 引数を符号化する XDR フィルタ */ char *in /* 引数へのポインタ */ xdr_proc_t outproc /* 結果を復号化するフィルタ */ char *out /* 結果を格納するアドレス */ char *nettype /* トランスポートの選択 */ );
この関数は、host 上で、prognum、versum、procnum によって指定する手続きを呼び出します。遠隔手続きに渡される引数は、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 のようになります。
#include <stdio.h> #include <utmp.h> #include <rpc/rpc.h> #include <rpcsvc/rusers.h> /* *RUSERSPROG RPC プログラムを呼び出すプログラム */ main(argc, argv) int argc; char **argv; { unsigned long 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_long, (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
long
型であるため、rpc_call() の最初の戻りパラメタは xdr_u_long (unsigned
long
用) で、2 番目は &nusers (unsigned
long
型の値があるメモリへのポインタ)です。RUSERSPROC_NUM には引数がないため、rpc_call() の XDR 符号化関数は xdr_void() で、その引数は NULL です。
単純インタフェースを使用するサーバプログラムは、大変理解しやすいものです。これは単に、呼び出される手続きを登録するため rpc_reg() を呼び出し、次に、RPC ライブラリの遠隔手続きディスパッチャである svc_run() を呼び出して、入ってくる要求を待ちます。
rpc_reg() には次の引数があります。
rpc_reg ( u_long prognum /* サーバプログラム番号r */ u_long versnum /* サーババージョン番号 */ u_long procnum /* サーバ手続き番号 */ char *procname /* 遠隔関数の名前 */ xdrproc_t inproc /* 引数を符号化するフィルタ */ xdrproc_t outproc /* 結果を復号化するフィルタ */ char *nettype /* トランスポートの選択 */ );
svc_run() は RPC 呼び出しメッセージに応えてサービス手続きを起動します。rpc_reg() のディスパッチャは遠隔手続きが登録されたときに指定された XDR フィルタを使用して、遠隔手続きの引数の復号化と、結果の符号化を行います。サーバプログラムについての注意点をいくつか挙げます。
ほとんどの RPC アプリケーションが、関数名の後に _1 を付ける命名規則に従っています。手続き名に _n 番号を付けることにより、サービスのバージョン番号 n を表します。
引数と結果はアドレスで渡されます。遠隔で呼び出される関数はすべてこうなります。関数の結果として NULL を渡すと、クライアントには応答が送信されません。送信する応答がないと仮定されます。
結果は固定のデータ領域に存在します。これは、その値が実際の手続きが終了したあとにアクセスされるからです。RPC 応答メッセージを作成する RPC ライブラリ関数は結果にアクセスして、その値をクライアントに戻します。
引数は 1 つだけ使用できます。データに複数の要素がある場合、構造体の中に入れると、1 つの引数として渡すことができます。
手続きは、指定するタイプのトランスポートごとに登録されます。タイプのパラメタが (char *)NULL
の場合、手続きは NETPATH により指定されるすべてのトランスポートに登録されます。
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_long, "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_long() のような XDR プリミティブを指定することもできますし、引数として渡された構造体全体を処理するようなユーザ作成の変換ルーチンを指定することもできます。引数の変換ルーチンは 2 つの引数を取ります。1 つは変換結果へのポインタで、もう 1 つは XDR ハンドルへのポインタです。
表 4-1 XDR プリミティブタイプのルーチン
XDR プリミティブ・ルーチン |
|||
---|---|---|---|
xdr_wrapstring() から呼び出す xdr_string() はプリミティブではなく、3 つ以上の引数を取ります。
ユーザが作成する変換ルーチンの例を次に示します。手続きに渡す引数は次の構造体に入れます。
struct simple { int a; short b; } simple;
この構造体で渡された引数を変換する XDR ルーチン xdr_simple() は、例 4-3に示すようになります。
#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-2 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に示すようになります。
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() を使用します。
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 へのポインタを変換します。
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() を呼び出してもよいことに注意してください。