単一のコンピュータ環境で実行されるアプリケーションを、ネットワーク上で実行する分散型アプリケーションに変更する場合を考えます。次の例で、システムコンソールにメッセージを表示するプログラムを分散型アプリケーションに変換する方法を、ステップ別に説明します。変換前のプログラム例を次に示します。
/* 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() 関数をリモートプロシージャに変換すると、ネットワーク上のどこからでも実行できるようになります。
最初に、手続きを呼び出すときのすべての引数と戻り値のデータ型を決定します。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" /* 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 つの点で異なることに注意してください。
printmessage_1() では、引数が文字へのポインタではなく、文字列配列へのポインタになっています。これは、-N オプションを使用しないリモートプロシージャの場合には常に当てはまります。その場合、常に引数自体ではなく、引数へのポインタが使われます。-N オプションを指定しなければ、リモートプロシージャの呼び出しで引数が 1 つしか渡されません。複数の引数が必要な場合は、引数を struct
型にして渡す必要があります。
printmessage_1() には、引数が 2 つあります。第 2 引数には、関数呼び出しのときのコンテキストが入っています。つまりプログラム番号、バージョン、そして手続き番号、 raw および canonical の認証、 SVCXPRT 構造体へのポインタが入っています。 SVCXPRT 構造体には、トランスポート情報が入っています。呼び出された手続きが要求されたサービスを実行するときに、これらの情報が必要になります。
printmessage_1() では、戻り値は整数自体ではなく、整数へのポインタになります。これもまた、-N オプションを使用しないリモートプロシージャの場合には常に当てはまります。そこでは、戻り値自体ではなく、戻り値へのポインタが返されるためです。-M (マルチスレッド) オプション または -A (自動モード) オプションが使用されていない場合は、戻り値は static
で宣言します。戻り値をリモートプロシージャのローカル値にしてしまうと、リモートプロシージャが戻り値を返した後、サーバー側スタブプログラムからその値を参照することができなくなります。 -M および -A が使用されている場合は、戻り値へのポインタは第 3 引数として手続きに渡されるため、戻り値は手続きで宣言されません。
手続き名を見ると、_1 が追加されてprintmessage_1 () になっています。一般に rpcgen がリモートプロシージャ呼び出しを生成するときは、次のように手続き名が決められます。プログラム定義で指定した手続き名 (この場合は PRINTMESSAGE) はすべて小文字に変換され、下線 (_) とバージョン番号 (この場合は 1) が追加されます。このように手続き名が決定されるので、同じ手続きの複数バージョンが使用可能となります。
リモートプロシージャを呼び出すクライアント側メインプログラムを次に示します。
/* * rprintmsg.c: "printmsg.c" の RPC 対応バージョン */ #include <stdio.h> #include "msg.h" /* rpcgen が生成した msg.h */ 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);}
このコード例では、最初に RPC ライブラリルーチン clnt_create() を呼び出して、クライアントハンドルを作成しています。 クライアントハンドルは、リモートプロシージャを呼び出すスタブルーチンに引き渡されます。これ以外にも、クライアントハンドルを作成する方法があります。詳細については、第 4 章「RPC プログラマインタフェース」 を参照してください。クライアントハンドルを使用するリモートプロシージャ呼び出しがすべて終了したら、clnt_destroy () を使用してそのクライアントハンドルを破棄し、システム資源を無駄に使用しないようにします。
clnt_create() の最後の引数に visible を指定して、/etc/netconfig で visible と指定したすべてのトランスポートを使用できるようにします。トランスポートについての詳細については、 /etc/netconfig ファイルと 『プログラミングインタフェース』 を参照してください。
リモートプロシージャ printmessage_1() の呼び出しは、第 2 引数として挿入されたクライアントハンドルを除いて、msg_proc.c で宣言されたとおりに実行されています。戻り値も値ではなく、値へのポインタで返されています。
リモートプロシージャ呼び出しのエラーは、2 種類あります。RPC 自体のエラーと、リモートプロシージャの実行中に発生したエラーです。最初のエラーの場合は、リモートプロシージャ printmessage_1() の戻り値が NULL になります。2 つめのエラーの場合は、アプリケーションによってエラーの返し方が異なります。この例では、*result によってエラーがわかります。
printmsg 全体をコンパイルする方法を次に示します。
$ 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 とリンクする必要があります。それには、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_proc.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 システムも含む)、コンソールにメッセージを表示できます。