ONC+ 開発ガイド

第 3 章 rpcgen プログラミングガイド

この章では、rpcgen ツールについて紹介します。コード例および使用可能なコンパイル時のフラグの使用方法を記載したチュートリアルです。この章で使用する用語の定義については、用語集を参照してください。

rpcgen の概要

rpcgen ツールは、RPC 言語で書かれたソースコードをコンパイルして、遠隔プログラムインタフェースモジュールを生成します。RPC 言語は構文も構造も C 言語に似ています。rpcgen は C 言語ソースモジュールを生成するので、次に C コンパイラでコンパイルします。

デフォルトでは、rpcgen は次のコードを生成します。

オプションを指定すれば、rpcgen で次のことを行うことができます。

rpcgen を使用すると、下位レベルのルーチンを作成する手間が省けるのでアプリケーション開発時間を大幅に短縮できます。rpcgen の出力コードとユーザー作成コードとは、簡単にリンクできます。(rpcgen を使用しないで RPC プログラムを作成する方法については、第 4 章「RPC プログラマインタフェース」を参照してください)。

SunOS 5.X の機能

この節では、以前のバージョンでは提供されなかった機能で、現在の rpcgen コード生成プログラムで追加されたものについて説明します。

テンプレートの生成

rpcgen では、クライアント側、サーバー側、および makefile の各テンプレートを生成することができます。オプションのリストについては、「クライアント側とサーバー側のテンプレート」を参照してください。

C 形式モード

rpcgen には、C 形式モードとデフォルトモードという 2 つのコンパイルモードがあります。C 形式モードでは、引数は構造体へのポインタではなく値で渡されます。また、C 形式モードでは複数の引数を渡すこともできます。デフォルトモードは旧バージョンと同じです。両方のモードのコード例については、「C 形式モード」を参照してください。

マルチスレッド対応コード

現バージョンでは、マルチスレッド環境で実行可能なマルチスレッド対応コードを生成することができるようになりました。デフォルトでは、rpcgen によって生成されたコードはマルチスレッド対応ではありません。詳細およびコード例については、「マルチスレッド対応のコード」を参照してください。

マルチスレッド自動モード

rpcgen では、マルチスレッド自動モードで実行するマルチスレッド対応サーバースタブを生成します。定義およびコーディング例については、「自動マルチスレッド対応モード」を参照ください。

ライブラリの選択

rpcgen では、TS-RPC ライブラリか TI-RPC ライブラリのどちらかを使用してコードを生成します。「TI-RPC または TS-RPC のライブラリ選択」を参照してください。

ANSI C 準拠のコード

rpcgen では、ANSI C に準拠したコードを生成します。また、ANSI C 準拠のコードは、Sun WorkshopTM Compilers C++ 環境で使用することができます。「ANSI C に準拠したコードの生成」を参照してください。

rpcgen チュートリアル

rpcgen を使用すると、分散型アプリケーションを簡単に作成できます。サーバー側手続きは、手続き呼び出し規約に準拠した言語で記述します。サーバー側手続きは、rpcgen によって生成されたサーバースタブとリンクして、実行可能なサーバープログラムを形成します。クライアント側手続きも同様に記述およびリンクします。

この節では、rpcgen を使用した基本的なプログラミング例を示します。また、rpcgen(1) のマニュアルページを参照してください。

ローカル手続きを遠隔手続きに変換

単一のコンピュータ環境で実行されるアプリケーションを、ネットワーク上で実行する分散型アプリケーションに変更する場合を考えます。次の例で、システムコンソールにメッセージを表示するプログラムを分散型アプリケーションに変換する方法を、ステップ別に説明します。変換前のプログラム例 3-1 を次に示します。


例 3-1 シングルコンピュータ用の printmsg.c

/* 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 という遠隔手続きに変更したものを示します。


例 3-2 RPC バージョンの printmsg.c

/*
 * 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 つの点で異なることに注意してください。

  1. 引数が文字へのポインタではなく、文字配列へのポインタになっています。-N オプションを使用しない遠隔手続きの場合は、引数自体が渡されるのではなく、常に引数へのポインタが渡されるからです。-N オプションを指定しなければ、遠隔手続きの呼び出しで引数が 1 つしか渡されません。複数の引数が必要な場合は、引数を struct 型にして渡す必要があります。

  2. 引数が 2 つあります。第 2 引数には、関数呼び出しのときのコンテキスト、すなわち、プログラム、バージョン、手続きの番号、raw および canonical の認証、SVCXPRT 構造体へのポインタが入っています。 SVCXPRT 構造体にはトランスポート情報が入っています。呼び出された手続きが要求されたサービスを実行するときに、これらの情報が必要になる場合があります。

  3. 戻り値は、整数そのものではなく整数へのポインタになっています。-N オプションを指定しない遠隔手続きの場合は、戻り値自体ではなく戻り値へのポインタが返されるためです。-M (マルチスレッド) オプション または -A (自動モード) オプションが使用されていない場合は、戻り値は static で宣言します。戻り値を遠隔手続きのローカル値にしてしまうと、遠隔手続きが戻り値を返した後、サーバー側スタブプログラムからその値を参照することができなくなります。 -M および -A が使用されている場合は、戻り値へのポインタは第 3 引数として手続きに渡されるため、戻り値は手続きで宣言されません。

  4. 手続き名に _1 が追加されています。一般に rpcgen が遠隔手続き呼び出しを生成するときは、次のように手続き名が決められます。プログラム定義で指定した手続き名 (この場合は PRINTMESSAGE) はすべて小文字に変換され、下線 (_) とバージョン番号 (この場合は 1) が追加されます。このように手続き名が決定されるので、同じ手続きの複数バージョンが使用可能になります。

例 3-3 には、この遠隔手続きを呼び出すクライアント側メインプログラムを示します。


例 3-3 printmsg.c を呼び出すクライアント側プログラム

/*
 * 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 では、次の点に注意してください。

  1. 最初に、RPC ライブラリルーチン clnt_create() を呼び出してクライアントハンドルを作成しています。クライアントハンドルは、遠隔手続きを呼び出すスタブルーチンに引き渡されます。(これ以外にもクライアントハンドルを作成する方法があります。詳細については、第 4 章「RPC プログラマインタフェース」を参照してください)。クライアントハンドルを使用する遠隔手続き呼び出しがすべて終了したら、clnt_destroy() を使用してそのクライアントハンドルを破棄し、システム資源を無駄に使用しないようにします。

  2. clnt_create() の最後の引数に visible を指定して、/etc/netconfigvisible と指定したすべてのトランスポートを使用できるようにします。詳細については、/etc/netconfig ファイルと『Transport Interfaces Programming Guide』を参照してください。

  3. 遠隔手続き printmessage_1() の呼び出しは、第 2 引数として挿入されたクライアントハンドルを除いて、msg_proc.c で宣言されたとおりに実行されています。戻り値も値ではなく、値へのポインタで返されています。

  4. 遠隔手続き呼び出しのエラーには、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 から何を生成するかを説明します。

  1. msg.h というヘッダーファイルを作成します。msg.h には、他のモジュールで使用できるように MESSAGEPROGMESSAGEVERSPRINTMESSAGE#define 文が入っています。このヘッダーファイルは、クライアント側とサーバー側の両方のモジュールでインクルードする必要があります。

  2. クライアント側スタブルーチンを msg_clnt.c というファイルに出力します。このファイルには、クライアントプログラム rprintmsg から呼び出されるルーチン printmessage_1() が 1 つだけ入っています。rpcgen への入力ファイルが FOO.x という名前ならば、クライアント側スタブルーチンは FOO_clnt.c というファイルに出力されます。

  3. msg_proc.cprintmessage_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 システムも含む)、コンソールにメッセージを表示できます。

複雑なデータ構造の引き渡し

「ローカル手続きを遠隔手続きに変換」では、クライアント側とサーバー側のRPC コードの生成について説明しました。rpcgen を使用して、XDR ルーチンを生成することもできます (XDR ルーチンは、ローカルデータ形式と XDR 形式との相互変換を行います)。

遠隔ディレクトリを一覧表示する RPC サービスの全体を 例 3-4 に示します。rpcgen を使用してスタブルーチンと XDR ルーチンの両方を生成します。


例 3-4 RPC 言語で書かれたプロトコル記述ファイル (dir.x)

/*
 * dir.x: 遠隔ディレクトリを一覧表示するサービスのプロトコル
 *
 * rpcgen の機能を説明するためのサンプルプログラム
 */
 
