サーバ・クライアントモデルを実現するための便利な方法にリモートプロシ ジャコールrpcという方法があります。これは別の計算機にあるサブルーチン を呼び出す手順として実装されています。呼び出す側は相手の計算機の名前、 プログラム番号、バージョン番号、手続き番号を指定して関数を選びます。引 数を渡す場合、計算機の種類の違いによる数値表現の差を吸収するためにrpc はネットワーク上の表現に置き換えます。XDRと呼ばれます。クライアント側 のプログラムの例を見ましょう。
#include <rpc/rpc.h> int i, o, st; st = callrpc( "yaoya", 600000001L, 2L, 1L, xdr_int, &i, xdr_int, &o ); if( st ) clnt_perrno( st ); /* エラーメッセージを出力 */
非常に簡単です。ホスト名yaoyaにあるプログラム番号600000001、バージョン 番号2、手続き番号1の関数を呼び出します。当然ですがyaoyaの上に該当する 番号の関数を持ったサーバプロセスが走っていなければなりません。次の xdr_intは実は関数の名前で、関数への引数を整数としてXDR表現に変換するも のです。次の&iは関数に与えるデータへのポインターです。その次のxdr_int は先程と同じですが、今度は戻り値をXDRから通常の表現に戻す関数として指 定します。最後の&oに結果が返されることになります。このように引数や戻り 値をポインターで渡すことに注意が必要です。
ではサーバプログラムを書きましょう。
#include <rpc/rpc.h> char *func( int ip ) /* この関数を登録する*/ int *ip; { static o; /* 値を返す記憶域としてstatic宣言 */ o = *ip + 1; /* 何か計算をする。*/ return ( char * ) &o; /* 結果をポインターで返す。*/ } main( ) { int st; /* 関数funcをrpcに登録 */ st = registerrpc( 600000001L, 2L, 1L, func, xdr_int, xdr_int ); if( st ) { /* 登録に失敗 */ } svc_run( ); /* サーバとして走る。戻ってこない。*/ }
サーバ側プログラムも非常に簡単ですね。ではサーバを走らせてみましょう。 もちろんya oyaの上でです。果たしてこの関数funcは登録されているでしょう か。シェルからrpcinfoコマンドを使って調べてみます。今、sakanayaにいる とします。
% rpcinfo -p yaoya program vers proto port 100000 2 tcp 111 portmapper 100000 2 udp 111 portmapper 100029 1 udp 661 keyserv ...(途中省略)... 600000001 2 udp 1080 %
プログラム番号600000001、バージョン2が登録されているのがわかります。 sakanayaでクライアントプログラムを走らせましょう。今の例ではわざわざ yaoyaまで行って1足して帰ってきました。とてつもなく大きい番号をプログラ ム番号に選んだのは色々なネットワークサービスと競合しないようにしたため です。ユーザは0x20000000から0x3fffffffの間を使うように指示されています。 600000001はこの範囲にはいる数字です。予約されているプログラム番号は /etc/rpcというファイルに書かれています。
実際の関数では引数や戻り値が整数だけということはありません。整数や文 字列や実数の混ざった引数を使った呼出しをしたくなります。この場合ちょっ と複雑です。上の例ではxdr_intという変換ルーチンを使用しました。今度は 引数や戻り値を格納する構造体を用意してそれを変換するXDR関数を書いてや る必要があります。サーバクライアントで共通な構造体と、XDR変換関数を次 のように用意します。
struct inarg { int i; double d; }; #define MAXSTRSIZE 256 struct outarg { int i; char *s; }
この例では入力用構造体inargは整数と実数、出力用構造体outargは整数と文 字列をメンバーに持っています。これらの構造体用のXDR変換関数は次のよう になります。
bool_t xdr_in( xdrsp, argp ) XDR *xdrsp; struct inarg *argp; { if( ! xdr_int( xdrsp, &( argp -> i ) ) ) return FALSE; if( ! xdr_double( xdrsp, &( argp -> d ) ) ) return FALSE; return TRUE; } bool_t xdr_out( xdrsp, argp ) XDR *xdrsp; struct outarg *argp; { if( ! xdr_int( xdrsp, &( argp -> i ) ) ) return FALSE; if( ! xdr_string( xdrsp, &( argp -> s ), MAXSTRSIZE ) ) return FALSE; return TRUE; }
XDR関数の中では構造体メンバーのそれぞれについて整数や実数などのプリミ ティブのXDR関数を呼んでいるだけです。この例で出力用構造体に文字列への ポインターがメンバーに入っています。すこし注意が必要です。ではクライア ントを見ましょう。
main( ) { int st; char obuf[ MAXSTRSIZE ]; struct inarg i; struct outarg o; i.i = 123; /* 整数型引数 */ i.d = 0.123456; /* 実数型引数 */ o.s = obuf; /* 文字列の値が返る*/ if( st = callrpc( "yaoya", 600000001L, 2L, 1L, xdr_in, &i, xdr_out, &o ) ) { clnt_perrno( st ); /* エラー */ exit( st ); } printf( "%d %s\n", o.i, o.s ); /* 結果を何かに使う。*/ ...
入力用データを構造体iで与えていますが、文字列型の結果を受け取るo.sメン バーもあらかじめ初期化しています。サーバ側は、
struct outarg *func( ip ) /* これが対象となる関数 */ struct inarg *ip; /* 入力用引数 */ { static struct outarg o; static char obuf[ MAXSTRSIZE ]; o.i = ip -> i * ip -> i; /* 引数を使ってなにか演算 */ sprintf( obuf, "input = %d, %f", ip -> i, ip -> d ); o.s = obuf; return &o; /* 結果を返す */ } main( ) { int st; st = registerrpc( 600000001L, 2L, 1L, func, xdr_in, xdr_out ); if( st ) { /* 登録に失敗 */ } svc_run( ); /* 無限ループ */ }
funcのなかで使った構造体や文字列は後からXDR関数で参照されるのでstatic でなければなりません。(もしくはグローバル変数やmallocで与えてももちろ ん結構です。) これで任意の型の引数の組み合わせでrpcを使うことが出来る ようになりました。プリミティブのX DR関数はman xdrで調べてみてください。
rpcinfoで見てわかるようにrpcはtcpやudpプロトコルを使って実装されてい ます。ここで見た例の場合はデフォルトを使っているのでudpプロトコルが使 われます。tcpプロトコルを使うなどもっと細かな設定をしたい場合などのた めに沢山の関数が用意されています。man rpcをやってみてください。