単一のコンピュータ環境で実行されるアプリケーションを、ネットワーク上で実行する分散型アプリケーションに変更する場合を考えます。次の例で、システムコンソールにメッセージを表示するプログラムを分散型アプリケーションに変換する方法を、ステップ別に説明します。変換前のプログラム例 3-1を次に示します。
/* printmsg.c: コンソールにメッセージを表示します。 */ #include <stdio.h> main(argc, argv) int argc; char *argv[]; { char *message; if (argc != 2) { fprintf(stderr, "usage: %s <message>¥n", argv[0]); exit(1); } message = argv[1]; if (!printmessage(message)) { fprintf(stderr,"%s: couldn't print your message¥n",argv[0]); exit(1); } printf("Message Delivered!¥n"); exit(0); } /* コンソールにメッセージを表示する。 * メッセージを表示できたかどうかを示すブール値を返す。*/ printmessage(msg) char *msg; { FILE *f; f = fopen("/dev/console", "w"); if (f == (FILE *)NULL) { return (0); } fprintf(f, "%s¥n", msg); fclose(f); return(1); }
このプログラムをシングルコンピュータ上で使用するときは、次のコマンドでコンパイルして実行できます。
$ cc printmsg.c -o printmsg $ printmsg "Hello, there." Message delivered! $
printmessage() 関数を遠隔手続きに変換すると、ネットワーク上のどこからでも実行できるようになります。rpcgen を使用すると、簡単にこのような変換を実行できます。
最初に、手続きを呼び出すときのすべての引数と戻り値のデータ型を決定します。printmessage() の引数は文字列で、戻り値は整数です。このようなプロトコル仕様を RPC 言語で記述して、遠隔手続きとしての printmessage() を作成することができます。RPC 言語でこのプロトコル仕様を記述したソースコードは次のようになります。
/* msg.x: メッセージを表示する遠隔手続きのプロトコル */ program MESSAGEPROG { version PRINTMESSAGEVERS { int PRINTMESSAGE(string) = 1; } = 1; } = 0x20000001;
遠隔手続きは常に遠隔プログラムの中で宣言されます。上のコードでは、PRINTMESSAGE という手続きが 1 つだけ含まれた遠隔プログラムが 1 つ宣言されています。この例では、PRINTMESSAGE という手続きが、MESSAGEPROG という遠隔プログラム内の手続き 1、バージョン 1 として宣言されています。遠隔プログラムのプログラム番号は、0x20000001 です。(プログラム番号の指定方法については、付録 B 「RPC プロトコルおよび言語の仕様」 を参照してください)。既存の手続きが変更されたり新規手続が追加されたりして、遠隔プログラムの機能が変更されると、バージョン番号が 1 つ増やされます。遠隔プログラムで複数のバージョンを定義することもできますし、1 つのバージョンで複数の手続きを定義することもできます。
プログラム名も手続き名も共に大文字で宣言していることに注意してくだ
また、引数のデータ型を C 言語で書くときのように char *
としないで string
としていることにも注意してください。これは、C 言語で char *
と指定すると、文字型配列とも、単一の文字へのポインタとも解釈できて不明確なためです。RPC 言語では、NULL で終わる文字型配列は string
型で宣言します。
更に次の 2 つのプログラムを書く必要があります。
遠隔手続き自体
遠隔手続きを呼び出すクライアント側のメインプログラム
例 3-2 には、例 3-1 の手続きを PRINTMESSAGE という遠隔手続きに変更したものを示します。
/* * msg_proc.c: 遠隔手続きバージョンの printmessage */ #include <stdio.h> #include "msg.h" /* rpcgen が生成 */ int * printmessage_1(msg, req) char **msg; struct svc_req *req; /* 呼び出しの詳細 */ { static int result; /* 必ず static で宣言 */ FILE *f; f = fopen("/dev/console", "w"); if (f == (FILE *)NULL) { result = 0; return (&result); } fprintf(f, "%s¥n", *msg); fclose(f); result = 1; return (&result); }
遠隔手続き printmessage_1() と、ローカル手続き printmessage() の宣言は次の 4 つの点で異なることに注意してください。
引数が文字へのポインタではなく、文字配列へのポインタになっています。-N オプションを使用しない遠隔手続きの場合は、引数自体が渡されるのではなく、常に引数へのポインタが渡されるからです。-N オプションを指定しなければ、遠隔手続きの呼び出しで引数が 1 つしか渡されません。複数の引数が必要な場合は、引数を struct
型にして渡す必要があります。
引数が 2 つあります。第 2 引数には、関数呼び出しのときのコンテキスト、すなわち、プログラム、バージョン、手続きの番号、raw および canonical の認証、SVCXPRT 構造体へのポインタが入っています。(SVCXPRT 構造体にはトランスポート情報が入っています)。呼び出された手続きが要求されたサービスを実行するときに、これらの情報が必要になる場合があります。
戻り値は、整数そのものではなく整数へのポインタになっています。-N オプションを指定しない遠隔手続きの場合は、戻り値自体ではなく戻り値へのポインタが返されるためです。-M (マルチスレッド)オプション または -A (自動モード) オプションが使用されている限り、戻り値は static
で宣言します。戻り値を遠隔手続きのローカル値にしてしまうと、遠隔手続きがリターンした後、サーバ側スタブプログラムからその値を参照することができなくなります。 -M および -A を使用している場合は、戻り値へのポインタは第 3 引数として手続きに渡されるため、戻り値手続きで宣言されません。
手続き名に _1 が追加されています。一般に rpcgen が遠隔手続き呼び出しを生成するときは、次のように手続き名が決められます。プログラム定義で指定した手続き名 (この場合は PRINTMESSAGE) はすべて小文字に変換され、下線 (_) とバージョン番号 (この場合は 1) が追加されます。このように手続き名が決定されるので、同じ手続きの複数バージョンが使用可能になります。
例 3-3 には、この遠隔手続きを呼び出すクライアント側メインプログラムを示します。
/* * rprintmsg.c: printmsg.c のRPC 対応バージョン */ #include <stdio.h> #include "msg.h" /* rpcgen が生成 */ main(argc, argv) int argc; char *argv[]; { CLIENT *clnt; int *result; char *server; char *message; if (argc != 3) { fprintf(stderr, "usage: %s host message¥n", argv[0]); exit(1); } server = argv[1]; message = argv[2]; /* * コマンドラインで指定したサーバの * MESSAGEPROG の呼び出しで使用する * クライアント「ハンドル」を作成 */ clnt = clnt_create(server, MESSAGEPROG, PRINTMESSAGEVERS, "visible"); if (clnt == (CLIENT *)NULL) { /* * サーバとの接続確立に失敗したため、 * エラーメッセージを表示して終了 */ clnt_pcreateerror(server); exit(1); } /* * サーバ上の遠隔手続き printmessage を呼び出す */ result = printmessage_1(&message, clnt); if (result == (int *)NULL) { /* * サーバの呼び出しでエラーが発生したため、 * エラーメッセージを表示して終了 */ clnt_perror(clnt, server); exit(1); } /* * 遠隔手続き呼び出しは正常終了 */ if (*result == 0) { /* * サーバがメッセージの表示に失敗したため、 * エラーメッセージを表示して終了 */ fprintf(stderr, "%s: could not print your message¥n",argv[0]); exit(1); } /* * サーバのコンソールにメッセージが出力された */ printf("Message delivered to %s¥n", server); clnt_destroy( clnt ); exit(0); }
この 例 3-3 では、次の点に注意してください。
最初に、RPC ライブラリルーチン clnt_create() を呼び出してクライアントハンドルを作成しています。クライアントハンドルは、遠隔手続きを呼び出すスタブルーチンに引き渡されます。(これ以外にもクライアントハンドルを作成する方法があります。詳細については、第 4 章「RPC プログラマインタフェース」を参照してください)。クライアントハンドルを使用する遠隔手続き呼び出しがすべて終了したら、clnt_destroy() を使用してそのクライアントハンドルを破棄し、システム資源を無駄に使用しないようにします。
clnt_create() の最後の引数に "visible" を指定して、/etc/netconfig で visible と指定したすべてのトランスポートを使用できるようにします。詳細については、/etc/netconfig ファイルと『Transport Interfaces Programming Guide』を参照してください。
遠隔手続き printmessage_1() の呼び出しは、第 2 引数として挿入されたクライアントハンドルを除いて、msg_proc.c で宣言された通りに実行されています。戻り値も値ではなく、値へのポインタで返されています。
遠隔手続き呼び出しのエラーには、RPC 自体のエラーと、遠隔手続きの実行中に発生したエラーの 2 種類があります。最初のエラーの場合は、遠隔手続き printmessage_1() の戻り値が NULL になります。2 つめのエラーの場合は、アプリケーションによってエラーの返し方が異なります。この例では、*result によってエラーがわかります。
これまでに示した各コードをコンパイルする方法を次に示します。
$ rpcgen msg.x$ cc rprintmsg.c msg_clnt.c -o rprintmsg -lnsl$ cc msg_proc.c msg_svc.c -o msg_server -lnsl
最初に rpcgen を実行してヘッダファイル (msg.h)、クライアント側スタブプログラム (msg_clnt.c)、サーバ側スタブプログラム (msg_svc.c) を生成します。次の 2 つのコンパイルコマンドで、クライアント側プログラム rprintmsg とサーバ側プログラム msg_server が作成されます。C のオブジェクトファイルは、ライブラリ libnsl とリンクする必要があります。ライブラリ libnsl には、RPC と XDR で必要な関数をはじめとするネットワーク関数がすべて含まれています。
この例では、アプリケーションが libnsl に含まれる基本型だけを使用しているので、XDR ルーチンは生成されません。
次に、rpcgen が入力ファイル msg.x から何を生成するかを説明します。
msg.h というヘッダファイルを作成します。msg.h には、他のモジュールで使用できるように MESSAGEPROG、MESSAGEVERS、PRINTMESSAGE の #define 文が入っています。このヘッダファイルは、クライアント側とサーバ側の両方のモジュールでインクルードする必要があります。
クライアント側スタブルーチンを msg_clnt.c というファイルに出力します。このファイルには、クライアントプログラム rprintmsg から呼び出されるルーチン printmessage_1() が 1 つだけ入っています。rpcgen への入力ファイルが FOO.x という名前ならば、クライアント側スタブルーチンは FOO_clnt.c というファイルに出力されます。
msg_svc.c の printmessage_1() を呼び出すサーバプログラムを msg_svc.c というファイルに出力します。サーバプログラムのファイル名は、クライアントプログラムのファイル名と同様の方法で決まります。rpcgen への入力ファイルが FOO.x という名前ならば、サーバプログラムは FOO_svc.c というファイルに出力されます。
サーバプログラムが作成されると、遠隔マシン上にインストールして実行することができます。(遠隔マシンが同じ機種の場合は、サーバプログラムをバイナリのままコピーすることができますが、機種が異なる場合は、サーバプログラムのソースファイルを遠隔マシンにコピーして再コンパイルしなければなりません)。遠隔マシンを remote、ローカルマシンを local とすると、遠隔システムのシェルから次のコマンドでサーバプログラムを起動することができます。
remote$ msg_server
rpcgen が生成したサーバプロセスは、常にバックグラウンドで実行されます。このとき、サーバプログラムにアンパサンド(&) を付けて起動する必要はありません。また、rpcgen が生成したサーバプロセスはコマンドラインからではなく、listen() や inetd() などのポートモニタから起動することもできます。
以降は、local マシン上のユーザが次のようなコマンドを実行して、remote マシンのコンソールにメッセージを表示できます。
local$ rprintmsg remote "Hello, there."
rprintmsg を使用すると、サーバプログラム msg_serverが起動されているどのシステムにでも (local システムも含む)、コンソールにメッセージを表示できます。