const MAXNAMELEN = 255;						/* ディレクトリエントリの最大長 */
typedef string nametype<MAXNAMELEN>;		/* ディレクトリエントリ */
typedef struct namenode *namelist;			/* リスト形式でリンク */
 
/* ディレクトリリスト内のノード */
struct namenode {
	nametype name;						/* ディレクトリエントリ名 */
	namelist next;						/* 次のエントリ */
};
 
/*
 * READDIR の戻り値
 *
 * どこにでも移植できるアプリケーションにするためには、
 * この例のように UNIX の errno を返さないで、
 * エラーコードリストを設定して使用する方がよい
 * 
 * このプログラムでは、次の共用体を使用して、遠隔呼び出しが
 * 正常終了したか異常終了したかを区別します。
 */
 
union readdir_res switch (int errno) {
	case 0:
		namelist list;		/* 正常終了: 戻り値はディレクトリリスト */
	default:
		void;					/* エラー発生: 戻り値なし */
};
 
/* ディレクトリを一覧表示するプログラムの定義 */
program DIRPROG {
	version DIRVERS {
		readdir_res
		READDIR(nametype) = 1;
	} = 1;
} = 0x20000076; 

上の例の readdir_res のように、RPC 言語のキーワード structunionenum を使用して型を再定義することができます。使用したキーワードは、後にその型の変数を宣言するときには指定しません。たとえば、共用体 foo を定義した場合、union foo ではなく foo で宣言します。

rpcgen でコンパイルすると、RPC の共用体は C 言語の構造体に変換されます。RPC の共用体は、キーワード union を使用して宣言しないでください。

dir.x に対して rpcgen を実行すると、次の 4 つのファイル、(1) ヘッダーファイル、(2) クライアント側のスタブルーチン、(3) サーバー側の骨組み、(4) XDR ルーチンの入った dir_xdr.c というファイルが生成されます。(4) のファイルに入っている XDR ルーチンは、宣言されたデータ型を、ホスト環境のデータ形式から XDR 形式に、またはその逆方向に変換します。

rpcgen では、.x ファイルで使用されている RPC 言語の各データ型に対して、データ型名の前に XDR ルーチンであることを示すヘッダー xdr_ が付いたルーチン (たとえば、xdr_int) が libnsl で提供されるものとみなします。.x ファイルにデータ型が定義されていると、rpcgen はそれに対するルーチンを生成します。msg.x のように、.x ソースファイルにデータ型が定義されていない場合は、_xdr.c ファイルは生成されません。

.x ソースファイルで、libnsl でサポートされていないデータ型を使用し、.x ファイルではそのデータ型を定義しないこともできます。その場合は、xdr_ ルーチンをユーザーが自分で作成することになります。こうして、ユーザー独自の xdr_ ルーチンを提供することができます。任意のデータ型を引き渡す方法についての詳細は、第 4 章「RPC プログラマインタフェース」を参照してください。例 3-5 に、サーバー側の READDIR 手続きを示します。


例 3-5 サーバー側の dir_proc.c の例

/*
 * dir_proc.c: 遠隔手続き readdir 
 */
#include <dirent.h>
#include "dir.h"                /* rpcgen が生成 */
 
extern int errno;
extern char *malloc();
extern char *strdup();
 
readdir_res *
readdir_1(dirname, req)
	nametype *dirname;
	struct svc_req *req;
 
{
	DIR *dirp;
	struct dirent *d;
	namelist nl;
	namelist *nlp;
	static readdir_res res; /* 必ず static で宣言 */
	
	/* ディレクトリのオープン */
	dirp = opendir(*dirname);
	if (dirp == (DIR *)NULL) {
		res.errno = errno;
		return (&res);
	}
	/* 直前の戻り値の領域解放 */
	xdr_free(xdr_readdir_res, &res);
/*
 * ディレクトリエントリをすべて取り出す。ここで割り当てたメモリーは、
 * 次に readdir_1 が呼び出されたときに xdr_free で解放
 */
	nlp = &res.readdir_res_u.list;
	while (d = readdir(dirp)) {
		nl = *nlp = (namenode *) 
							malloc(sizeof(namenode));
		if (nl == (namenode *) NULL) {
			res.errno = EAGAIN;
			closedir(dirp);
			return(&res);
		}
		nl->name = strdup(d->d_name);
		nlp = &nl->next;
	}
	*nlp = (namelist)NULL;
	/* 結果を返す */
	res.errno = 0;
	closedir(dirp);
	return (&res);
}

例 3-6 に、クライアント側のREADDIR 手続きを示します。


例 3-6 クライアント側のプログラム (rls.c)

/*
 * rls.c: クライアント側の遠隔ディレクトリリスト
 */
 
#include <stdio.h>
#include "dir.h"                       /* rpcgen が生成 */
 
extern int errno;
 
