サーバ・クライアントモデルを実現するための便利な方法にリモートプロシ ジャコール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をやってみてください。