main(argc, argv)
	int argc;
	char *argv[];
{
	CLIENT *clnt;
	char *server;
	char *dir;
	readdir_res *result;
	namelist nl;
 
	if (argc != 3) {
		fprintf(stderr, "usage: %s host 
					directory¥n",argv[0]);
		exit(1);
	}
	server = argv[1];
	dir = argv[2];
/*
 * コマンドラインで指定したサーバーの MESSAGEPROG の呼び出しで使用する
 * クライアント「ハンドル」を作成
 */
	cl = clnt_create(server, DIRPROG,
								DIRVERS, "tcp");
	if (clnt == (CLIENT *)NULL) {
		clnt_pcreateerror(server);
		exit(1);
	}
	result = readdir_1(&dir, clnt);
	if (result == (readdir_res *)NULL) {
		clnt_perror(clnt, server);
		exit(1);
	}
/*
 * 遠隔手続き呼び出しは正常終了
 */
	if (result->errno != 0) {
   /*
    * 遠隔システム上のエラー。エラーメッセージを表示して終了
    */
		errno = result->errno;
		perror(dir);
		exit(1);
	}
/*
 * ディレクトリリストの取り出しに成功。ディレクトリリストを表示
 */
	for (nl = result->readdir_res_u.list;
								nl != NULL;
		nl = nl->next) {
		printf("%s¥n", nl->name);
	}
	xdr_free(xdr_readdir_res, result);
	clnt_destroy(cl);
	exit(0);
}

この前のサンプルプログラムと同様に、システム名を localremote とします。ファイルのコンパイルと実行は、次のコマンドで行います。

remote$ rpcgen dir.x
remote$ cc -c dir_xdr.c
remote$ cc rls.c dir_clnt.c dir_xdr.o -o rls -lnsl
remote$ cc dir_svc.c dir_proc.c dir_xdr.o -o dir_svc -lnsl
remote$ dir_svc

local システムに rls() をインストールすると、次のように remote システム上の/usr/share/lib の内容を表示できます。

local$ rls remote /usr/share/libascii
eqnchar
greek
kbd
marg8
tabclr
tabs
tabs4
local$ 

rpcgen が生成したクライアント側のコードは、RPC 呼び出しの戻り値のために割り当てたメモリーを解放しないので、必要がなくなったら xdr_free() を呼び出してメモリーを解放してください。xdr_free() の呼び出しは free() ルーチンの呼び出しに似ていますが、XDR ルーチンの戻り値のアドレスを引き渡す点が異なります。この例では、ディレクトリリストを表示した後で次のように xdr_free() を呼び出しています。

xdr_free(xdr_readdir_res,result); 


注 -

xdr_free() を使用して malloc() で割り当てたメモリーを解放します。xdr_free() を使用してメモリーを解放すると、メモリーリークを生じて失敗します。


前処理命令

rpcgen では、C 言語などの前処理命令をサポートしています。rpcgen の入力ファイルに入っている C 言語の前処理命令は、コンパイル前に処理されます。.x ソースファイルでは、標準 C のすべての前処理命令を使用できます。生成する出力ファイルのタイプによって、次の 5 つのシンボルが rpcgen によって定義されます。

rpcgen 入力ファイルのパーセント記号 (%) で始まる行はそのまま出力ファイルに書き出され、その行の内容には影響を及ぼしません。そのとき、意図した位置に出力されるとは限らないため注意が必要です。出力ファイルのどこに書き出されたか確認して、必要ならば編集し直してください。

表 3-1 rpcgen の前処理命令

シンボル 

使用目的 

RPC_HDR

ヘッダーファイルの出力 

RPC_XDR

XDR ルーチンの出力 

RPC_SVC

サーバー側スタブプログラムの出力 

RPC_CLNT

クライアント側スタブプログラムの出力 

RPC_TBL

インデックステーブルの出力 

例 3-7 に、簡単な rpcgen の例を示します。rpcgen の前処理機能の使用方法に注意してください。


例 3-7 時刻プロトコルを記述する rpcgen ソースプログラム

/*
 * time.x: 遠隔時刻のプロトコル
 */
program TIMEPROG {
	version TIMEVERS {
			unsigned int TIMEGET() = 1;
	} = 1;
} = 0x20000044;
 
#ifdef RPC_SVC
%int *
%timeget_1()
%{
%	static int thetime;
%
%	thetime = time(0);
%	return (&thetime);
%}
#endif

cpp 命令

rpcgen では、C 言語の前処理機能をサポートしています。rpcgen では、デフォルトで /usr/ccs/lib/cpp を C のプリプロセッサとして使用します。これを使用できないときは、/lib/cpp を使用します。これ以外の cpp を含むライブラリを使用するときは、rpcgen-Y フラグで指定します。

たとえば、/usr/local/bin/cpp を使用するには、次のように rpcgen を起動します

rpcgen -Y /usr/local/bin test.x

コンパイル時に指定するフラグ

この節では、コンパイル時に使用可能な rpcgen オプションについて説明します。次の表に、この節で説明するオプションを要約します。

表 3-2 rpcgen コンパイル時に指定するフラグ

オプション 

フラグ 

コメント 

テンプレート

-a, -Sc, -Ss, -Sm

表 3-3 を参照

C 形式

-N

新しい形式のモードを呼び出す 

ANSI C

-C

-N オプションとともに使用

マルチスレッド対応コード

-M

マルチスレッド環境で使用 

マルチスレッド自動モード

-A

このオプションを指定すると、-M も自動的に指定される

TS-RPC ライブラリ

-b

デフォルトは TI-RPC ライブラリ 

xdr_inline カウント

-i

デフォルトはパックされた 5 つの要素。他の数字も指定できる

クライアント側とサーバー側のテンプレート

rpcgen で次のフラグを指定して、クライアント側とサーバー側のテンプレートを生成することができます。

表 3-3 rpcgen テンプレート選択フラグ

フラグ 

機能 

-a

すべてのテンプレートを生成 

-Sc

クライアント側のテンプレートを生成 

-Ss

サーバー側のテンプレートを生成 

-Sm

makefile のテンプレートを生成

生成されたテンプレートファイルを参考にしてプログラムを書くか、テンプレートに抜けている部分を直接書き込んで使用します。rpcgen は、スタブプログラムのほかにこれらのテンプレートファイルを生成します。

ソースプログラム add.x から C 形式モードでサーバー側テンプレートを生成するときは、次のコマンドを実行します。

rpcgen -N -Ss -o add_server_template.c add.x

生成されたテンプレートファイルは add_server_template.c という名前になります。同じソースプログラム add.x から C 形式モードでクライアント側テンプレートを生成するときは、次のコマンド行を実行します。

rpcgen -N -Sc -o add_client_template.c add.x

生成されたテンプレートファイルは add_client_template.c という名前になります。同じソースプログラム add.x から makefile テンプレートを生成するときは、次のコマンド行を実行します。

rpcgen -N -Sm -o mkfile_template add.x

生成されたテンプレートファイルは mkfile_template という名前になります。このファイルを使用して、サーバー側とクライアント側のプログラムをコンパイルできます。次のように、-a フラグを指定した場合は、3 つのテンプレートファイルがすべて生成されます。

rpcgen -N -a add.x

クライアント側テンプレートは add_client.c、サーバー側テンプレートは add_server.cmakefile テンプレートは makefile.a という名前になります。このうち 1 つでも同名のファイルが存在していれば、rpcgen はエラーメッセージを表示して終了します。


注 -

テンプレートファイルを生成する際には、次に rpcgen が実行された時に上書きされないように新しい名前を付けてください。


C 形式モード

-N フラグを指定して rpcgen を起動すると、C 形式モード (Newstyle モードとも呼ばれる) で処理が行われます。このモードでは、引数は値で渡され、複数の引数も構造体にせずに渡すことができます。この機能を使用して、RPC コードを、C 言語やその他の高級言語に近い形式で書くことができます。既存のプログラムや makefile との互換性を保つため、従来モード (標準モード) がデフォルトになっています。次の例では、-N フラグにより利用できる機能を示します。従来モードと C 形式モードの両方のソースモジュールを、例 3-8例 3-9 に示します。


例 3-8 C 形式モードの add.x

/*
 * このプログラムには、2 つの数値を加える手続きが入っています。
 * ここでは、C 形式モードによる引数の引き渡し方法を示します。
 * 関数 add() が 2 つの引数を取ることに注意してください。
 */
program ADDPROG {					/* プログラム番号 */
	version ADDVER {					/* バージョン番号 */
		int add(int, int) = 1;		/* 手続き */
	} = 1;
} = 0x20000199;


例 3-9 デフォルトモードの add.x

/*
 * このプログラムには、2 つの数値を加える手続きが入っています。
 * ここでは、デフォルトモードによる引数の引き渡し方法を示します。
 * デフォルトモードの場合、rpcgen は引数を 1 つしか処理しないことに
 * 注意してください。
 */
struct add_arg {
	int first;
	int second;
};
program ADDPROG {					/* プログラム番号 */
	version ADDVER {					/* バージョン番号 */
		int add (add_arg) = 1;		/* 手続き */
	} = 1;
} = 0x20000199;

次に、生成されるクライアント側テンプレートを示します。


例 3-10 C 形式モードのクライアント側スタブプログラムの例 : add.x

/*
 * C 形式のクライアント側メインルーチン。
 * 遠隔 RPC サーバー上の関数 add() を呼び出します。
 */
#include <stdio.h>
#include "add.h"
 
main(argc, argv)
int argc;
char *argv[];
{
	CLIENT *clnt;
	int *result,x,y;
	
	if(argc != 4) {
		printf("usage: %s host num1 
					num2¥n" argv[0]);
		exit(1);
	}
	/* 
 * クライアントハンドルの作成 - サーバーに結合
 */
	clnt = clnt_create(argv[1], ADDPROG,
								ADDVER, "udp");
	if (clnt == NULL) {
		clnt_pcreateerror(argv[1]);
		exit(1);
	}
	x = atoi(argv[2]);
	y = atoi(argv[3]);
/* 遠隔手続きの呼び出し: add_1() には、ポインタではなく、
 * 複数の引数が渡されていることに注意してください。
 */
	result = add_1(x, y, clnt);
	if (result == (int *) NULL) {
		clnt_perror(clnt, "call failed:");
		exit(1);
	} else {
		printf("Success: %d + %d = %d¥n", 
					x, y, *result);
	}
	exit(0);
}

例 3-11 に、デフォルトモードと C 形式モードとのコードの相違点を示します。


例 3-11 デフォルトモードのクライアント側スタブプログラムの例

	arg.first = atoi(argv[2]);
	arg.second = atoi(argv[3]);
/*
 * 遠隔手続きの呼び出し -- クライアント側スタブプログラムには、
 * 引数へのポインタを渡さなければならないことに注意してください。
 */
	result = add_1(&arg, clnt);
 

例 3-12 に、C 形式モードのサーバー側手続きを示します。


例 3-12 C 形式モードのサーバー側プログラムの例

#include "add.h"
 
int *
add_1(arg1, arg2, rqstp)
	int arg1;
	int arg2;
	struct svc_req *rqstp;
{
	static int result;
 
	result = arg1 + arg2;
	return(&result);
}

例 3-13 に、デフォルトモードのサーバー側手続きを示します。


例 3-13 デフォルトモードのサーバー側スタブプログラムの例

#include "add.h"
int *
add_1(argp, rqstp)
	add_arg *argp;
	struct svc_req *rqstp;
{
	static int result;
 
	result = argp->first + argp->second;
	return(&result);
}
 

マルチスレッド対応のコード

デフォルトでは、rpcgen で生成されるコードはマルチスレッド対応になりません。グローバル変数は保護されず、戻り値も静的変数で返されます。マルチスレッド環境で実行できるマルチスレッド対応コードを生成するには、-M フラグを指定します。-M フラグは、-N-C のどちらか (または両方) のフラグと共に指定します。

この機能を使用したマルチスレッド対応プログラムの例を示します。例 3-14rpcgen のプロトコルファイル msg.x を示します。


例 3-14 マルチスレッド対応プログラム : msg.x

program MESSAGEPROG {
version PRINTMESSAGE {
        int PRINTMESSAGE(string) = 1;
        } = 1;
} = 0x4001;

文字列が遠隔手続きに渡され、遠隔手続きでは文字列を表示してから文字数をクライアントに返します。マルチスレッド対応のテンプレートを生成するには、次のコマンドを実行します。


% rpcgen -M msg.x

例 3-15 に、クライアント側のコードを示します。


例 3-15 マルチスレッド対応のクライアント側スタブプログラム

#include "msg.h"
 
void
messageprog_1(host)
	char *host;
{
	CLIENT *clnt;
	enum clnt_stat retval_1;
	int result_1;
	char * printmessage_1_arg;
 
	clnt = clnt_create(host, MESSAGEPROG,
									PRINTMESSAGE,
									"netpath");
	if (clnt == (CLIENT *) NULL) {
		clnt_pcreateerror(host);
		exit(1);
	}
	printmessage_1_arg = 
							(char *) malloc(256);
	strcpy(printmessage_1_arg, "Hello World");
 
	retval_1 = printmessage_1(&printmessage_1_arg,
											&result_1,clnt);
	if (retval_1 != RPC_SUCCESS) {
		clnt_perror(clnt, "call failed");
	}
	printf("result = %d¥n", result_1);
 
	clnt_destroy(clnt);
}
 
main(argc, argv)
	int argc;
	char *argv[];
{
	char *host;
 
	if (argc < 2) {
		printf("usage:  %s server_host¥n", argv[0]);
		exit(1);
	}
	host = argv[1];
	messageprog_1(host);
}

ここで、rpcgen が生成したコードには、引数も戻り値もポインタで渡さなければならないことに注意してください。これはプログラムを再入可能にするために必要です。スタブ関数の戻り値は、遠隔手続きの呼び出しが正常終了したかエラーが起こったかを示します。正常終了した場合は、RPC_SUCCESS が返されます。例 3-16 に示すマルチスレッド対応のクライアント側スタブプログラム (-M で生成) とマルチスレッド対応でないクライアント側スタブプログラムを比較してください。マルチスレッド未対応のクライアント側スタブプログラムは、静的変数を使用して戻り値を格納し、一度に 1 つしかスレッドを使用することができません。


例 3-16 クライアント側スタブプログラム (マルチスレッドに対応していない場合)

int *
printmessage_1(argp, clnt)
	char **argp;
	CLIENT *clnt;
{
	static int clnt_res;
	memset((char *)&clnt_res, 0, 
								sizeof (clnt_res));
	if (clnt_call(clnt, PRINTMESSAGE,
		(xdrproc_t) xdr_wrapstring, 
										(caddr_t) argp,
		(xdrproc_t) xdr_int, (caddr_t) 
										&clnt_res,
		TIMEOUT) != RPC_SUCCESS) {
		return (NULL);
	}
	return (&clnt_res);
}
 

例 3-17 に、サーバー側コードを示します。


注 -

マルチスレッド対応モードを使用するサーバープログラムをコンパイルする場合は、スレッドライブラリをリンクしなければなりません。そのためには、コンパイルコマンドに -lthread オプションを指定します。



例 3-17 マルチスレッド対応サーバー側スタブプログラム

#include "msg.h"
#include <syslog.h>
 
bool_t
printmessage_1_svc(argp, result, rqstp)
	char **argp;
	int *result;
	struct svc_req *rqstp;
{
	int retval;
 
	if (*argp == NULL) {
		syslog(LOG_INFO, "argp is NULL¥n");
		*result = 0;
	}
	else {
		syslog("argp is %s¥n", *argp);
		*result = strlen (*argp);
	}
	retval = 1;
	return (retval);
}
 
int
messageprog_1_freeresult(transp, xdr_result, result)
	SVCXPRT *transp;
	xdrproc_t xdr_result;
	caddr_t result;
{
	/*
	 * 必要に応じてメモリー解放のためのコードを挿入
	 */
	(void) xdr_free(xdr_result, result);
}

サーバー側のコードでは、静的変数を使用して戻り値を格納しないでください。呼び出し側のルーチンから戻り値へのポインタが渡されますので、戻り値はそこに返します。正常終了の場合は 1 を返し、エラーが起こった場合は 0 を返します。

rpcgen が生成するコードには、手続きの呼び出しで割り当てたメモリーを解放するルーチンの呼び出しも含まれています。メモリーの不正使用を避けるため、サービスルーチンで割り当てたメモリーはすべてそのルーチンで解放する必要があります。上の例では、messageprog_1_freeresult() でメモリーの解放を行います。

通常は、xdr_free() を使用して割り当てたメモリーを解放します。上の例では、メモリー割り当てを行なっていないので、メモリーの解放は実行されません。

-M フラグを -N-C のフラグと共に指定する例として、add.x例 3-18 に示します。


例 3-18 マルチスレッド対応のプログラム : add.x

program ADDPROG {
 version ADDVER {	
 int add(int, int) = 1;
	 } = 1;
}= 199;

このプログラムでは、2 つの数値を加えてその結果をクライアントに返します。次のコマンドで、このファイルに対して rpcgen を実行します。


% rpcgen -N -M -C add.x

このプログラムを呼び出す例を次に示します。


例 3-19 マルチスレッド対応クライアント側プログラム : add.x

/*
 * このクライアント側メインルーチンでは複数のスレッドを起動します。
 * 各スレッドから同時にサーバールーチンを呼び出します。
 */
 
#include "add.h"
 
CLIENT *clnt;
#define NUMCLIENTS 5
struct argrec {
	int arg1;
	int arg2;
};
 
/* 
 * 現在実行中のスレッド数をカウント
 */
int numrunning;
mutex_t numrun_lock;
cond_t condnum;
 
void
addprog(struct argrec *args)
{
	enum clnt_stat retval;
	int result;
	/* サーバールーチンの呼び出し */
	retval = add_1(args->arg1, args->arg2,
											&result, clnt);
	if (retval != RPC_SUCCESS) {
		clnt_perror(clnt, "call failed");
	} else
		printf("thread #%x call succeeded,
					result = %d¥n", thr_getself(),
					result);
/*
 * 実行中のスレッド数をデクリメント
 */
	mutex_lock(&numrun_lock);
	numrunning--;
	cond_signal(&condnum);
	mutex_unlock(&numrun_lock);
	thr_exit(NULL);
}
 
main(int argc, char *argv[])
{
	char *host;
	struct argrec args[NUMCLIENTS];
	int i;
	thread_t mt;
	int ret;
 
	if (argc < 2) {
		printf("usage:  %s server_host¥n",
					argv[0]);
		exit(1);
	}
	host = argv[1];
	clnt = clnt_create(host, ADDPROG, ADDVER,
									"netpath");
	if (clnt == (CLIENT *) NULL) {
		clnt_pcreateerror(host);
		exit(1);
	};
	mutex_init(&numrun_lock, USYNC_THREAD, NULL);
	cond_init(&condnum, USYNC_THREAD, NULL);
	numrunning = 0;
 
	/* 個々のスレッドの起動 */
	for (i = 0; i < NUMCLIENTS; i++) {
		args[i].arg1 = i;
		args[i].arg2 = i + 1;
		ret = thr_create(NULL, NULL, addprog,
									(char *) &args[i],
				 					THR_NEW_LWP, &mt);
		if (ret == 0)
			numrunning++;
	}
 
	mutex_lock(&numrun_lock);
	/* 全スレッドの終了を待つ */
	while (numrunning != 0)
		cond_wait(&condnum, &numrun_lock);
	mutex_unlock(&numrun_lock);
	clnt_destroy(clnt);
}
 

サーバー側の手続きは例 3-20 のようになります。


注 -

マルチスレッド対応モードを使用するサーバー側プログラムをコンパイルする場合は、スレッドライブラリにリンクしなければなりません。そのためには、コンパイルコマンドに -lthread オプションを指定します



例 3-20 マルチスレッド対応サーバー側プログラム : add.x

add_1_svc(int arg1, int arg2, 
						int *result, struct svc_req *rqstp)
{
	bool_t retval;
	/* 結果の計算 */
	*result = arg1 + arg2;
	retval = 1;
	return (retval);
}
 
/*
 * サーバー手続きで割り当てたメモリーを解放するルーチン
 */
int
addprog_1_freeresult(SVCXPRT *transp,
									xdrproc_t xdr_result,
									caddr_t result)
 
{
	(void) xdr_free(xdr_result, result);
}
 

自動マルチスレッド対応モード

自動マルチスレッド対応モードにより、クライアントの要求を同時に処理するために Solaris スレッドが自動的に使用されます。-A オプションを指定して、RPC コードを自動マルチスレッド対応モードで生成します。また、-A を指定すると自動的に -M が指定されるため、-M を明示的に指定する必要はありません。生成されたコードはマルチスレッド対応でなければならないため、-M が (明示的ではなくても) 必要です。

マルチスレッド対応 RPC の詳細については 「マルチスレッド RPC プログラミング」、および 「マルチスレッド自動モード」 を参照してください。

次に、rpcgen によって生成される自動モードのプログラムの例を示します。例 3-21 は、rpcgen のプロトコルファイルである time.x のコードです。文字列は遠隔手続きに引き渡されます。遠隔手続きは、文字列を表示してクライアントの文字列長を返します。マルチスレッド対応スタブを生成するには、次のコマンドを実行します。


例 3-21 自動マルチスレッド対応モード : time.x

		program TIMEPROG {
		 version TIMEVERS {
 		 unsigned int TIMEGET(void) = 1;
			 void TIMESET(unsigned) = 2;
		} = 1;
	} = 0x20000044;


% rpcgen -A time.x

注 -

-A オプションを使用すると、生成されたサーバー側のコードには、サーバーの自動マルチスレッド対応モードを使用するための命令が含まれます。

マルチスレッド対応モードを使用するサーバー側プログラムをコンパイルする場合は、スレッドライブラリにリンクしなければなりません。そのためには、コンパイルコマンドに -lthread オプションを指定します。


TI-RPC または TS-RPC のライブラリ選択

旧バージョンの rpcgen では、ソケット関数を使用してスタブプログラムを作成していました。SunOS 5.x では、トランスポート独立の RPC ルーチン (TI-RPC) か、特定のトランスポート固有のソケットルーチン (TS-RPC) のどちらを使用するか選択できます。この機能は、旧バージョンとの互換性を保つために提供されています。デフォルトでは TI-RPC ルーチンが使用されます。TS-RPC ルーチンを使用したソースコードを生成するには、rpcgen-b フラグを指定します。

ANSI C に準拠したコードの生成

rpcgen では、ANSI C に準拠したコードを出力するか、SunC WorkShop(TM) Compilers C++ に準拠したコードを選択するか指定できます。ANSI C に準拠したコードを生成するには、-C フラグを指定します。ほとんどの場合、「C 形式モード」指定フラグ -N も同時に指定します。

add.x のサーバー側テンプレート例は、次のコマンドで生成できます。

rpcgen -N -C -Ss -o add_server_template.c add.x 

ここで、C++ 3.0 で記述されたサーバー上では遠隔手続き名が接尾辞 _svc で終わっていなければならないことに特に注意してください。次の例では、add.x に対して、コンパイルフラグ -C を指定してクライアント側の add_1 とサーバー側の add_1_svc が生成されています。


例 3-22 ANSI C に準拠したサーバー側テンプレート

/*
 * このファイルはテンプレートです。これを基にしてユーザー独自の関数を
 * 作成してください。
 */
#include <c_varieties.h>
#include "add.h"
 
int *
add_1_svc(int arg1, int arg2,
					struct svc_req *rqstp)
{
	static int result;
	/*
	 * ここにサーバープログラムのコードを挿入
	 */
	return(&result);
}

この出力ファイルは、構文も構造も ANSI C に準拠しています。-C フラグを指定して生成したヘッダーファイルは、ANSI C でも SunC WorkShop Compilers C++ でも使用できます。

xdr_inline() カウント

rpcgen は、可能な限り xdr_inline() (xdr_admin(3NSL) のマニュアルページを参照) を使用して、より効率の良いコードを生成しようとします。構造体の中に xdr_inline() を使用できるような要素 (たとえば、integerlongbool) があれば、構造体のその部分は xdr_inline() を使用してパックされます。デフォルトでは、パックされる要素が 5 つ以上連続していれば、インラインコードが生成されます。-i フラグを使用してインラインコードを生成する個数を変更することができます。たとえば、次のコマンドでは、パックできる要素が 3 つ以上連続していれば、インラインコードが生成されます。

rpcgen -i 3 test.x

次のコマンドでは、インラインコードの生成が禁止されます。

rpcgen -i 0 test.x

ほとんどの場合、-i フラグを指定する必要はありません。このフラグの対象となるのは _xdr.c スタブプログラムだけです。

rpcgen プログラミングテクニック

この節では、RPC プログラミングと rpcgen の使用方法に関するさまざまなテクニックを示します。

ネットワークタイプ / トランスポート選択

rpcgen の省略可能な引数には、使用したいネットワークのタイプや特定のネットワーク識別子を指定するためのものがあります。ネットワーク選択についての詳細は、『Transport Interfaces Programming Guide』を参照してください。

-s フラグを指定すると、指定したタイプのトランスポートからの要求に応答するサーバーが作成されます。たとえば、次のコマンドを実行すると、NETPATH 環境変数で指定した非接続型トランスポートすべてに応答するサーバーが標準出力に書き出されます。

rpcgen -s datagram_n prot.x

NETPATH 環境変数が定義されていない場合は、/etc/netconfig で指定した非接続型トランスポートすべてに応答するサーバーが標準出力に書き出されます。コマンド行では、-s フラグとネットワークタイプのペアを複数指定できます。

同様に、-n フラグを指定すると、1 つのネットワーク識別子で指定したトランスポートからの要求だけに応答するサーバーを作成することができます。


注意 - 注意 -

rpcgen-n フラグを指定して作成したサーバーを使用するときは注意が必要です。ネットワーク識別子は各ホストに固有なため、作成されたサーバーは別のホストで予測通りに機能しないことがあります。


コマンド行の定義文

コマンド行で、C 言語のプリプロセッサシンボルを定義し、値を割り当てることができます。コマンド行の定義文は、たとえば、DEBUG シンボルが定義されているときの条件付きデバッグコードの生成に使用できます。

$ rpcgen -DDEBUG proto.x

ブロードキャスト呼び出しへのサーバーからの応答

手続きがブロードキャスト RPC を通して呼び出され、有効な応答を返せないときは、サーバーはクライアントに応答しないでください。その方がネットワークが混雑しません。サーバーが応答を返さないようにするには、遠隔手続きの戻り値を NULL にします。rpcgen が生成したサーバープログラムは、NULL を受け取った場合は応答しません。

例 3-23 に、NFS サーバーの場合だけ応答する手続きを示します。


例 3-23 ブロードキャスト呼び出しに対する NFS サーバーの応答

void *
reply_if_nfsserver()
{
	char notnull; /*
						 *この場所のみで、そのアドレスが使用可能
						 */
 
	if( access( "/etc/dfs/sharetab",
						F_OK ) < 0 ) {
		/* RPC の応答を禁止 */
		return( (void *) NULL );
	}
	/*
 * NULL 以外の値 notnull を指定したので、RPC は応答する
 */
	return( (void *) &notnull );
}

RPC ライブラリルーチンが応答するには、手続きが NULL 以外のポインタ値を返す必要があります

例 3-23 で手続き reply_if_nfsserver()NULL 以外の値を返すように定義されているならば、戻り値 (&notnull) は静的変数を指していなければなりません。

ポートモニタのサポート

inetdlisten のようなポートモニタは、特定の RPC サービスに対するネットワークアドレスを監視することができます。特定のサービスに対する要求が到着すると、ポートモニタは、サーバープロセスを生成します。サービスを提供したら、サーバーは終了できます。この方法によりシステム資源を節約することができます。rpcgen で生成するサーバー関数 main()inetd で呼び出すことができます。詳細は、「inetd の使用」を参照してください。

サーバープロセスがサービス要求に答えた後、続けて要求が来る場合に備えて一定時間待つことには意味があります。一定時間内に次の呼び出しが起こらなければ、サーバーは終了し、inetd のようなポートモニタがサーバーのための監視を続けます。サーバーが終了しないうちに次の要求が来れば、ポートモニタは新たなプロセスを生成することなく待ち状態のサーバーにその要求を送ります。


注 -

listen() などのポートモニタの場合は、サーバーのための監視を行い、サービス要求が来れば必ず新たなプロセスを生成します。このようなモニタからサーバープロセスを起動する場合は、サーバープロセスはサービス提供後すぐに終了するようにしなければなりません。


rpcgen がデフォルトで生成したサービスは、サービス提供後 120 秒間待ってから終了します。待ち時間を変更するには、-K フラグを使用します。たとえば、次のコマンドでは、サーバーは 20 秒待ってから終了します。

$ rpcgen -K 20 proto.x

サービス提供後すぐに終了させるには、次のように待ち時間に対して 0 を指定します。

$ rpcgen -K 0 proto.x.

ずっと待ち状態を続けて終了しないようにするには、-K -1 と指定します。

タイムアウト値の変更

クライアントプログラムはサーバーに要求を送った後、デフォルトで 25 秒間応答を待ちます。応答待ちのタイムアウト値は、clnt_control() ルーチンを使用して変更できます。clnt_control() ルーチンの使用方法についての詳細は、「標準インタフェース」および rpc(3NSL) のマニュアルページを参照してください。タイムアウト値を変更する場合に、ネットワークを往復するのに必要な最短時間より長くなるようにする必要があります。例 3-24clnt_control () の使用方法を示します。


例 3-24 clnt_control ルーチン

struct timeval tv;
CLIENT *clnt;
clnt = clnt_create( "somehost", SOMEPROG,
									SOMEVERS, "visible" );
 
if (clnt == (CLIENT *)NULL)
	exit(1);
tv.tv_sec = 60;	/* 
							 * タイムアウト値を 60 秒に変更
							 */
tv.tv_usec = 0;
clnt_control(clnt, CLSET_TIMEOUT, &tv);

クライアントの認証

クライアント作成ルーチンにはクライアント認証機能はありません。クライアントによっては、サーバーに対して自分自身を証明する必要があります。

次の例では、セキュリティレベルが最も低いクライアント認証方法のうち、一般に使用される方法を示します。よりセキュリティレベルが高い認証方法の詳細については、「認証」を参照してください。


例 3-25 AUTH_SYS クライアントの認証

CLIENT *clnt;
clnt = clnt_create( "somehost", SOMEPROG,
								SOMEVERS, "visible" );
if (clnt != (CLIENT *)NULL) {
/* AUTH_SYS 形式の認証情報を設定 */
 clnt->cl_auth = authsys_createdefault();
}

一定のセキュリティレベルを保持しなければならないサーバーではクライアント認証情報が必要になります。クライアント認証情報は、第 2 引数でサーバーに渡されます。

例 3-26 に、クライアント認証情報をチェックするサーバープログラムを示します。これは、rpcgen チュートリアル」で説明した printmessage_1() を修正したもので、スーパーユーザーにだけコンソールへのメッセージの表示を許可します。


例 3-26 スーパーユーザーだけが使用できる printmsg_1

int *
printmessage_1(msg, req)
char **msg;
struct svc_req  *req;
{
static int result;	/* 必ず static で宣言 */
FILE *f;
struct authsys_parms *aup;
 
aup = (struct authsys_parms *)req->rq_clntcred;
if (aup->aup_uid != 0) {
 result = 0;
	return (&result)
}
 
/* 元のコードと同じ */
}

ディスパッチテーブル

RPC パッケージで使用するディスパッチテーブルにプログラムからアクセスしたい場合があります。たとえば、サーバーディスパッチルーチンで権限を調べてからサービスルーチンを呼び出したり、クライアントライブラリで記憶管理や XDR データ変換の詳細を扱う場合です。

-T オプションを指定して rpcgen を起動すると、プロトコル記述ファイル proto.x で定義した各プログラムごとの RPC ディスパッチテーブルがファイル proto_tbl.i に出力されます。接尾辞.i は index を表します。-t オプションを指定して rpcgen を起動した場合は、ヘッダーファイルだけが生成されます。rpcgen を起動するときは、C 形式モード (-N オプション) と同時に -T または -t フラグを指定することはできません。

ディスパッチテーブルの各エントリは struct rpcgen_table で、この構造体はヘッダーファイル proto.h で次のように定義されています。

struct rpcgen_table {
  char *(*proc)();
  xdrproc_t xdr_arg;
  unsigned len_arg;
  xdrproc_t xdr_res;
  xdrproc_t len_res
};

ここでの定義は、次のとおりです。

proc      サービスルーチンへのポインタ
 xdr_arg   入力 (引数) の xdr ルーチンへのポインタ
 len_arg   入力引数の長さ (バイト数)
xdr_res   出力 (結果) の xdr ルーチンへのポインタ
len_res    出力結果の長さ (バイト数)

サンプルプログラム dir.x のディスパッチテーブル dirprog_1_table は、手続き番号がインデックスになっています。変数 dirprog_1_nproc には、テーブル内のエントリ数が入っています。

ディスパッチテーブルから手続きを探し出すルーチン find_proc() を次に示します。


例 3-27 ディスパッチテーブルの使用方法

struct rpcgen_table *
find_proc(proc)
  rpcproc_t proc;
{
  if (proc >= dirprog_1_nproc)
       /* error */
  else
  return (&dirprog_1_table[proc]);
} 

ディスパッチテーブル内の各エントリは対応するサービスルーチンへのポインタです。ところが、サービスルーチンは一般にクライアント側のコードでは定義されていません。未解決の外部参照を起こさないため、また、ディスパッチテーブルに対するソースファイルを 1 つだけ要求にするために RPCGEN_ACTION(proc_ver) で、rpcgen サービスルーチン の初期化を行います。

これを使用して、クライアント側とサーバー側に同一のディスパッチテーブルを持たせることができます。クライアント側プログラムをコンパイルするときは、次の define 文を使用します。

#define RPCGEN_ACTION(routine) 0

サーバー側プログラムを作成するときは、次の define 文を使用します。

#define RPCGEN_ACTION(routine)routine

rpcgen の 64 ビットの場合の考慮事項

例 3-27 では、proc は タイプ rpcproc_t として宣言されていることに注意してください。正式には、RPC のプログラム、バージョン、手続き、およびポートは、タイプ u_long として宣言されていました。32 ビットマシン上では、u_long の量は 4 バイト (int として) で、64 ビットシステム上では u_long の量は 8 バイトになります。RPC のプログラム、バージョン、手続き、およびポートを宣言できる場合には必ず、u_longlong の代わりに Solaris 7 で導入されたデータタイプ rpcprog_trpcvers_trpc_proc_trpcport_t を使用する必要があります。これらの新しいタイプを使用すると、32 ビットシステムとの下位互換性があるからです。つまり、この新しいデータタイプによって、rpcgen を実行するシステムに関係なく、4 バイトの量が保証されます。プログラム、バージョン、および手続きの u_long バージョンを使用する rpcgen プログラムを引き続き実行すると、32 ビットマシンと 64 ビットマシンでは、異なる結果になる場合があります。そのため、これらを該当する新しいデータタイプに置き換えることをお勧めします。実際、可能な限り longu_long の使用は避けた方が賢明です (この後の注を参照)。

Solaris 7 以降の rpcgen を起動する場合、rpcgen によって作成されたソースファイルには XDR ルーチンが組み込まれていて、このソースファイルは、そのコードを 32 ビットマシンと 64 ビットマシン上のどちらで実行するかによって、異なるインラインマクロが使用されます。特に、IXDR_GETLONG()IXDR_PUTLONG() の代わりに、IXDR_GET_INT32()IXDR_PUT_INT32() マクロが使用されます。たとえば、rpcgen ソースファイル foo.x に以下のコードが組み込まれている場合を考えます。

struct foo {
        char      c;
        int       i1;
        int       i2;
        int       i3;
        long      l;
        short     s;
};
この場合生成されるファイル foo_xdr.c ファイルでは、次のように適切なインラインマクロが使用されているかどうか確認されます。
#if defined(_LP64) || defined(_KERNEL)
        register int *buf;
#else
        register long *buf;
#endif
 
. . .
 
#if defined(_LP64) || defined(_KERNEL)
                        IXDR_PUT_INT32(buf, objp->i1);
                        IXDR_PUT_INT32(buf, objp->i2);
                        IXDR_PUT_INT32(buf, objp->i3);
                        IXDR_PUT_INT32(buf, objp->l);
                        IXDR_PUT_SHORT(buf, objp->s);
#else
                        IXDR_PUT_LONG(buf, objp->i1);
                        IXDR_PUT_LONG(buf, objp->i2);
                        IXDR_PUT_LONG(buf, objp->i3);
                        IXDR_PUT_LONG(buf, objp->l);
                        IXDR_PUT_SHORT(buf, objp->s);
#endif
このコードにより、 bufint または long のどちらかになるように宣言されますが、これはマシンが 64 ビットであるか、または 32 ビットであるかによって決まるということに注意してください。


注 -

現在は、RPC を通じて転送されるデータタイプのサイズは、4 バイトの量 (32 ビット) に制限されています。8 バイトの long は、アプリケーションが 64 ビットのアーキテクチャを最大限に使用できるようにする場合に提供されます。ただし、プログラマは、int のために long や、x_putlong() などの long を使用する関数の使用は、可能な限り避ける必要があります。上述したように、RPC プログラム、バージョン、手続きおよびポートにはそれぞれ専用のタイプがあります。 それは、データ値が INT32_MININT32_MAX の間にない場合、xdr_long() は失敗し、また、IXDR_GET_LONG()IXDR_PUT_LONG() などのインラインマクロが使用されると、そのデータは切り捨てられるからです (u_long の場合も同様) 。xdr_long(3NSL) のマニュアルページも参照してください。


rpcgen の IPv6 の場合の考慮事項

IPv6 トランスポートをサポートするのは、TI-RPC だけです。現在または将来的に、IPv6 を使用してアプリケーションを実行する予定がある場合は、下位互換スイッチは使用する必要はありません。IPv4 と IPv6 のどちらを選択するかは、 /etc/netconfig 内の関連エントリのそれぞれの順序によって決まります。

アプリケーションのデバッグ

作成したアプリケーションのテストとデバッグは、簡単に実行できます。最初は、クライアント側とサーバー側の手続きをリンクして全体をシングルプロセスとしてテストします。最初は、各手続きをそれぞれクライアント側とサーバー側のスケルトンとはリンクしません。クライアントを作成するRPC ライブラリルーチン (rpc_clnt_create(3NSL) のマニュアルページを参照) と認証ルーチンの呼び出し部分はコメントにします。この段階では、libnsl をリンクしないでください。

これまでに説明したサンプルプログラムの手続きを、次のコマンドでリンクします。

cc rls.c dir_clnt.c dir_proc.c -o rls

RPC と XDR の関数をコメントにすると、手続き呼び出しは通常のローカル関数呼び出しとなり、プログラムは dbxtool のようなローカルデバッガでデバッグ可能になります。プログラムが正しく機能することが確認されたら、クライアント側プログラムを rpcgen が生成したクライアント側のスケルトンとリンクし、サーバー側プログラムを rpcgen が生成したクライアント側のスケルトンとリンクします。

また、Raw PRC モードを使用して XDR ルーチンをテストすることもできます。その方法についての詳細は、「下位レベルの Raw RPC を使用したプログラムテスト」を参照してください。

RPC 呼び出しで発生するエラーには 2 種類あります。1 つは、遠隔手続き呼び出し過程で起こるエラーです。これには、(1) 手続きが実行できない、(2)遠隔サーバーが応答しない、(3) 遠隔サーバーが引数を復号化できない、などがあります。例 3-26で考えると、resultNULL の場合は RPC エラーです。エラーの原因を調べるには、clnt_perror() を使用してエラーの原因を表示するか、clnt_sperror() を使用してエラー文字列を取り出します。

もう 1 つのエラーは、サーバー自体のエラーです。例 3-26で考えると、opendir() からエラーが返された場合です。このようなエラーの処理はアプリケーションによって異なるため、プログラマの責任で対応します。

-C オプションを指定した場合はサーバー側ルーチンに _svc という接尾辞が付くため、上の説明がそのまま当てはまらないことに注意してください。