ONC+ 開発ガイド

パート II 遠隔手続き呼び出し (RPC)

Part 2 では RPC について説明します。

第 2 章 TI-RPC 入門

この章では Sun RPC としても知られている TI-RPC について概要を説明します。RPC に初めて接するユーザーに役立つ情報を記載しています (用語の定義は、用語集を参照してください)。

TI-RPC の概要

TI-RPC はクライアントサーバーをベースにした分散型アプリケーションを構築するための強力な技術です。従来のローカルの手続き呼び出しの概念を拡張し、呼び出された手続きが呼び出す手続きと同じアドレス空間に存在する必要がないようにしています。2 つのプロセスが同じシステム上に存在することもあり、また、ネットワーク上で接続された異なるシステム上に存在する場合もあります。

RPC を使用すると、分散型アプリケーションを作成するプログラマはネットワークとの詳細なインタフェースを意識する必要がありません。 RPC はトランスポート層に依存しないため、データ通信の物理的および論理的な機構からアプリケーションを切り離して作成することができ、したがって、アプリケーションはさまざまなトランスポートを使用できます。

図 2-1 遠隔手続き呼び出しの動作

Graphic

RPC は関数呼び出しに類似したものです。RPC を実行すると、呼び出し時の引数が遠隔手続きに渡され、呼び出し側は遠隔手続きからの応答を待ちます。

図 2-1 に、2 つのネットワーク上のシステム間でのRPC 呼び出し時に実行される動作のフローを示します。クライアントは、サーバーに要求を送信して応答を待つ手続き呼び出しを行います。応答が受信されるかまたはタイムアウトになるまで、スレッドの実行は停止されます。要求が届くと、サーバーは要求されたサービスを実行するディスパッチルーチンを呼び出し、その結果をクライアントに返します。RPC 呼び出しが終了すると、クライアントはプログラムの続きを実行します。

RPC はネットワークアプリケーションをサポートします。TI-RPC は TCP/IP のようなネットワーク機構上で実行されます。その他の標準の RPC としては、OSF DCE (Apollo の NCS システムをベースにしています)、Xerox Courier、Netwise があります。

TI-RPC の問題

特定の RPC を実装する場合には次の点に注意が必要です。

パラメータの引き渡し

TI-RPC では 1 つのパラメータをクライアントからサーバーに渡すことができます。複数のパラメータが必要なときは、1 つの要素とみなされる 1 つの構造体に含めて渡されます。サーバーからクライアントに渡される情報は、関数の戻り値として渡されます。サーバーからクライアントにパラメータリストを通して情報を戻すことはできません。

結合

クライアントは、サービスの使用方法を知っていなければなりません。サーバーのホスト名を知ることと、実際のサーバーのプロセスに接続することが必要です。各ホストでは、rpcbind と呼ばれるサービスが RPC サービスを管理します。TI-RPC は hosts ファイルと ipnodes ファイル、NIS+、DNS などのホストネームサービスを使用してホストの位置を確認します。

トランスポートプロトコル

トランスポートプロトコルは、クライアントとサーバーとの間で呼び出しおよび返答メッセージがどのように送信されるかを指定します。TS-RPC はトランスポートプロトコルとして TCP と UDP を使用しますが、現在の TI-RPC バージョンはトランスポートに依存しません。つまり、TI-RPC は任意のトランスポートプロトコルで動作します。

呼び出しセマンティクス

呼び出しセマンティクスは、遠隔手続きの実行に関し、特にその手続きが何回実行されたかについてクライアントが仮定することに関係があります。これはエラー条件を扱う場合に重要です。この場合、「1 回」、「多くても 1 回」、「少なくとも 1 回」の 3 つのセマンティクスがあります。ONC+ では「少なくとも 1 回」のセマンティクスを提供します。遠隔で呼び出される手順は一貫しています。つまり、たとえ数回にわたって呼び出されても同じ結果を返す必要があります。

データ表現

データ表現とは、プロセス間でパラメータと結果が渡されるときに使用されるフォーマットのことです。さまざまなシステムアーキテクチャ上で RPC が機能するためには、標準データ形式が必要です。TI-RPC では、標準データ形式として外部データ表現 (XDR: external Data Representation) を使用します。XDR はマシンに依存しないデータ形式と符号化のためのプロトコルです。TI-RPC では、XDR を使用することによって、各ホストのバイト順序や構造体の配置方法に影響されることなく、任意のデータ構造を扱うことができます。XDR の詳細については、付録 C 「XDR プロトコル仕様」 および 付録 A 「XDR テクニカルノート」 を参照してください。

プログラム、バージョン、手続き番号

遠隔手続きは次の 3 つの要素によって一意に識別されます。

プログラム番号とは、関連する遠隔手続きがグループ化された 1 つのプログラムを示します。プログラム内の各手続きは固有の手続き番号を持っています。

プログラムは 1 つまたは複数のバージョンを持つ場合があります。各バージョンは遠隔で呼び出せる手続きの集まりです。バージョン番号を利用することにより、1 つの RPC プロトコルの複数のバージョンを同時に使用できます。

各バージョンには遠隔で呼び出せる多くの手続きが含まれます。各手続きは、手続き番号を持っています。

「プログラムと手続き番号」では、値の範囲と意味を示し、プログラム番号を RPC プログラムに割り当てる方法を説明しています。RPC サービス名とプログラム番号との対応リストは、rpc ネットワークデータベースの /etc/rpc にあります。

インタフェースルーチンの概要

RPC が提供するサービスには、さまざまなレベルのアプリケーションインタフェースがあります。レベルごとに制御の度合いが異なるため、インタフェースのコーディング量との兼ね合いで適当なレベルを使用してください。この節では、制御の度合いとプログラムの複雑さの順に、各レベルで利用できるルーチンについて要約します。

単純インタフェースのルーチン

単純インタフェースは、使用するトランスポートタイプだけを指定して、他のマシン上のルーチンを遠隔手続き呼び出しにより実行します。ほとんどのアプリケーションで、このレベルのルーチンを使用します。この説明とコード例は、「単純インタフェース」を参照してください。

表 2-1 RPC ルーチン - 単純レベル

ルーチン 

説明 

rpc_reg()

手続きを RPC プログラムとして、指定したタイプのトランスポートすべてに登録する 

rpc_call()

指定した遠隔ホスト上の指定した手続きを遠隔呼び出しする 

rpc_broadcast()

指定したタイプのトランスポートすべてに呼び出しメッセージをブロードキャストする 

標準インタフェースのルーチン

標準インタフェースはトップレベル、中間レベル、エキスパートレベル、ボトムレベルの 4 つのレベルに分けられます。開発者はこれらのインタフェースを使用して、トランスポートの選択、エラーへの応答または要求の再送まで待つ時間の指定などのパラメータをかなり詳細に制御できます。

トップレベルのルーチン

トップレベルのインタフェースも簡単に使用できますが、RPC 呼び出しを行う前にクライアントハンドルを作成し、RPC 呼び出しを受ける前にサーバーハンドルを作成しなければなりません。アプリケーションをすべてのトランスポート上で実行したい場合は、このインタフェースを使用してください。このルーチンの使用方法とコード例は、「トップレベルのインタフェース」を参照してください。

表 2-2 RPC ルーチン - トップレベル

ルーチン 

説明 

clnt_create()

汎用のクライアント作成ルーチン。このルーチンは、サーバーの位置と、使用するトランスポートのタイプを指定して呼び出す 

clnt_create_timed()

clnt_create() に似ているが、クライアントの作成を試みる間、各トランスポートタイプに許される最長時間を指定できる

svc_create()

指定したタイプのトランスポートすべてに対しサーバーハンドルを作成する。このルーチンは、使用するディスパッチ関数を svc_create() に指定して呼び出す

clnt_call()

要求をサーバーに送信するための手続きをクライアント側から呼び出す 

中間レベルのインタフェース

RPC の中間レベルのインタフェースを使用すると、通信を詳細に制御できます。このような下位レベルのインタフェースを使用すると、プログラムは複雑になりますが、効率はよくなります。中間レベルでは特定のトランスポートを指定できます。このルーチンの使用方法とコード例は、「中間レベルのインタフェース」を参照してください。

表 2-3 RPC ルーチン - 中間レベル

ルーチン 

説明 

clnt_tp_create()

指定したトランスポートに対するクライアントハンドルを作成する 

clnt_tp_create_timed()

clnt_tp_create() に似ているが、許される最長時間を指定できる

svc_tp_create()

指定したトランスポートに対するサーバーハンドルを作成する 

clnt_call()

要求をサーバーに送信するための手続きをクライアント側から呼び出す 

エキスパートレベルのインタフェース

エキスパートレベルには、トランスポートに関連するパラメータを指定するさまざまなルーチンがあります。このルーチンの使用方法とコード例は、「エキスパートレベルのインタフェース」を参照してください。

表 2-4 RPC ルーチン - エキスパートレベル

ルーチン 

説明 

clnt_tli_create()

指定したトランスポートに対するクライアントハンドルを作成する 

svc_tli_create()

指定したトランスポートに対するサーバーハンドルを作成する 

rpcb_set()

rpcbind デーモンを呼び出して、RPC サービスとネットワークアドレスとのマップを作成する

rpcb_unset()

rpcb_set() で作成したマップを削除する

rpcb_getaddr()

rpcbind デーモンを呼び出して、指定した RPC サービスのトランスポートアドレスを取り出す

svc_reg()

指定したプログラム番号とバージョン番号のペアを、指定したディスパッチルーチンに関連付ける 

svc_unreg()

svc_reg() で設定した関連付けを解除する

clnt_call()

要求をサーバーに送信するための手続きをクライアント側から呼び出す 

ボトムレベルのインタフェース

ボトムレベルには、トランスポートを完全に制御することができるルーチンがあります。これらのルーチンについては、「ボトムレベルのインタフェース」を参照してください。

表 2-5 RPC ルーチン - ボトムレベル

ルーチン 

説明 

clnt_dg_create()

非接続型トランスポートを使用して、指定した遠隔プログラムに対する RPC クライアントハンドルを作成する 

svc_dg_create()

非接続型トランスポートを使用して、RPC サーバーハンドルを作成する 

clnt_vc_create()

接続型トランスポートを使用して、指定した遠隔プログラムに対する RPC クライアントハンドルを作成する 

svc_vc_create()

接続型トランスポートを使用して、RPC サーバーハンドルを作成する 

clnt_call()

要求をサーバーに送信するための手続きをクライアント側から呼び出す

ネットワーク選択

特定のトランスポートまたはトランスポートタイプで実行されるプログラムを書くことができます。あるいは、システムが選択するトランスポート、またはユーザーが選択するトランスポート上で実行されるプログラムを書くこともできます。ネットワークの選択では、/etc/netconfig データベースと環境変数 NETPATH を使用します。これにより希望するトランスポートを指定したり、また可能であればアプリケーションがそれを使用できます。指定したトランスポートが不適当な場合、アプリケーションは自動的に適切な機能を持つ他のトランスポートを使用しようとします。

/etc/netconfig には、ホストで使用できるトランスポートが記載されていて、タイプによって識別されます。NETPATH はオプションで、ユーザーはこれを使用してトランスポートを指定したり、/etc/netconfig にあるリストからトランスポートを選択したりできます。NETPATH を設定するとユーザーは、アプリケーションが利用できるトランスポートを試みる順序を指定できます。NETPATH を設定しないと、システムはデフォルトで /etc/netconfig に指定されているすべての選択可能なトランスポート (visible (可視) トランスポート) について、ファイルに現れる順番で選択を試みます。

ネットワーク選択についての詳細は、『Transport Interfaces Programming Guide』または getnetconfig(3NSL)netconfig(4) のマニュアルページを参照してください。

RPC では、選択可能なトランスポートを次のタイプに分類します。

表 2-6 nettype パラメータ

値 

説明 

NULL

netpath と同じ

visible

/etc/netconfig ファイルのエントリのうち、可視フラグ (v フラグ) の付いたトランスポートが使用される

circuit_v

visible と同じ。ただし、接続型トランスポートに限定される。/etc/netconfig ファイルに記載された順に選択される

datagram_v

visible と同じ。ただし、非接続型トランスポートに限定される

circuit_n

接続型トランスポートが、NETPATH で設定された順に使用される

datagram_n

非接続型トランスポートが、NETPATH で設定された順に使用される

udp

インターネット・ユーザーデータグラム・プロトコル (UDP) が指定される 

tcp

インターネット・トランスミッション・コントロール・プロトコル (TCP) が指定される 

トランスポート選択

RPC サービスは、接続型と非接続型の両方のトランスポートでサポートされています。トランスポート選択は、アプリケーションの性質で決まります。

アプリケーションが次のすべてに当てはまる場合は、非接続型トランスポートの方が適当です。

アプリケーションが次のどれかに当てはまる場合は、接続型トランスポートの方が適当です。

名前からアドレスへの変換

各トランスポートには固有の変換ルーチンがあり、汎用ネットワークアドレス (トランスポートアドレスを文字列で表現したもの) とローカルアドレスとの相互変換を行います。RPC システム内 (例えば、rpcbind とクライアントの間) では、汎用アドレスが使用されます。各トランスポートには、名前からアドレスへの変換ルーチンの入った実行時リンクライブラリがあります。表 2-7 に、主な変換ルーチンを示します。

上の各ルーチンについての詳細は、netdir(3NSL) のマニュアルページと『Transport Interfaces Programming Guide』を参照してください。どのルーチンの場合も、netconfig 構造体が名前からアドレスへの変換のコンテキストを提供していることに注意してください。

表 2-7 名前からアドレスへの変換ルーチン

netdir_getbyname()

ホストとサービスのペア(たとえば、server1rpcbind) と netconfig 構造体から、netbuf アドレスのセットに変換する。netbuf は TLI 構造体で、実行時にトランスポート固有のアドレスが入る

netbuf アドレスと netconfig 構造体から、ホストとサービスのペアに変換する

uaddr2taddr()

汎用アドレスと netconfig 構造体から、netbuf アドレスに変換する

taddr2uaddr ()

netbuf アドレスと netconfig 構造体から、汎用アドレスに変換する

アドレスルックアップサービス

トランスポートサービスにはアドレスルックアップサービスは含まれていません。トランスポートサービスはネットワーク上のメッセージ転送だけを行います。クライアントプログラムは、使用するサーバープログラムのアドレスを知る必要があります。旧バージョンの SunOS では、portmap デーモンがそのサービスを実行していました。現バージョンでは rpcbind デーモンを使用します。

RPC では、ネットワークアドレスの構造を考慮する必要がありません。RPC では、NULL で終わる ASCII 文字列で指定される汎用アドレスを使用するためです。RPC はトランスポート固有の変換ルーチンを使用して、汎用アドレスをローカルトランスポートアドレスに変換します。変換ルーチンについての詳細は、netdir(3NSL)rpcbind(3NSL) のマニュアルページを参照してください。

rpcbind の機能を次に示します。

アドレス登録

rpcbind が RPC サービスをアドレスにマップするので、rpcbind 自体のアドレスはその利用者に知られていなければなりません。全トランスポートの名前からアドレスへの変換ルーチンが、トランスポートが使用する各タイプのためのアドレスを保有している必要があります。たとえば、インターネットドメインでは、TCPでも UDP でも rpcbind のポート番号は 111 です。rpcbind は、起動されるとホストがサポートしている全トランスポートに自分のアドレスを登録します。RPC サービスのうち rpcbind だけは、前もってアドレスが知られていなければなりません。

rpcbind は、ホストがサポートしている全トランスポートに RPC サービスのアドレスを登録して、クライアントがそれらを使用できるようにします。各サービスは、rpcbind デーモンでアドレスを登録し、クライアントから利用できるようになります。そこで、サービスのアドレスが rpcinfo(1M)rpcbind(3NSL) のマニュアルページで指定されているライブラリルーチンを使用するプログラムとで利用可能になります。クライアントやサーバーからは RPC サービスのネットワークアドレスを知ることはできません。

クライアントプログラムとサーバープログラム、および、クライアントホストとサーバーホストとは通常別のものですが、同じであってもかまいません。サーバープログラムもまたクライアントプログラムになることができます。あるサーバーが別の rpcbind サーバーを呼び出す場合は、クライアントとして呼び出したことになります。

クライアントが遠隔プログラムのアドレスを調べるには、ホストの rpcbind デーモンに RPC メッセージを送信します。サービスがホスト上にあれば、デーモンは RPC 応答メッセージにアドレスを入れて返します。そこで、クライアントプログラムは RPC メッセージをサーバーのアドレスに送ることができます。(クライアントプログラムから rpcbind を頻繁に呼び出さなくて済むように、最後に呼び出した遠隔プログラムのネットワークアドレスを保存しておきます)。

rpcbindRPCBPROC_CALLIT 手続きを使用すると、クライアントはサーバーのアドレスがわからなくても遠隔手続きを呼び出すことができます。クライアントは目的の手続きのプログラム番号、バージョン番号、手続き番号、引数を RPC 呼び出しメッセージで引き渡します。rpcbind は、アドレスマップから目的の手続きのアドレスを探し出し、RPC 呼び出しメッセージにクライアントから受け取った引数を入れて、その手続きに送信します。

目的の手続きから結果が返されると、RPCBPROC_CALLIT はクライアントプログラムにその結果を引き渡します。そのとき、目的の手続きの汎用アドレスも同時に渡されますので、次からはクライアントが直接その手続きを呼び出すことができます。

RPC ライブラリは rpcbind の全手続きのインタフェースを提供します。RPC ライブラリの手続きには、クライアントとサーバーのプログラムのために rpcbind を自動的に呼び出すものもあります。詳細については、付録 B 「RPC プロトコルおよび言語の仕様」 を参照してください。

RPC 情報の取り出し

rpcinfo は、rpcbind で登録した RPC の最新情報を取り出すユーティリティです。rpcbind または portmap ユーティリティと共に rpcinfo を使用して、あるホストに登録されたすべての RPC サービスの汎用アドレスとトランスポートを知ることができます。指定したホスト上で指定したプログラムの特定バージョンを呼び出して、応答が返ったかどうかを調べることができます。詳細については、rpcinfo(1M) のマニュアルページを参照してください。

第 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 という接尾辞が付くため、上の説明がそのまま当てはまらないことに注意してください。

第 4 章 RPC プログラマインタフェース

この章では、RPC との C インタフェースについて取り上げ、RPC を使用してネットワークアプリケーションを書く方法を説明します。RPC ライブラリにおけるルーチンの完全な仕様については、rpc(3NSL) のマニュアルページおよび関連するマニュアルページを参照してください。

マルチスレッド対応の RPC

この章で説明するクライアントおよびサーバーインタフェースは、特に注意書きがある場合 (raw モードなど) 以外は、マルチスレッド対応です。すなわち、RPC 関数を呼び出すアプリケーションはマルチスレッド環境で自由に実行することができます。

単純インタフェース

単純インタフェースでは、その他の RPC ルーチンは不要なため最も簡単に使用できます。しかし、利用できる通信メカニズムの制御は制限されます。このレベルでのプログラム開発は早く、rpcgen コンパイラによって直接サポートされます。大部分のアプリケーションに対しては、rpcgen が提供する機能で十分です。

RPC サービスの中には C の関数としては提供されていないものがありますが、それも RPC プログラムとして使用できます。単純インタフェースライブラリルーチンは、詳細な制御を必要としないプログラムでは RPC 機能を直接使用できます。rnusers() のようなルーチンは、RPC サービスライブラリ librpcsvc にあります。例 4-1 は、RPC ライブラリルーチン rnusers() を呼び出して、遠隔ホスト上のユーザー数を表示します。


例 4-1 rnusers プログラム

#include <rpc/rpc.h>
#include <rpcsvc/rusers.h>
#include <stdio.h>
 
/*
 * rnusers() サービスを
 * 呼び出すプログラム
 */
 
main(argc, argv)
	int argc;
 char **argv;
{
	int num;
 
 if (argc != 2) {
		fprintf(stderr, "usage: %s hostname¥n",
 					argv[0]);
		exit(1);
	}
 if ((num = rnusers(argv[1])) < 0) {
		fprintf(stderr, "error: rnusers¥n");
		exit(1);
	}
	fprintf(stderr, "%d users on %s¥n", num,
						argv[1] );
	exit(0);
} 

例 4-1 のプログラムを次のコマンドを使用してコンパイルします。

cc program.c -lrpcsvc -lnsl

クライアント側

単純インタフェースのクライアント側には、rpc_call() という関数が 1 つだけあります。次の 9 個のパラメータがあります。

int	0 or error code
rpc_call (  
       char				*host		/* サーバーホストの名前 */
       rpcprog_t		prognum 	/* サーバープログラム番号 */
       rpcvers_t		versnum		/* サーバーバージョン番号 */
       rpcproc_t		procnum		/* サーバー手続き番号 */
       xdrproc_t		inproc		/* 引数を符号化する XDR フィルタ */
       char 			*in			/* 引数へのポインタ */
       xdr_proc_t		outproc		/* 結果を復号化するフィルタ */
       char				*out		/* 結果を格納するアドレス */
       char				*nettype	/* トランスポートの選択 */
);

この関数は、host 上で、prognumversumprocnum によって指定する手続きを呼び出します。遠隔手続きに渡される引数は、in パラメータによって指定され、inproc はこの引数を符号化するための XDR フィルタです。out パラメータは、遠隔手続きから戻される結果が置かれるアドレスです。outproc は、結果を復号化してこのアドレスに置く XDR フィルタです。

クライアントプログラムは、サーバーから応答を得るまで rpc_call() のところで停止します。サーバーが呼び出しを受け入れると、0 の値で RPC_SUCCESS を返します。呼び出しが失敗した場合は、0 以外の値が返されます。この値は、clnt_stat で指定される型に型変換されます。これは RPC インクルードファイルの中で定義される列挙型で、clnt_sperrno() 関数により解釈されます。この関数は、このエラーコードに対応する標準 RPC エラーメッセージへのポインタを返します。

この例では、/etc/netconfig に列挙されているすべての選択可能な可視トランスポートが試されます。試行回数を指定するには、下位レベルの RPC ライブラリを使用する必要があります。

複数の引数と複数の結果は、構造体の中で処理されます。

単純インタフェースを使用するために例 4-1 を変更すると、例 4-2 のようになります。


例 4-2 単純インタフェースを使用する rusers プログラム

#include <stdio.h>
#include <utmpx.h>
#include <rpc/rpc.h>
#include <rpcsvc/rusers.h>
 
/*
 *RUSERSPROG RPC プログラムを呼び出すプログラム
 */
 
main(argc, argv)
	int argc;
	char **argv;
{
 unsigned int nusers;
	enum clnt_stat cs;
 
	if (argc != 2) {
		   fprintf(stderr, "usage: rusers hostname¥n");
 	   exit(1);
	}
	if( cs = rpc_call(argv[1], RUSERSPROG,
			 	RUSERSVERS,	RUSERSPROC_NUM, xdr_void,
 				(char *)0, xdr_u_int, (char *)&nusers,
                    "visible")  !=  RPC_SUCCESS )  {
		          clnt_perrno(cs);
 	          exit(1);
	           }
	fprintf(stderr, "%d users on %s¥n", nusers,
						argv[1] );
 exit(0);
}

マシンが異なれば、データ型も異なる表現になるため、rpc_call() は RPC 引数の型と RPC 引数へのポインタを必要とします (サーバーから返される結果についても同様)。RUSERSPROC_NUM の場合、戻り値は unsigned int 型であるため、rpc_call() の最初の戻りパラメータは xdr_u_int (unsigned int 用) で、2 番目は &nusers (unsigned int 型の値があるメモリーへのポインタ) です。RUSERSPROC_NUM には引数がないため、rpc_call() の XDR 符号化関数は xdr_void() で、その引数は NULL です。

サーバー側

単純インタフェースを使用するサーバープログラムは、大変理解しやすいものです。これは単に、呼び出される手続きを登録するため rpc_reg() を呼び出し、次に、RPC ライブラリの遠隔手続きディスパッチャである svc_run() を呼び出して、入ってくる要求を待ちます。

rpc_reg() には次の引数があります。

rpc_reg (
      rpcprog_t		prognum			/* サーバープログラム番号 */
      rpcvers_t		versnum			/* サーバーバージョン番号 */
      rpcproc_t		procnum			/* サーバー手続き番号 */
      char				*procname 	/* 遠隔関数の名前 */
      xdrproc_t		inproc			/* 引数を符号化するフィルタ */
      xdrproc_t		outproc			/* 結果を復号化するフィルタ */
      char			 *nettype			/* トランスポートの選択 */
);

svc_run() は RPC 呼び出しメッセージに応えてサービス手続きを起動します。rpc_reg() のディスパッチャは遠隔手続きが登録されたときに指定された XDR フィルタを使用して、遠隔手続きの引数の復号化と、結果の符号化を行います。サーバープログラムについての注意点をいくつか挙げます。

ユーザーが作成する登録ルーチン

rpcgen は汎用のコードジェネレータであるため、ユーザーが自分で書いた方が、効率のよい短いコードにできる場合があります。そのような登録ルーチンの例を次に示します。次の例では、手続きを 1 つ登録してから、svc_run() に入ってサービス要求を待ちます。

#include <stdio.h>
 #include <rpc/rpc.h>
 #include <rpcsvc/rusers.h>
 void *rusers();
 
 main()
 {
 	if(rpc_reg(RUSERSPROG, RUSERSVERS,
						RUSERSPROC_NUM, rusers,
 					xdr_void, xdr_u_int,
						"visible") == -1) {
 		fprintf(stderr, "Couldn't Register¥n");
 		exit(1);
 	}
 	svc_run();     /* この関数は値を戻さない */
 	fprintf(stderr, "Error: svc_run
						returned!¥n");
 	exit(1);
 }

rpc_reg() は、異なるプログラム、バージョン、手続きを登録するごとに何度でも呼び出すことができます。

任意のデータ型の引き渡し

遠隔手続きへ渡すデータ型と遠隔手続きから受け取るデータ型は、事前に定義した型あるいはプログラマが定義する型の任意のものが可能です。RPC では、データを XDR (external data representation: 外部データ表現) 形式という標準データ形式に変換してからトランスポートに送信するため、個々のマシンに固有のバイト順序や構造体のデータレイアウトに関係なく、任意のデータ構造を扱うことができます。マシン固有のデータ形式から XDR 形式に変換することをシリアライズといい、反対に XDR 形式からマシン固有のデータ形式に変換することをデシリアライズといいます。

rpc_call()rpc_reg() の引数で変換ルーチンを指定するときは、 xdr_u_int() のような XDR プリミティブを指定することも、引数として渡された構造体全体を処理するようなユーザーが作成した変換ルーチンを指定することもできます。引数の変換ルーチンは 2 つの引数を取ります。1 つは変換結果へのポインタで、もう 1 つは XDR ハンドルへのポインタです。

表 4-1 XDR プリミティブタイプのルーチン

XDR プリミティブ・ルーチン 

xdr_int()

xdr_netobj()

xdr_u_long()

xdr_enum()

xdr_long()

xdr_float()

xdr_u_int()

xdr_bool()

xdr_short()

xdr_double()

xdr_u_short()

xdr_wrapstring()

xdr_char()

xdr_quadruple()

xdr_u_char()

xdr_void()

xdr_hyper()

xdr_u_hyper()

 

 

int_types.h 内にある固定幅の整数タイプに慣れている ANSI C プログラマにとって都合がよいように、ルーチン xdr_char()xdr_short()xdr_int()xdr_hyper() (および、それぞれの符号なしバージョン) には、表 4-2 で示すように、ANSI C を連想させる名前の付いた同等の関数があります。

表 4-2 プリミティブタイプの等価関数

関数 

Equivalent 

xdr_char()

xdr_int8_t()

xdr_u_char()

xdr_u_int8_t()

xdr_short()

xdr_int16_t()

xdr_u_short()

xdr_u_int16_t()

xdr_int()

xdr_int32_t()

xdr_u_int()

xdr_u_int32_t()

xdr_hyper()

xdr_int64_t()

xdr_u_hyper()

xdr_u_int64_t()

xdr_wrapstring() から呼び出す xdr_string() はプリミティブではなく、3 つ以上の引数を取ります。

ユーザーが作成する変換ルーチンの例を次に示します。手続きに渡す引数は次の構造体に入れます。

struct simple {
 	int a;
 	short b;
 } simple;

この構造体で渡された引数を変換する XDR ルーチン xdr_simple() は、例 4-3 に示すようになります。


例 4-3 xdr_simple() ルーチン

#include <rpc/rpc.h>
#include "simple.h"
 
bool_t
xdr_simple(xdrsp, simplep)
	XDR *xdrsp;
	struct simple *simplep;
{
 if (!xdr_int(xdrsp, &simplep->a))
		return (FALSE);
 if (!xdr_short(xdrsp, &simplep->b))
		return (FALSE);
 return (TRUE);
}

rpcgen でも、同じ機能を持つ変換ルーチンを自動生成できます。

XDR ルーチンは、データ変換に成功した場合はゼロ以外の値 (C では TRUE) を返し、失敗した場合はゼロを返します。XDR についての詳細は、付録 C 「XDR プロトコル仕様」 を参照してください。

表 4-3 XDR ブロック構築ルーティン
 基本のルーチン

xdr_array()

xdr_bytes()

xdr_reference()

xdr_vector()

xdr_union()

xdr_pointer()

xdr_string()

xdr_opaque()

 

たとえば、可変長の整数配列を送るときは、配列へのポインタと配列サイズを次のような構造体にパックします。

struct varintarr {
 	int *data;
 	int arrlnth;
 } arr;

この配列を変換するルーチン xdr_varintarr()例 4-4 に示すようになります。


例 4-4 変換ルーチン xdr_varintarr()

bool_t
xdr_varintarr(xdrsp, arrp)
 XDR *xdrsp;
	struct varintarr *arrp;
{
 return(xdr_array(xdrsp, (caddr_t)&arrp->data,
		(u_int *)&arrp->arrlnth, MAXLEN,
		sizeof(int), xdr_int));
} 

xdr_array() に渡す引数は、XDR ハンドル、配列へのポインタ、配列サイズへのポインタ、配列サイズの最大値、配列要素のサイズ、配列要素を変換する XDR ルーチンへのポインタです。配列サイズが前もってわかっている場合は、例 4-5 のように xdr_vector() を使用します。


例 4-5 変換ルーチン xdr_vector()

int intarr[SIZE];
 
bool_t
xdr_intarr(xdrsp, intarr)
	XDR *xdrsp;
	int intarr[];
{
 return (xdr_vector(xdrsp, intarr, SIZE,
				sizeof(int),
xdr_int));
} 

XDR ルーチンでシリアライズすると、データが 4 バイトの倍数になるように変換されます。たとえば、文字配列を変換すると、各文字が 32 ビットを占有するようになります。xdr_bytes() は、文字をパックするルーチンで、xdr_array() の最初の 4 つの引数と同様の引数を取ります。

NULL で終わる文字列は xdr_string() で変換します。このルーチンは、長さの引数がない xdr_bytes() ルーチンのようなものです。文字列をシリアライズするときは strlen() で長さを取り出し、デシリアライズするときは NULL で終わる文字列を生成します。

例 4-6 では、組み込み関数 xdr_string()xdr_reference() を呼び出して、文字列へのポインタと、前の例で示した構造体 simple へのポインタを変換します。


例 4-6 変換ルーチン xdr_reference()

struct finalexample {
	char *string;
 struct simple *simplep;
} finalexample;
 
bool_t
xdr_finalexample(xdrsp, finalp)
	XDR *xdrsp;
	struct finalexample *finalp;
{
 if (!xdr_string(xdrsp, &finalp->string,
			MAXSTRLEN))
 	return (FALSE);
	if (!xdr_reference( xdrsp, &finalp->simplep,
 		sizeof(struct simple), xdr_simple))
		return (FALSE);
 return (TRUE);
 
}

ここで、xdr_reference() の代わりに xdr_simple() を呼び出してもよいことに注意してください。

標準インタフェース

RPC パッケージの標準レベルへのインタフェースは、RPC 通信へのさらに詳細な制御を提供します。この制御を使用するプログラムはより複雑になります。下位レベルでの効果的なプログラミングには、コンピュータネットワークの構造に対するより深い知識が必要です。トップ、中間、エキスパート、ボトムレベルは、標準インタフェースの一部です。

この節では、RPC ライブラリの下位レベルを使用して RPC プログラムを詳細に制御する方法について説明します。たとえば、単純インタフェースレベルでは NETPATH を介してしか使用できなかったトランスポートプロトコルを自由に使用できます。これらのルーチンを使用するには、TLI に対する知識が必要です。

表 4-4 に示したルーチンにはトランスポートハンドルの指定が必要なため、単純インタフェースからは使用できません。たとえば、単純レベルでは、XDR ルーチンでシリアライズとデシリアライズを行うときに、メモリーの割り当てと解放を行うことはできません。

表 4-4 トランスポートハンドルの指定が必要な XDR ルーチン

単純インタフェースでは使用できないルーチン 

clnt_call()

clnt_destroy()

clnt_control()

clnt_perrno()

clnt_pcreateerror()

clnt_perror()

svc_destroy()

 

 

トップレベルのインタフェース

トップレベルのルーチンを使用すると、アプリケーションで使用するトランスポートタイプを指定できますが、特定のトランスポートは指定できません。このレベルは、クライアントとサーバーの両方でアプリケーションが自分のトランスポートハンドルを作成する点で、単純インタフェースと異なります。

クライアント側

例 4-7 に示すようなヘッダーファイルがあるとします。


例 4-7 ヘッダーファイル time_prot.h

/* time_prot.h */
#include <rpc/rpc.h>
#include <rpc/types.h>
 
struct timev {
 int second;
	int minute;
	int hour;
};
typedef struct timev timev;
bool_t xdr_timev();
 
#define TIME_PROG 0x40000001
#define TIME_VERS 1
#define TIME_GET  1

例 4-8 に、クライアント側の、トップレベルのサービスルーチンを使用する簡単な日時表示プログラムを示します。このプログラムでは、時刻を返すサービスを呼び出します。トランスポートタイプはプログラムを起動するときの引数で指定します。


例 4-8 時刻を返すサービス : クライアント側

#include <stdio.h>
#include "time_prot.h"
 
#define TOTAL (30)
/*
 * 時刻を返すサービスを呼び出すプログラム
 * 使用方法: calltime ホスト名
 */
main(argc, argv)
 int argc;
	char *argv[];
{
	struct timeval time_out;
	CLIENT *client;
	enum clnt_stat stat;
	struct timev timev;
	char *nettype;
 
	if (argc != 2 && argc != 3) {
		fprintf(stderr,"usage:%s host[nettype]¥n"
					,argv[0]);
		exit(1);
 }
	if (argc == 2)
		nettype = "netpath";		/* デフォルト */	
	else
		nettype = argv[2];
 client = clnt_create(argv[1], TIME_PROG,
									TIME_VERS, nettype);
	if (client == (CLIENT *) NULL) {
 	clnt_pcreateerror("Couldn't create client");
 	exit(1);
	}
	time_out.tv_sec = TOTAL;
 time_out.tv_usec = 0;
	stat = clnt_call( client, TIME_GET, 
 				xdr_void, (caddr_t)NULL,
					xdr_timev, (caddr_t)&timev,
 				time_out);
	if (stat != RPC_SUCCESS) {
 	clnt_perror(client, "Call failed");
		exit(1);
 }
	fprintf(stderr,"%s: %02d:%02d:%02d GMT¥n",
 			nettype timev.hour, timev.minute,
				timev.second);
 (void) clnt_destroy(client);
	exit(0);
}

プログラムを起動するときに nettype を指定しなかった場合は、代わりに 「netpath」という文字列が使用されます。RPC ライブラリルーチンは、この文字列を見つけると、環境変数 NETPATH 値によって使用するトランスポートを決めます。

クライアントハンドルが作成できない場合は、clnt_pcreateerror() でエラー原因を表示するか、グローバル変数 rpc_createerr の値としてエラーステータスを取り出します。

クライアントハンドルが作成できたら、clnt_call() を使用して遠隔呼び出しを行います。clnt_call() の引数は、クライアントハンドル、遠隔手続き番号、入力引数に対する XDR フィルタ、引数へのポインタ、戻り値に対する XDR フィルタ、戻り値へのポインタ、呼び出しのタイムアウト値です。この例では、遠隔手続きに渡す引数はないので、XDR ルーチンとしては xdr_void() を指定しています。最後に clnt_destroy() を使用して使用済みメモリーを解放します。

上記の例でプログラマがクライアントハンドル作成に許される時間を 30 秒に設定したいとすると、次のコード例の一部のように、clnt_create() への呼び出しは clnt_create_timed() への呼び出しに替わります。

struct timeval timeout;
timeout.tv_sec = 30;		/* 30 秒 */
timeout.tv_usec = 0;
 
client = clnt_create_timed(argv[1],
				TIME_PROG, TIME_VERS, nettype,
 			&timeout);
 

例 4-9 には、トップレベルのサービスルーチンを使用したサーバー側プログラムを示します。このプログラムでは、時刻を返すサービスを実行します。


例 4-9 時刻を返すサービス : サーバー側

#include <stdio.h>
#include <rpc/rpc.h>
#include "time_prot.h"
 
static void time_prog();
 
main(argc,argv)
 int argc;
	char *argv[];
{
	int transpnum;
	char *nettype;
 
	if (argc > 2) {
 
	 fprintf(stderr, "usage: %s [nettype]¥n",
 					argv[0]);
		exit(1);
	}
 if (argc == 2)
		nettype = argv[1];
	else
 	nettype = "netpath";			/* デフォルト */
	transpnum =
svc_create(time_prog,TIME_PROG,TIME_VERS,nettype);
 if (transpnum == 0) {
		fprintf(stderr,"%s: cannot create %s service.¥n",
					argv[0], nettype);
 	exit(1);
	}
	svc_run();
}
 
/*
 * サーバーのディスパッチ関数
 */
static void
time_prog(rqstp, transp)
	struct svc_req *rqstp;
	SVCXPRT *transp;
{
 struct timev rslt;
	time_t thetime;
 
	switch(rqstp->rq_proc) {
		case NULLPROC:
			svc_sendreply(transp, xdr_void, NULL);
			return;
		case TIME_GET:
 		break;
		default:
			svcerr_noproc(transp);
 		return;
		}
	thetime = time((time_t *) 0);
 rslt.second = thetime % 60;
	thetime /= 60;
 rslt.minute = thetime % 60;
	thetime /= 60;
 rslt.hour = thetime % 24;
	if (!svc_sendreply( transp, xdr_timev, &rslt)) {
		svcerr_systemerr(transp);
	 }
}

svc_create() は、サーバーハンドルを作成したトランスポートの個数を返します。サービス関数 time_prog() は、対応するプログラム番号とバージョン番号を指定したサービス要求がきたときに svc_run() に呼び出されます。サーバーは、svc_sendreply() を使用して戻り値をクライアントに返します。

rpcgen を使用してディスパッチ関数を生成する場合は、svc_sendreply() は手続きが戻り値を返してから呼び出されるため、戻り値 (この例では rslt) は実際の手続き内で static 宣言しなければなりません。この例では、svc_sendreply() はディスパッチ関数の中で呼び出されているので、rsltstatic で宣言されていません。

この例の遠隔手続きには引数がありませんが、引数を渡す必要がある場合は次の 2 つの関数を呼び出して、引数を取り出し、デシリアライズ (XDR 形式から復号化) し、解放します。

svc_getargs( SVCXPRT_handle, XDR_filter, argument_pointer);
svc_freeargs( SVCXPRT_handle, XDR_filter argument_pointer );

中間レベルのインタフェース

中間レベルのルーチンを使用するときは、使用するトランスポート自体をアプリケーションから直接選択します。

クライアント側

例 4-10 は、「トップレベルのインタフェース」の時刻サービスのクライアント側プログラムを、中間レベルの RPC で書いたものです。この例のプログラムを実行するときは、どのトランスポートで呼び出しを行うか、コマンド行で指定する必要があります。


例 4-10 時刻サービスのクライアント側プログラム

#include <stdio.h>
#include <rpc/rpc.h>
#include <netconfig.h>		/* 構造体 netconfig を使用するため */
#include "time_prot.h"
 
#define TOTAL (30)
 
main(argc,argv)
	int argc;
	char *argv[];
{
	CLIENT *client;
	struct netconfig *nconf;
	char *netid;
 /* 以前のサンプルプログラムの宣言と同じ */
 
	if (argc != 3) {
 	fprintf(stderr, "usage: %s host netid¥n",
					argv[0]);
 	exit(1);
	}
	netid = argv[2];
 if ((nconf = getnetconfigent( netid)) ==
 
	    (struct netconfig *) NULL) {
		fprintf(stderr, "Bad netid type: %s¥n",
					netid);
		exit(1);
 }
	client = clnt_tp_create(argv[1], TIME_PROG,
 									TIME_VERS, nconf);
	if (client == (CLIENT *) NULL) {
		clnt_pcreateerror("Could not create client");
 	exit(1);
	}
	freenetconfigent(nconf);
 
	/* これ以降は以前のサンプルプログラムと同じ */
}

この例では、getnetconfigent(netid) を呼び出して netconfig 構造体を取り出しています 。詳細については、getnetconfig(3NSL) のマニュアルページと『Transport Interfaces Programming Guide』を参照してください。このレベルの RPC を使用する場合は、プログラムで直接ネットワーク (トランスポート) を選択できます

上記の例でプログラマがクライアントハンドル作成に許される時間を 30 秒に設定したいとすると、次のコード例の一部のように、 clnt_tp_create() への呼び出しは clnt_tp_create_timed() への呼び出しに替わります。

struct timeval timeout;
timeout.tv_sec = 30; /* 30 秒 */
timeout.tv_usec = 0;
 
client = clnt_tp_create_timed(argv[1], 
			TIME_PROG, TIME_VERS, nconf,
			&timeout);
 

サーバー側

これに対するサーバー側プログラムを 例 4-11 に示します。サービスを起動するコマンド行では、どのトランスポート上でサービスを提供するかを指定する必要があります。


例 4-11 時刻サービスのサーバー側プログラム

/*
 * このプログラムは、サービスを呼び出したクライアントにグリニッチ標準時を
 * 返します。呼び出し方法: server netid
 */
#include <stdio.h>
#include <rpc/rpc.h>
#include <netconfig.h>    /* 構造体 netconfig を使用するため */
#include "time_prot.h"
 
static void time_prog();
 
main(argc, argv)
 int argc;
	char *argv[];
{
	SVCXPRT *transp;
	struct netconfig *nconf;
 
	if (argc != 2) {
		fprintf(stderr, "usage: %s netid¥n",
 				argv[0]);
		exit(1);
	}
 if ((nconf = getnetconfigent( argv[1])) ==
					(struct netconfig *) NULL) {
		fprintf(stderr, "Could not find info on %s¥n",
 				argv[1]);
		exit(1);
	}
 transp = svc_tp_create(time_prog, TIME_PROG,
										TIME_VERS, nconf);
	if (transp == (SVCXPRT *) NULL) {
 	fprintf(stderr, "%s: cannot create 
						%s service¥n", argv[0], argv[1]);
		exit(1)
	}
	freenetconfigent(nconf);
 svc_run();
}
 
	static
 void time_prog(rqstp, transp)
		struct svc_req *rqstp;
 	SVCXPRT *transp;
{
/* トップレベルの RPC を使用したコードと同じ */
 

エキスパートレベルのインタフェース

エキスパートレベルのネットワーク選択は、中間レベルと同じです。中間レベルとの唯一の違いは、アプリケーションから CLIENTSVCXPRT のハンドルをより詳細に制御できる点です。次の例では、clnt_tli_create()svc_tli_create() の 2 つのルーチンを使用した制御方法を示します。TLI についての詳細は、『Transport Interfaces Programming Guide』を参照してください。

クライアント側

例 4-12 には、clnt_tli_create() を使用して UDP トランスポートに対するクライアントを作成するルーチン clntudp_create() を示します。このプログラムでは、指定したトランスポートファミリに基づいたネットワークの選択方法を示します。clnt_tli_create() には、クライアントハンドルの作成のほかに次の 3 つの機能があります。


例 4-12 下位レベル RPC 使用に対するクライアント側プログラム

#include <stdio.h>
#include <rpc/rpc.h>
#include <netconfig.h>
#include <netinet/in.h>
/*
 * 旧バージョンの RPC では、TCP/IP と UDP/IP だけがサポートされていました。 
 * 現バージョンの clntudp_create() は TLI/STREAMS に基づいています。
 */
CLIENT *
clntudp_create(raddr, prog, vers, wait, sockp)
 struct sockaddr_in *raddr;		/* 遠隔アドレス */
	rpcprog_t prog;	 				/* プログラム番号 */
	prcvers_t vers;						/* バージョン番号 */
 struct timeval wait;				/* 待ち時間 */
	int *sockp;								/* ファイル記述子 (fd) のポインタ */
{
	CLIENT *cl;								/* クライアントハンドル */
 int madefd = FALSE;					/* fd はオープンされているか */
	int fd = *sockp; 					/* TLI の fd */
	struct t_bind *tbind;				/* 結合アドレス */
 struct netconfig *nconf;			/* netconfig 構造体 */
	void *handlep;
 
	if ((handlep = setnetconfig() ) == (void *) NULL) {
		/* ネットワーク設定開始でのエラー */
		rpc_createerr.cf_stat = RPC_UNKNOWNPROTO;
 	return((CLIENT *) NULL);
	}
	/*
  * 非接続型で、プロトコルファミリが INET、名前が UDP の 
	 * トランスポートが見つかるまで探す。
  */
	while (nconf = getnetconfig( handlep)) {
 	if ((nconf->nc_semantics == NC_TPI_CLTS) &&
		     (strcmp( nconf->nc_protofmly, NC_INET ) == 0) &&
 	     (strcmp( nconf->nc_proto, NC_UDP ) == 0))
		 break;
 }
	if (nconf == (struct netconfig *) NULL)
 	rpc_createerr.cf_stat = RPC_UNKNOWNPROTO;
		goto err;
 }
	if (fd == RPC_ANYFD) {
		fd = t_open(nconf->nc_device, O_RDWR, &tinfo);
		if (fd == -1) {
			rpc_createerr.cf_stat = RPC_SYSTEMERROR;
			goto err;
		}
 }
	if (raddr->sin_port == 0) { /* 未知の遠隔アドレス */
 	u_short sport;
		/*
		 * ユーザー作成のルーチン rpcb_getport() は rpcb_getaddr を呼び出して、
		 * netbuf アドレスをホストのバイト順序に従ってポート番号に変換する
 	 */
		sport = rpcb_getport(raddr, prog, vers, nconf);
 	if (sport == 0) {
			rpc_createerr.cf_stat = RPC_PROGUNAVAIL;
 		goto err;
		}
		raddr->sin_port = htons(sport);
 }
	/* sockaddr_in を netbuf に変換 */
	tbind = (struct t_bind *) t_alloc(fd, T_BIND, T_ADDR);
	if (tbind == (struct t_bind *) NULL)
		rpc_createerr.cf_stat = RPC_SYSTEMERROR;
 	goto err;
	}
	if (t_bind->addr.maxlen < sizeof( struct sockaddr_in))
		goto err;
	(void) memcpy( tbind->addr.buf, (char *)raddr,
	               sizeof(struct sockaddr_in));
	tbind->addr.len = sizeof(struct sockaddr_in);
 /* fd を結合 */
	if (t_bind( fd, NULL, NULL) == -1) {
 	rpc_createerr.ct_stat = RPC_TLIERROR;
		goto err;
 }
	cl = clnt_tli_create(fd, nconf, &(tbind->addr), prog, vers,
	                      tinfo.tsdu, tinfo.tsdu);
 /* netconfig ファイルを閉じる */
	(void) endnetconfig( handlep);
 (void) t_free((char *) tbind, T_BIND);
	if (cl) {
 	*sockp = fd;
		if (madefd == TRUE) {
			/* fd はハンドルの破棄と同時に閉じる */
			(void)clnt_control(cl,CLSET_FD_CLOSE, (char *)NULL);
		}
		/* リトライ時間の設定 */
 	(void) clnt_control( l, CLSET_RETRY_TIMEOUT,
		                     (char *) &wait);
		return(cl);
 }
err:
	if (madefd == TRUE)
 	(void) t_close(fd);
	(void) endnetconfig(handlep);
 return((CLIENT *) NULL);
}

ネットワーク (トランスポート) 選択には、setnetconfig()getnetconfig()endnetconfig() を使用します。


注 -

endnetconfig() の呼び出しは、プログラムの終り近くの clnt_tli_create() の呼び出しの後で行なっていることに注意してください。


clntudp_create() には、オープンしている TLI ファイル記述子を渡すことができます。ファイル記述子が渡されなかった場合 (fd == RPC_ANYFD) は、t_open() に渡すデバイス名を UDP の netconfig 構造体から取り出して自分でオープンします。

遠隔アドレスがわからない場合 (raddr->sin_port == 0) は、遠隔の rpcbind デーモンを使って取り出します。

クライアントハンドルが作成されれば、clnt_control() を使用してさまざまな変更を加えることができます。RPC ライブラリはハンドルを破棄するときにファイル記述子を閉じ (fd をライブラリ内でオープンしたときは、clnt_destroy() の呼び出しにより閉じられる)、リトライのタイムアウト値を設定します。

サーバー側

例 4-13 には、これに対するサーバー側プログラム svcudp_create() を示します。サーバー側では svc_tli_create() を使用します。

svc_tli_create() は、アプリケーションで次のように詳細な制御を行う必要があるときに使用します。

サービスを rpcbind により登録するには、rpcb_set() を使用します。


例 4-13 下位レベル RPC を使用したサーバー側プログラム

#include <stdio.h>
#include <rpc/rpc.h>
#include <netconfig.h>
#include <netinet/in.h>
 
SVCXPRT *
svcudp_create(fd)
	register int fd;
{
 struct netconfig *nconf;
	SVCXPRT *svc;
	int madefd = FALSE;
	int port;
	void *handlep;
 struct  t_info tinfo;
 
	/* どのトランスポートも使用不可の場合 */
 if ((handlep = setnetconfig() ) == (void *) NULL) {
		nc_perror("server");
 	return((SVCXPRT *) NULL);
	}
	/*
  * 非接続型で、プロトコルファミリが INET、名前が UDP の
	 * トランスポートが見つかるまで探す。
  */
	while (nconf = getnetconfig( handlep)) {
 	if ((nconf->nc_semantics == NC_TPI_CLTS) &&
		    (strcmp( nconf->nc_protofmly, NC_INET) == 0 )&&
		    (strcmp( nconf->nc_proto, NC_UDP) == 0 ))
			break;
 }
	if (nconf == (struct netconfig *) NULL) {
 	endnetconfig(handlep);
		return((SVCXPRT *) NULL);
 }
	if (fd == RPC_ANYFD) {
		fd = t_open(nconf->nc_device, O_RDWR, &tinfo);
		if (fd == -1) {
			(void) endnetconfig();
			return((SVCXPRT *) NULL);
 	}
		madefd = TRUE;
	} else
 	t_getinfo(fd, &tinfo);
	svc = svc_tli_create(fd, nconf, (struct t_bind *) NULL,
	                      tinfo.tsdu, tinfo.tsdu);
	(void) endnetconfig(handlep);
 if (svc == (SVCXPRT *) NULL) {
		if (madefd)
 		(void) t_close(fd);
		return((SVCXPRT *)NULL);
 }
	return (svc);
}

この例では、clntudp_create() と同じ方法でネットワーク選択を行なっています。svc_tli_create() で結合しているため、ファイル記述子は明示的にはトランスポートアドレスと結合されません。

svcudp_create() はオープンしている fd を使用できます。有効な fd が渡されなければ、選択された netconfig 構造体を使用してこのルーチン内でオープンします。

ボトムレベルのインタフェース

アプリケーションで RPC のボトムレベルインタフェースを使用すると、すべてのオプションを使用して通信を制御できます。clnt_tli_create() などのエキスパートレベルの RPC インタフェースは、ボトムレベルのルーチンを使用しています。ユーザーがこのレベルのルーチンを直接使用することはほとんどありません。

ボトムレベルのルーチンは内部データ構造を作成し、バッファを管理し、RPC ヘッダーを作成します。ボトムレベルルーチンの呼び出し側 (たとえば、clnt_tli_create()) では、クライアントハンドルの cl_netidcl_tp の両フィールドを初期化する必要があります。作成したハンドルの cl_netid にはトランスポートのネットワーク ID (たとえば udp) を設定し、cl_tp にはトランスポートのデバイス名 (たとえば /dev/udp) を設定します。clnt_dg_create()clnt_vc_create() のルーチンは、clnt_opscl_private のフィールドを設定します。

クライアント側

例 4-14 は、clnt_vc_create()clnt_dg_create() の呼び出し方法を示します。


例 4-14 ボトムレベルのルーチンを使用したクライアント作成

/*
 * 使用する変数 :
 * cl: CLIENT *
 * tinfo: struct t_info (t_open() または t_getinfo() からの戻り値)
 * svcaddr: struct netbuf *
 */
	switch(tinfo.servtype) {
		case T_COTS:
		case T_COTS_ORD:
 		cl = clnt_vc_create(fd, svcaddr,
			 prog, vers, sendsz, recvsz);
			break;
		case T_CLTS:
 		cl = clnt_dg_create(fd, svcaddr,
			 prog, vers, sendsz, recvsz);
			break;
		default:
 		goto err;
	}

これらのルーチンを使用するときは、ファイル記述子がオープンされて結合されている必要があります。svcaddr はサーバーのアドレスです。

サーバー側

サーバー側は 例 4-15 のようになります。


例 4-15 ボトムレベル用のサーバー

/*
 * 使用する変数
 * xprt: SVCXPRT *
 */
switch(tinfo.servtype) {
 case T_COTS_ORD:
	case T_COTS:
		xprt = svc_vc_create(fd, sendsz, recvsz);
 
		break;
	case T_CLTS:
		xprt = svc_dg_create(fd, sendsz, recvsz);
 
	 break;
	default:
		goto err;
} 

サーバーのキャッシュ

svc_dg_enablecache() はデータグラムトランスポートのキャッシュを開始します。キャッシュは、サーバー手続きが「一度だけ」行われるバージョンにのみ、使用されるべきです。これは、キャッシュされたサーバー手続きを何回も実行すると、異なる別の結果を生じるためです。

svc_dg_enablecache(xprt, cache_size)
    SVCXPRT *xprt;
    unsigned int cache_size;

この関数は、cache_size エントリを保持するために十分な大きさで、サービスのエンドポイント xprt に、重複要求キャッシュを割り当てます。サービスに、異なる戻り値を返す手続きが含まれる場合は、重複要求キャッシュが必要です。キャッシュをいったん有効にすると、後で無効にする方法はありません。

下位レベルのデータ構造

次のデータ構造は参考のために示しますが、変更される可能性があります。

最初に示すのは、クライアント側の RPC ハンドルで、<rpc/clnt.h> で定義されています。下位レベルの RPC を使用する場合は、例 4-16 に示したように接続ごとに 1 つのハンドルを作成して初期化する必要があります。


例 4-16 クライアント側 RPC ハンドル (CLIENT 構造体)

typedef struct {
	AUTH *cl_auth;								/* 認証情報 */
	struct clnt_ops {
		enum clnt_stat (*cl_call)();		/* 遠隔手続き呼び出し */
		void			(*cl_abort)();			/* 呼び出しの中止 */
		void			(*cl_geterr)();		/* 特定エラーコードの取得 */
 	bool_t			(*cl_freeres)();	/* 戻り値の解放*/
		void			(*cl_destroy)(); 	/* この構造体の破棄 */
		bool_t			(*cl_control)();	/* RPC の ioctl() */
	} *cl_ops;
	caddrt_t			cl_private;				/* プライベートに使用 */
	char			*cl_netid;					/* ネットワークトークン */
 char			*cl_tp;						/* デバイス名 */
} CLIENT;

クライアント側ハンドルの第 1 フィールドは、 <rpc/auth.h> で定義された認証情報の構造体です。このフィールドはデフォルトで AUTH_NONE に設定されているため、例 4-17に示すように、必要に応じてクライアント側プログラムで cl_auth を初期化する必要があります。


例 4-17 クライアント側の認証ハンドル

typedef struct {
	struct			opaque_auth  ah_cred;
 struct			opaque_auth  ah_verf;
	union			des_block    ah_key;
 struct auth_ops {
		void		(*ah_nextverf)();
 	int		(*ah_marshal)();			/* nextverf とシリアライズ */
		int		(*ah_validate)(); 		/* 妥当性検査の確認 */
		int		(*ah_refresh)();			/* 資格のリフレッシュ */
 	void		(*ah_destroy)();			/* この構造体の破棄 */
	} *ah_ops;
 caddr_t ah_private;
} AUTH;

AUTH 構造体の ah_cred には呼び出し側の資格が、ah_verf には資格を確認するためのデータが入っています。詳細については、「認証」 を参照してください。

例 4-18 には、サーバー側のトランスポートハンドルを示します。


例 4-18 サーバー側のトランスポートハンドル

typedef struct {
	int			xp_fd;
#define xp_sock					xp_fd
	u_short xp_port;		/* 結合されたポート番号、旧形式 */
 struct xp_ops {
	    bool_t				(*xp_recv)();		/* 要求の受信 */
    enum xprt_stat (*xp_stat)();			/* トランスポートステータスの取得 */
	    bool_t				(*xp_getargs)();	/* 引数の取り出し */
	    bool_t				(*xp_reply)(); 	/* 応答の送信 */
	    bool_t				(*xp_freeargs)();		/* 引数に割り当てたメモリーの解放* */
	    void				(*xp_destroy)();		/* この構造体の破棄 */
 } *xp_ops;
	int		xp_addrlen;			/* 遠隔アドレスの長さ、旧形式 */
 char		*xp_tp;				/* トランスポートプロバイダのデバイス名 */
	char		*xp_netid; 		/* ネットワークトークン */
	struct netbuf  xp_ltaddr;		/* ローカルトランスポートアドレス */
	struct netbuf  xp_rtaddr;		/* 遠隔トランスポートアドレス */
 char				xp_raddr[16];	/* 遠隔アドレス、旧形式 */
	struct opaque_auth xp_verf;	/* raw 応答の確認 */
	caddr_t				xp_p1;		/* プライベート: svc ops で使用 */
	caddr_t				xp_p2;		/* プライベート: svc ops で使用 */
 caddr_t				xp_p3;		/* プライベート: svc lib で使用 */
} SVCXPRT;

表 4-5 は、サーバー側のトランスポートハンドルに対応するフィールドを示します。

表 4-5 RPC サーバー側のトランスポートハンドル

xp_fd

ハンドルに結合したファイル記述子。複数のサーバーハンドルで 1 つのファイル記述子を共有できる 

xp_netid

トランスポートのネットワーク ID (たとえば、udp)。ハンドルはこのトランスポート上に作成される。xp_tp は、このトランスポートに結合したデバイス名

xp_ltaddr

サーバー自身の結合アドレス 

xp_rtaddr

RPC の呼び出し側アドレス (したがって、呼び出しのたびに変る) 

xp_netid xp_tp xp_ltaddr

svc_tli_create() のようなエキスパートレベルのルーチンで初期化される

その他のフィールドは、ボトムレベルのサーバールーチン svc_dg_create()svc_vc_create() で初期化されます。

接続型端点では、表 4-6 の各フィールドには、接続要求がサーバーに受け入れられるまで正しい値が入りません。

表 4-6 RPC 接続型端点

接続が確立するまでは無効なフィールド 

xp_fd

xp_ops()

xp_p1()

xp_p2

xp_verf()

xp_tp()

xp_ltaddr

xp_rtaddr()

xp_netid()

下位レベルの Raw RPC を使用したプログラムテスト

デバッグツールとして、ネットワーク機能をすべてバイパスする 2 つの擬似 RPC インタフェースがあります。ルーチン clnt_raw_create()svc_raw_create() は、実際のトランスポートを使用しません。


注 -

製品システムで RAW モードは使用しないでください。RAW モードは、デバッグを行い易くするために使用します。RAW モードはマルチスレッド対応ではありません。


例 4-19 は、次の Makefile を使用してコンパイルとリンクを行います。

all: raw
CFLAGS += -g
raw: raw.o
cc -g -o raw raw.o -lnsl
 

例 4-19 Raw RPC を使用した簡単なプログラム

/*
 * 数値を 1 増加させる簡単なプログラム
 */
 
#include <stdio.h>
#include <rpc/rpc.h>
#include <rpc/raw.h>
#define prognum 0x40000001
#define versnum 1
#define INCR 1
 
struct timeval TIMEOUT = {0, 0};
static void server();
 
main (argc, argv)
	int argc;
	char **argv;
{
 CLIENT *cl;
	SVCXPRT *svc;
	int num = 0, ans;
 int flag;
 
	if (argc == 2)
		num = atoi(argv[1]);
 	svc = svc_raw_create();
	if (svc == (SVCXPRT *) NULL) {
 	fprintf(stderr, "Could not create server handle¥n");
		exit(1);
 }
	flag = svc_reg( svc, prognum, versnum, server,
         (struct netconfig *) NULL );
    if (flag == 0) {
     fprintf(stderr, "Error: svc_reg failed.¥n");
	    exit(1);
 }
	cl = clnt_raw_create( prognum, versnum );
 if (cl == (CLIENT *) NULL) {
		clnt_pcreateerror("Error: clnt_raw_create");
 	exit(1);
	}
	if (clnt_call(cl, INCR, xdr_int, (caddr_t) &num, xdr_int,
	      (caddr_t) &ans, TIMEOUT)
   != RPC_SUCCESS) {
		clnt_perror(cl, "Error: client_call with raw");
		exit(1);
	}
	printf("Client: number returned %d¥n", ans);
	exit(0);
}
 
static void
server(rqstp, transp)
	struct svc_req *rqstp;
 SVCXPRT *transp;
{
	int num;
 
   fprintf(stderr, "Entering server procedure.¥n");
 
	switch(rqstp->rq_proc) {
		case NULLPROC:
			if (svc_sendreply( transp, xdr_void,
				(caddr_t) NULL) == FALSE) {
	 		fprintf(stderr, "error in null proc¥n");
				exit(1);
 		}
			return;
		case INCR:
 		break;
		default:
			svcerr_noproc(transp);
 		return;
	}
	if (!svc_getargs( transp, xdr_int, &num)) {
		svcerr_decode(transp);
		return;
 }
    fprintf(stderr, "Server procedure: about to increment.¥n");
 num++;
	if (svc_sendreply(transp, xdr_int, &num) == FALSE) {
		fprintf(stderr, "error in sending answer¥n");
 	exit (1);
	}
    fprintf(stderr, "Leaving server procedure.¥n");
} 

次の点に注意してください。

RPC プログラミングの高度なテクニック

この節では、RPC の下位レベルインタフェースを使用するさまざまな開発テクニックを説明します。この章で説明する項目を次に示します。

サーバー側の poll( )

この節で説明する内容は、(デフォルトの) シングルスレッドのモードで RPC を実行する場合にだけ適用されます。

RPC 要求をサービスしたり、その他のアクティビティを実行したりするプロセスでは、svc_run() を呼び出せない場合があります。他のアクティビティで定期的にデータ構造を更新する場合は、プロセスから svc_run() を呼び出す前に SIGALRM 信号をセットできます。そうすると、シグナルハンドラがデータ構造を処理してから svc_run() に制御を戻します。

プロセスから svc_run() をバイパスして直接ディスパッチャにアクセスするには、svc_getreqset() を呼び出します。待っているプログラムに結合したトランスポート端点のファイル記述子がわかれば、プロセスは自分で poll() を呼び出して、RPC ファイル記述子と自身の記述子の両方で要求を待つことができます。

例 4-20 には svc_run() を示します。svc_pollset は、_rpc_select_to_poll() の呼び出しを通して svc_fdset() から派生した pollfd 構造体の配列です。この配列は、RPC ライブラリルーチンのどれかが呼び出されるたびに変わる可能性があります。そのたびに記述子がオープンされ、クローズされるからです。poll() がいくつかの RPC ファイル記述子への RPC 要求の到着を確認すると、svc_getreq_poll() が呼び出されます。


注 -

関数 _rpc_dtbsize()_rpc_select_to_poll() は、SVID の一部ではありませんが、libnsl ライブラリで使用できます。Solaris 以外でも実行できるように、これらの関数を作成するために、関数の仕様を説明します。


int __rpc_select_to_poll(int fdmax, fd_set *fdset,
 														struct pollfd *pollset)

ビットフラグとして fd_set ポインタとチェックすべきビット数が指定されます。この関数内で、指定された pollfd 配列を RPC が使用するために初期化するようにします。RPC は、入力イベントだけをポーリングします。初期化された pollfd スロット数が返されます。

int __rpc_dtbsize()

この関数は、getrlimit() 関数を呼び出し、新しく作成された記述子にシステムが割り当てる最大値を決定します。結果は、効率化のためにキャッシュされます。

この節の SVID ルーチンについての詳細は、rpc_svc_calls(3NSL) および poll(2) のマニュアルページを参照してください。


例 4-20 svc_run( )poll( )

void
svc_run()
{
 int nfds;
	int dtbsize = __rpc_dtbsize();
	int i;
	struct pollfd svc_pollset[fd_setsize];
 
	for (;;) {
		/*
		 * 要求待ちするサーバー fd があるかどうかをチェック
 	 */
		nfds = __rpc_select_to_poll(dtbsize, &svc_fdset,
 	                            svc_pollset);
		if (nfds == 0)
 		break;	/* 要求待ちの fd がないので終了 */
 
		switch (i = poll(svc_pollset, nfds, -1)) {
		case -1:
	 	/*
			 * エラーが起こった場合は、poll() ではなく、シグナルハンドラなど
 		 *  外部イベントによるものと考えて、無視して継続する
			 */
		case 0:
			continue;
		default:
	 	svc_getreq_poll(svc_pollset, i);
		}
	}
}
 

ブロードキャスト RPC

RPC のブロードキャストが要求されると、メッセージはネットワーク上の rpcbind デーモンに送られます。要求されたサービスが登録されている rpcbind デーモンは、その要求をサーバーに送ります。ブロードキャスト RPC と通常の RPC 呼び出しとの主な相違点を次に示します。

例 4-21 では、rpc_broadcast() の使用方法を示し、引数を説明します。


例 4-21 RPC ブロードキャスト

/*
 * bcast.c: RPC ブロードキャストの使用例
 */
 
#include <stdio.h>
#include <rpc/rpc.h>
 
main(argc, argv)
 int argc;
	char *argv[];
{
	enum clnt_stat rpc_stat;
	rpcprog_t prognum;
	rpcvers_t vers;
	struct rpcent *re;
 
	if(argc != 3) {
		fprintf(stderr, "usage : %s RPC_PROG VERSION¥n", argv[0]);
		exit(1);
	}
	if (isdigit( *argv[1]))
		prognum = atoi(argv[1]);
	else {
		re = getrpcbyname(argv[1]);
		if (! re) {
			fprintf(stderr, "Unknown RPC service %s¥n", argv[1]);
 		exit(1);
		}
		prognum = re->r_number;
 }
	vers = atoi(argv[2]);
	rpc_stat = rpc_broadcast(prognum, vers, NULLPROC, xdr_void,
	           (char *)NULL, xdr_void, (char *)NULL, bcast_proc,
NULL);
	if ((rpc_stat != RPC_SUCCESS) && (rpc_stat != RPC_TIMEDOUT)) {
	 fprintf(stderr, "broadcast failed: %s¥n",
		         clnt_sperrno(rpc_stat));
 	exit(1);
	}
	exit(0);
}
 
 

例 4-22 の関数 bcast_proc() では、ブロードキャストに対する応答を収集します。通常は、最初の応答だけを取り出すか、応答をすべて収集します。bcast_proc() は、応答を返したサーバーの IP アドレスを表示します。この関数は FALSE を返して応答の収集を続け、RPC クライアントコードはタイムアウトになるまでブロードキャストを再送信し続けます。


例 4-22 ブロードキャストへの応答の収集

bool_t
bcast_proc(res, t_addr, nconf)
 void *res;									/* 応答なし */
	struct t_bind *t_addr;					/* 応答したアドレス */
	struct netconfig *nconf;
{
 register struct hostent *hp;
	char *naddr;
 
	naddr = taddr2naddr(nconf, &taddr->addr);
	if (naddr == (char *) NULL) {
		fprintf(stderr,"Responded: unknown¥n");
 } else {
		fprintf(stderr,"Responded: %s¥n", naddr);
 	free(naddr);
	}
	return(FALSE);
} 

TRUE が返されるとブロードキャストは終了し、rpc_broadcast() は正常終了します。FALSE が返された場合は、次の応答を待ちます。数秒間待ってから、要求が再びブロードキャストされます。応答が返されない場合は、rpc_broadcast()RPC_TIMEDOUT を返します。

バッチ処理

RPC の設計方針では、クライアントは呼び出しメッセージを送信して、サーバーがそれに応答するのを待ちます。すなわち、サーバーが要求を処理する間、クライアントは停止していることになります。これは、クライアントが各メッセージへの応答を待つ必要がないときには非効率です。

RPC のバッチ処理を使用すると、クライアントは非同期に処理を進めることができます。RPC メッセージは呼び出しパイプラインに入れてサーバーに送られます。バッチ処理では次のことが必要になります。

サーバーはそれぞれの呼び出しに対しては応答しないので、クライアントは、サーバーが前の呼び出しを処理している間に平行して次の呼び出しを送信できます。トランスポートは複数の呼び出しメッセージをバッファリングし、システムコール write() で一度にサーバーに送信します。このため、プロセス間通信のオーバヘッドが減少し、一連の呼び出しに要する総時間が短縮されます。クライアントは終了前に、パイプラインをフラッシュする呼び出しをバッチにしないで実行します。

例 4-23 には、バッチ処理を使用しないクライアント側プログラムを示します。文字配列 buf を走査して文字列を順に取り出し、1 つずつサーバーに送信します。


例 4-23 バッチ処理を使用しないクライアントプログラム

#include <stdio.h>
#include <rpc/rpc.h>
#include "windows.h"
 
main(argc, argv)
 int argc;
	char **argv;
{
	struct timeval total_timeout;
	register CLIENT *client;
 enum clnt_stat clnt_stat;
	char buf[1000], *s = buf;
 
	if ((client = clnt_create( argv[1], WINDOWPROG, WINDOWVERS,
	 						"circuit_v")) == (CLIENT *) NULL) {
		clnt_pcreateerror("clnt_create");
 	exit(1);
	}
 
	total_timeout.tv_sec = 20;
	total_timeout.tv_usec = 0;
	while (scanf( "%s", s ) != EOF) {
		if (clnt_call(client, RENDERSTRING, xdr_wrapstring, &s,
		   xdr_void, (caddr_t) NULL, total_timeout) != RPC_SUCCESS) {
			clnt_perror(client, "rpc");
			exit(1);
 	}
	}
 
	clnt_destroy( client );
 exit(0);
}

例 4-24 には、このクライアントプログラムでバッチ処理を使用する場合を示します。各文字列の送信には応答を待たず、サーバーからの終了応答だけを待ちます。


例 4-24 バッチ処理を使用するクライアントプログラム

#include <stdio.h>
#include <rpc/rpc.h>
#include "windows.h"
 
main(argc, argv)
 int argc;
	char **argv;
{
	struct timeval total_timeout;
	register CLIENT *client;
 enum clnt_stat clnt_stat;
	char buf[1000], *s = buf;
 
	if ((client = clnt_create( argv[1], WINDOWPROG, WINDOWVERS,
	 							"circuit_v")) == (CLIENT *) NULL) {
		clnt_pcreateerror("clnt_create");
 	exit(1);
	}
	timerclear(&total_timeout);
 while (scanf("%s", s) != EOF)
		clnt_call(client, RENDERSTRING_BATCHED, xdr_wrapstring,
		           &s, xdr_void, (caddr_t) NULL, total_timeout);
	/* ここでパイプラインをフラッシュ*/
	total_timeout.tv_sec = 20;
	clnt_stat = clnt_call(client, NULLPROC, xdr_void,
          (caddr_t) NULL, xdr_void, (caddr_t) NULL,
total_timeout);
 if (clnt_stat != RPC_SUCCESS) {
		clnt_perror(client, "rpc");
 	exit(1);
	}
	clnt_destroy(client);
 exit(0);
}

例 4-25 には、バッチ処理を使用した場合のサーバーのディスパッチ部分を示します。サーバーは、メッセージを送信しないので、クライアント側は、失敗に気付きません。


例 4-25 バッチ処理を行うサーバー

#include <stdio.h>
#include <rpc/rpc.h>
#include "windows.h"
 
void
windowdispatch(rqstp, transp)
	struct svc_req *rqstp;
	SVCXPRT *transp;
{
 char    *s = NULL;
 
	switch(rqstp->rq_proc) {
 	case NULLPROC:
			if (!svc_sendreply( transp, xdr_void, NULL))
 			fprintf(stderr, "can't reply to RPC call¥n");
			return;
 	case RENDERSTRING:
			if (!svc_getargs( transp, xdr_wrapstring, &s)) {
				fprintf(stderr, "can't decode arguments¥n");
 			/* 呼び出し側にエラーを通知 */
				svcerr_decode(transp);
 			break;
			}
			/* 文字列 s を処理するコード */
 		if (!svc_sendreply( transp, xdr_void, (caddr_t) NULL))
	 		fprintf( stderr, "can't reply to RPC call¥n");
			break;
 	case RENDERSTRING_BATCHED:
			if (!svc_getargs(transp, xdr_wrapstring, &s)) {
				fprintf(stderr, "can't decode arguments¥n");
 			/* プロトコルエラーのため何も返さない */
				break;
			}
 		/* 文字列 s を処理するコード。ただし応答はしない。 */
			break;
 	default:
			svcerr_noproc(transp);
			return;
 }
	/* 引数の復号化で割り当てた文字列を解放 */
	svc_freeargs(transp, xdr_wrapstring, &s);
}

バッチ処理のパフォーマンス

バッチ処理によるパフォーマンスの向上を調べるために、例 4-23例 4-25で 25144 行のファイルを処理しました。このサービスは、ファイルの各行を引き渡すだけの簡単なサービスです。バッチ処理を使用した方が、使用しない場合の 4 倍の速さで終了しました。

認証

この章でこれまでに示した例では、呼び出し側は自分自身の ID をサーバーに示さず、サーバーも呼び出し側の ID を要求しませんでした。ネットワークサービスによっては、ネットワークファイルシステムのように、呼び出し側の ID が要求される場合があります。『Solaris のシステム管理』を参照して、この節で説明したいずれかの認証の方法を実行してください。

RPC のクライアントとサーバーを作成するときにさまざまなトランスポートを指定できるように、RPC クライアントにもさまざまなタイプの認証メカニズムを採用できます。RPC の認証サブシステムは端点が開かれているので、認証はさまざな使用法がサポートされます。認証プロトコルは、付録 B 「RPC プロトコルおよび言語の仕様」 で詳細に定義されています。

RPC が現在サポートしている認証タイプを 表 4-7 に示します。

表 4-7 RPC が現在サポートしている認証タイプ

AUTH_NONE

デフォルト。認証は実行されない 

AUTH_SYS

UNIX オペレーティングシステムのプロセスアクセス権を基にした認証タイプ 

AUTH_SHORT

サーバーによっては効率向上のため AUTH_SYS の代わりに AUTH_SHORT を使用できる。AUTH_SYS 認証を使用するクライアントプログラムは、サーバーからの AUTH_SHORT 応答ベリファイアを受信できる。詳細は、付録 B 「RPC プロトコルおよび言語の仕様」 を参照

AUTH_DES

DES 暗号化技法を基にした認証タイプ 

AUTH_KERB

DES フレームワークを基にした Version 5 Kerberos 認証形式

呼び出し側が次の方法で RPC クライアントハンドルを新規作成する場合を考えます。

clnt = clnt_create(host, prognum, versnum, nettype);

この場合対応するクライアント作成ルーチンが次のように認証ハンドルを設定します。

clnt->cl_auth = authnone_create();
 
 

新たな認証インスタンスを作成するときは、auth_destroy(clnt->cl_auth) を使用して現在のインスタンスを破棄します。この操作はメモリーの節約のために必要です。

サーバー側では、RPC パッケージがサービスディスパッチルーチンに、任意の認証スタイルが結合されている要求を渡します。サービスディスパッチルーチンに渡された要求ハンドルには、rq_cred という構造体が入っています。その構成は、認証資格タイプを示すフィールドを除いて、ユーザーから隠されています。

/*
 * 認証データ
 */
struct opaque_auth {
   enum_t    oa_flavor;		/* 資格スタイル */
   caddr_t   oa_base;			/* より詳細な認証データのアドレス */
   u_int     oa_length; 	/* 最大 MAX_AUTH_BYTES まで */
};

RPC パッケージでは、サービスディスパッチルーチンに対して次のことを保証しています。

AUTH_SYS タイプの認証

クライアント側で AUTH_SYS (旧バージョンでは AUTH_UNIX) タイプの認証を使用するには、RPC クライアントハンドルの作成後に clnt->cl_auth を次のように設定します。

clnt->cl_auth = authsys_create_default();

以降は、この clnt を使用した RPC 呼び出しでは、clnt とともに例 4-26 に示す資格 - 認証構造体が渡されます。


例 4-26 AUTH_SYS タイプの資格 - 認証構造体

/*
 * AUTH_SYS タイプの資格
 */
struct authsys_parms {
	u_long aup_time;					/* 資格作成時刻 */
 char *aup_machname;				/* クライアント側のホスト名 */
	uid_t aup_uid;	 				/* クライアント側の実効 uid */
	gid_t aup_gid;						/* クライアント側の現在のグループ ID */
	u_int aup_len;						/* aup_gids の配列の長さ */
 gid_t *aup_gids;					/* ユーザーが所属するグループの配列 */
};

rpc.broadcast では、デフォルトで AUTH_SYS タイプの認証になります。

例 4-27 には、手続きを使用し、ネットワーク上のユーザー数を返すサーバープログラムである RUSERPROC_1() を示します。認証の例として AUTH_SYS タイプの資格をチェックし、呼び出し側の uid16 の場合は要求に応じないようにしてあります。


例 4-27 認証データをチェックするサーバープログラム

nuser(rqstp, transp)
	struct svc_req *rqstp;
 SVCXPRT *transp;
{
	struct authsys_parms *sys_cred;
 uid_t uid;
	unsigned int nusers;
 
	/* NULLPROC の場合は認証データなし */
	if (rqstp->rq_proc == NULLPROC) {
 	if (!svc_sendreply( transp, xdr_void, (caddr_t) NULL))
		 fprintf(stderr, "can't reply to RPC call¥n");
		return;
 }
 
	/* ここで uid を取得 */
	switch(rqstp->rq_cred.oa_flavor) {
		case AUTH_SYS:
			sys_cred = (struct authsys_parms *) rqstp->rq_clntcred;
			uid = sys_cred->aup_uid;
 		break;
		default:
			svcerr_weakauth(transp);
 		return;
	}
	switch(rqstp->rq_proc) {
 	case RUSERSPROC_1:
			/* 呼び出し側が、この手続きの呼び出し資格を持っているかどうか確認 */
 		if (uid == 16) {
				svcerr_systemerr(transp);
 
	 		return;
			}
			/*
			 * ユーザー数を求めて変数 nusers に設定するコード
			 */
			if (!svc_sendreply( transp, xdr_u_int, &nusers))
				fprintf(stderr, "can't reply to RPC call¥n");
			return;
		default:
 		svcerr_noproc(transp);
			return;
	}
} 

このプログラムでは次の点に注意してください。

最後の点で重要なのは、RPC の認証パッケージとサービスの関係です。RPC は認証を処理しますが、個々のサービスへのアクセス制御は行いません。サービス自体でアクセス制御の方針を決め、それがプロトコル内で戻り値として反映されるようにしなければなりません。

AUTH_DES タイプの認証

AUTH_SYS タイプより厳しいセキュリティレベルが要求されるプログラムでは、AUTH_DES タイプの認証を使用します。AUTH_SYS タイプは AUTH_DES タイプに簡単に変更できます。たとえば、authsys_create_default() を使用する代わりに、プログラムから authsys_create() を呼び出し、RPC 認証ハンドルを変更して目的のユーザー ID とホスト名を設定することができます。

AUTH_DES タイプの認証を使用するには、サーバー側とクライアント側の両方のホストで、keyserv() デーモンと NIS また NIS+ ネームサービスが実行されている必要があります。また、両方のホスト上のユーザーに対してネットワーク管理者が割り当てた公開鍵 / 秘密鍵ペアが、publickey() のデータベースに入っていなければなりません。ユーザーは keylogin() のコマンドを実行して自分の秘密鍵を暗号化しておく必要があります。通常は、ログインパスワードと Secure RPC パスワードが同一の場合には、これを login() で行います。

AUTH_DES タイプの認証を使用するには、クライアントが認証ハンドルを正しく設定しなければなりません。その例を次に示します。

cl->cl_auth = authdes_seccreate(servername, 60, server,
 					       (char *)NULL);
 

最初の引数は、サーバープロセスのネットワーク名か、サーバープロセスの所有者のネット名です。サーバープロセスは通常 root プロセスで、次の関数呼び出しでネット名を得ることができます。

char servername[MAXNETNAMELEN];
host2netname(servername, server, (char *)NULL);

servername は受信文字列へのポインタで、server はサーバープロセスが実行されているホスト名です。サーバープロセスがスーパーユーザー以外のユーザーから起動されている場合は、次のように user2netname() を呼び出します。

char servername[MAXNETNAMELEN];
user2netname(servername, serveruid(), (char *)NULL);

serveruid() はサーバープロセスのユーザー id です。どちらの関数も最後の引数は、サーバーを含むドメイン名です。NULL を指定すると、ローカルドメイン名が使用されます。

authdes_seccreate() の第 2 引数では、このクライアントの資格の存在時間 (ウィンドウとも呼ばれる) を指定します。この例では 60 秒が指定されているので、この資格はクライアント側が RPC 呼び出しを行なってから、60 秒間で失われます。プログラムから再びこの資格を使用しようとしても、サーバー側の RPC サブシステムは、資格がすでに失われていることを知って、資格を失ったクライアントからの要求に答えません。また資格の存在時間中に別のプログラムがその資格を再使用しようとしても拒否されます。サーバー側の RPC サブシステムが最近作成された資格を保存していて、重複して使用できないようにするためです。

authdes_seccreate() の第 3 引数は、クロックを同期させる timehost 名です。AUTH_DES タイプの認証を使用するには、サーバーとクライアントの時間が一致していなければなりません。この例では、サーバーに同期させています。(char *)NULL と指定すると同期しません。この指定は、クライアントとサーバーがすでに同期していることが確実な場合にだけ行なってください。

authdes_seccreate() の第 4 引数は、タイムスタンプとデータとを暗号化するための DES 暗号化キーへのポインタです。この例のように (char *)NULL と指定した場合は、ランダムキーが選択されます。このキーは、認証ハンドルの ah_key フィールドに入っています。

サーバー側はクライアント側より簡単です。例 4-27 のサーバーを AUTH_DES タイプの認証を使用するように変更したものを、例 4-28 に示します。


例 4-28 AUTH_DES タイプの認証を使用するサーバー

#include <rpc/rpc.h>
	...
 ...
nuser(rqstp, transp)
	struct svc_req *rqstp;
 SVCXPRT *transp;
{
	struct authdes_cred *des_cred;
 uid_t uid;
	gid_t gid;
	int gidlen;
 gid_t gidlist[10];
 
	/* NULLPROC の場合は認証データなし */
 if (rqstp->rq_proc == NULLPROC) {
		/* 元のプログラムと同じ */
 }
	/* ここで uid を取得 */
	switch(rqstp->rq_cred.oa_flavor) {
		case AUTH_DES:
			des_cred = (struct authdes_cred *) rqstp->rq_clntcred;
			if (! netname2user( des_cred->adc_fullname.name, &uid,
			                    &gid, &gidlen, gidlist)) {
				fprintf(stderr, "unknown user: %s¥n",
 			         des_cred->adc_fullname.name);
				svcerr_systemerr(transp);
 			return;
			}
			break;
		default:
 		svcerr_weakauth(transp);
			return;
	}
 /* 以降は元のプログラムと同じ */

netname2user() ルーチンは、ネットワーク名 (またはユーザーの netname) をローカルシステム ID に変換することに注意してください。このルーチンはグループ ID も返します (この例では使用していません)。

AUTH_KERB 認証形式

SunOS 5.x は、klogin 以外の Kerberos V5 の大部分のクライアント側機能をサポートします。AUTH_KERBAUTH_DES と概念的に同じです。主要な違いは、DES がネットワーク名と暗号化された DES セッションキーを引き渡すのに対し、Kerberos は、暗号化されたサービスチケットを引き渡すことです。実装状態と相互運用性に影響を及ぼすその他の要因については、このあとで説明します。

詳細は、kerberos(3KRB) のマニュアルページと MIT Project Athena implementation of Kerberos の Steiner-Neuman-Shiller 報告書 [Steiner, Jennifer G., Neuman, Clifford, and Schiller, Jeffrey J. "Kerberos: An Authentication Service for Open Network Systems." USENIX Conference Proceedings, USENIX Association, カリフォルニア州, バークレー, June 1988.] を参照してください。MIT 文書には、athena-dist.mit.edu 上の FTP ディレクトリ、/pub/kerberos/doc または、ドキュメント URL、ftp://athena-dist.mit.edu/pub/kerberos/doc を使用して Mosaic でアクセスできます。

時刻の同期化

Kerberos はその資格が有効である時間ウィンドウの概念を使用します。クライアントまたはサーバーのクロックを制限しません。クライアントは、サーバーに指定されたウィンドウの時間を調整することによって、自身とサーバー間のずれを決定し、この違いを補う必要があります。具体的には、windowauthkerb_seccreate() に引き数として渡します。この場合、ウィンドウは変わりません。timehostauthkerb_seccreate() の引き数として指定されると、クライアント側は timehost から時刻を取得して、時刻の差異によってタイムスタンプを変更します。時刻を同期化するには、さまざまな方法が使用できます。詳細は、kerberos_rpc(3KRB) のマニュアルページを参照してください。

周知の名前

Kerberos ユーザーは、一次名、インスタンス、領域によって識別されます。RPC 認証コードは、領域とインスタンスを無視しますが、Kerberos ライブラリコードは無視しません。ユーザー名は、クライアントとサーバー間で同じであると仮定します。これによって、サーバーは一次名をユーザー ID 情報に変換することができます。周知の名前として 2 つの書式が使用されます (領域は省略されます)。

暗号化

Kerberos は、完全資格名 (チケットとウィンドウを含むもの) の送信時に暗号文ブロックチェイン (CBC: Cipher Block Chaining) モード、それ以外の場合は、電子コードブック (ECB: Electronic Code Book) モードを使用します。CBC と ECB は、DES 暗号化のための 2 つの方法です。詳細は、des_crypt(3) のマニュアルページを参照してください。セッションキーは、CBC モードに対する初期入力ベクトルとして使用されます。表記は次のようになります。

xdr_type(object)

これは、XDR が objecttype とみなして使用されることを示します。次のコードセクションの長さ (資格またはベリファイアのバイト数) を、4 バイト単位に丸めたサイズで表されます。完全資格名およびベリファイアは、次のようになります。

xdr_long(timestamp.seconds)
xdr_long(timestamp.useconds)
xdr_long(window)
xdr_long(window - 1)

セッションキーに等しい入力ベクトルを持つ CBC で暗号化を行うと、出力結果は次のような 2 つの DES 暗号化ブロックになります。

CB0
CB1.low
CB1.high

資格は、次のようになります。

xdr_long(AUTH_KERB)
xdr_long(length)
xdr_enum(AKN_FULLNAME)
xdr_bytes(ticket)
xdr_opaque(CB1.high) 

ベリファイアは、次のようになります。

xdr_long(AUTH_KERB)
xdr_long(length)
xdr_opaque(CB0)
xdr_opaque(CB1.low) 

ニックネーム交換によって、次のように生成されます。

xdr_long(timestamp.seconds)
xdr_long(timestamp.useconds) 

ニックネームは、ECB によって暗号化され、ECB0 と資格を得ます。

xdr_long(AUTH_KERB)
xdr_long(length)
xdr_enum(AKN_NICKNAME)
xdr_opaque(akc_nickname)
 

ベリファイアは、次のようになります。

xdr_long(AUTH_KERB)
xdr_long(length)
xdr_opaque(ECB0)
xdr_opaque(0) 

RPCSEC_GSS を使用した認証

上述の認証タイプ (AUTH_SYS、AUTH_DES、AUTH_KERB) は、1 つの決まった見方で同じように扱うことができます。このため、新しいネットワーキング階層、Generic Security Standard API (汎用セキュリティ規格 API)、すなわち GSS-API が追加されています。GSS-API のフレームワークでは、認証に加え次の 2 つの「サービス」が提供されています。


注 -

現在、GSS-API はまだ発表されていません。ただし、特定の GSS-API 機能は RPCSEC_GSS の機能 (この機能は「不透明な」(opaque 型) で扱うことができる) を通じて参照できます。プログラマはこれらの値に直接かかわる必要はありません。


RPCSEC_GSS API

RPCSEC_GSS API セキュリティタイプを使用すると、ONC RPC アプリケーションは、GSS-API の機能を利用することができます。RPCSEC_GSS は、次の図のように、GSS-API 階層の「最上部」に位置しています。

図 4-1 GSS-API と RPCSEC-GSS のセキュリティ階層

Graphic

RPCSEC-GSS のプログラミングインタフェースを使用する場合は、ONC RPC アプリケーションは以下の項目を指定できます。

メカニズム

セキュリティのパラダイム。各種セキュリティメカニズムでは、1 つまたは複数レベルのデータ保護と同時に、それぞれ異なる種類のデータ保護を提供します。この場合、GSS-API によってサポートされる任意のセキュリティメカニズムを指定します (Kerberos v5、RSA 公開鍵など)。

サービス

プライバシまたは完全性のいずれかを指定します (あるいはどちらも指定しない)。デフォルトは完全性です。この項目はメカニズムに依存しません。

QOP

保護の質。QOP により、プライバシまたは完全性サービスを実現するために使用する暗号化アルゴリズムのタイプが指定されます。各セキュリティメカニズムには、それに関連する 1 つまたは複数の QOP があります。

アプリケーションは、RPCSEC_GSS によって提供される関数により、QOP およびメカニズムのリストを入手できます (「その他の関数」を参照)。開発者は、メカニズムと QOP をハードコード化し、使用するアプリケーション内に埋め込むことは避けてください。そうすれば、新しい、または異なるメカニズムおよび QOP を使用するためにアプリケーションを修正する必要はありません。


注 -

これまでは、「セキュリティタイプ」と「認証タイプ」は同じものを表していました。RPCSEC_GSS の導入によって、「タイプ」は現在、多少異なる意味を持ちます。タイプには、認証とともにサービス (一貫性またはプライバシ) を含むことができますが、現在は RPCSEC_GSS が、これを実行できる唯一のタイプです。


RPCSEC_GSS を使用すると、ONC RPC アプリケーションは、他のタイプを使用して行う場合と同様に、ピアにセキュリティコンテキストを確立し、データを交換してこのコンテキストを破棄します。一度コンテキストが確立されると、アプリケーションは、送信したデータユニットごとに QOP およびサービスを変更できます。

RPCSEC_GSS データタイプを含む RPCSEC_GSS の詳細については、rpcsec_gss(3NSL) のマニュアルページを参照してください。

RPCSEC_GSS ルーチン

表 4-8 は、RPCSEC_GSS コマンドを要約したものです。この表では、各関数の個別の説明ではなく、RPCSEC_GSS 関数の全般的な概要を示しています。各関数の詳細については、該当するマニュアルページを参照するか、RPCSEC_GSS データ構造のリストなどの概要が記載された、rpcsec_gss(3NSL) のマニュアルページを参照してください。

表 4-8 RPCSEC_GSS Functions
 処理 関数 入力 出力
 セキュリティコンテキストの作成rpc_gss_seccreate() クライアントのハンドル、主体名、メカニズム、QOP、サービスタイプAUTH ハンドル
 コンテキストの QOP とサービスタイプの変更rpc_gss_set_defaults() 古い QOP とサービス 新しい QOP とサービス
 セキュリティの変換前に、データの最大サイズを示すrpc_gss_max_data_length() 伝送できる最大データサイズ 変換前の最大データサイズ
 セキュリティの変換前に、データの最大サイズを示すrpc_gss_svc_max_data_length() 伝送できる最大データサイズ 変換前の最大データサイズ
 表示するサーバーの主体名を設定するrpc_gss_set_svc_name() 主体名 , RPC プログラム、バージョン番号 正常に完了した場合は TRUE
 呼び出し元 (クライアント) の資格を得るrpc_gss_getcred()svc_req 構造へのポインタ UNIX 資格、RPCSEC_GSS 資格、cookie
 (ユーザーの作成した) コールバック関数を指定するrpc_gss_set_callback() コールバック関数へのポインタ 正常に完了した場合は TRUE
 固有のパラメータから主体名の RPCSEC_GSS 構造を作成するrpc_gss_get_principal_name() メカニズム、ユーザー名、マシン名、ドメイン名 RPCSEC_GSS 主体名の構造
 RPCSEC_GSS ルーチンが失敗した場合にエラーコードを得るrpc_gss_get_error()    RPCSEC_GSS エラー番号、該当する場合には errno
 インストールされているメカニズムの文字列を入手するrpc_gss_get_mechanisms()    有効なメカニズムのリスト
 有効な QOP 文字列を入手するrpc_gss_get_mech_info() メカニズム そのメカニズムの有効な QOP
 サポートされている RPCSEC_GSS の最大および最小のバージョン番号を得るrpc_gss_get_versions()    最大および最小のバージョン番号
 メカニズムが導入されているかどうかをチェックするrpc_gss_is_installed() メカニズム インストールされている場合は TRUE
 ASCII メカニズムを RPC オブジェクト識別子に変換するrpc_gss_mech_to_oid() メカニズム (文字列で) メカニズム (OID で)
 ASCII QOP を整数に変換するrpc_gss_qop_to_num() QOP (文字列で) QOP (整数で)

コンテキストの作成

コンテキストは、rpc_gss_seccreate() 呼び出しを使用して作成します。この関数では引数として次のものをとります。

この関数で、AUTH 認証ハンドルを返します。例 4-29 は、Kerberos v5 セキュリティメカニズムと完全性サービスを使用したコンテキストを作成する場合、rpc_gss_seccreate() がどのように使用されるかを示しています。


例 4-29 rpc_gss_seccreate()


 
CLIENT *clnt;						/* クライアントハンドル */
char server_host[] = "foo";
char service_name[] = "nfs@machine.eng.company.com";
char mech[] = "kerberosv5";
 
clnt = clnt_create(server_host, SERVER_PROG, SERV_VERS, "netpath");
clnt->clnt_auth = rpc_gss_seccreate(clnt, service_name, mech, ¥
															rpc_gss_svc_integrity, NULL, NULL, NULL);
 
. . .


この例 4-29 では、次の点に注意してください。

値の変更とコンテキストの破棄

コンテキストが設定されると、アプリケーションは伝送される個々のデータユニットの QOP およびサービス値を変更する必要がある場合があります。たとえば、プログラムのパスワードは暗号化したいがログイン名は暗号化したくない場合。これは、次のように rpc_gss_set_defaults() を使用すると実行できます。


例 4-30 rpc_gss_set_defaults()

 
rpc_gss_set_defaults(clnt->clnt_auth, rpc_gss_svc_privacy, qop);
 
. . .

この場合、セキュリティサービスはプライバシに設定されます (「コンテキストの作成」を参照)。

ここで、qop は新しいQOPの名前を表わす文字列へのポインタです。

コンテキストは、通常どおり、auth_destroy() を使用して破棄します。

QOP とサービスの変更に関する詳細は、rpc_gss_set_defaults(3NSL) のマニュアルページを参照してください。

主体名

セキュリティコンテキストを確立し、保持するには、次の 2 つのタイプの主体名が必要です。

サーバー主体名の設定

サーバーは、起動時に、そのサーバーを表わす主体名を指定する必要があります (1 つのサーバーが複数の主体として機能する場合もあります)。サーバー主体名の設定には、rpc_gss_set_svc_name() を使用します。


例 4-31 rpc_gss_set_svc_name()

char *principal, *mechanism;
u_int req_time;

principal = "nfs@eng.acme.com";
mechanism = "kerberos_v5";
req_time = 10000;		/* 資格の有効時間 */

rpc_gss_set_svc_name(principal, mechanism, req_time, SERV_PROG, SERV_VERS);

Kerberos は、req_time パラメータを無視します。他の認証システムでは、このパラメータを使用する場合があります。

詳細については、rpc_gss_set_svc_name(3NSL) のマニュアルページを参照してください。

クライアント主体名の作成

サーバーは、クライアントの主体名で稼動できなければなりません。たとえば、クライアントの主体名をアクセス制御リストと比較するため、またはクライアントの UNIX 資格を検出するため (このような資格が存在する場合) に必要です。このような主体名は、rpc_gss_principal_t 構造ポインタのフォームで維持されます (rpc_gss_principal_t の詳細については、rpcsec_gss(3NSL) のマニュアルページを参照してください)。サーバーが、受信した主体名を既知のエンティティの名前と比較する必要がある場合、サーバーは、この形式で rpc_gss_principal_t 主体名を生成できなければなりません。

rpc_gss_get_principal_name() 呼び出しでは、ネットワーク上で個人を識別するパラメータをいくつか入力し、rpc_gss_principal_t 構造ポインタとして主体名を生成します。


例 4-32 rpc_gss_get_principal_name()

rpc_gss_principal_t *principal;

rpc_gss_get_principal_name(principal, mechanism, name, node, domain);
. . .

rpc_gss_get_principal_name() への引数は、次のとおりです。

各セキュリティメカニズムには、別々の識別パラメータが必要です。たとえば、Kerberos V5 にはユーザー名が必ず必要です。また、オプションの場合に限り、修飾されたノード名とドメイン名が必要です (Kerberos 用語では、ホスト名と領域名)。

詳細については、rpc_gss_get_principal_name(3NSL) のマニュアルページを参照してください。

主体名の解放

主体名は、free() ライブラリコールを使用して解放します。

サーバーで資格を受信する

サーバーは、クライアントの資格を獲得できなければなりません。 例 4-33 で示すように、rpc_gss_getcred() 関数を使用すると、サーバーは UNIX 資格または RPCSEC_GSS 資格のいずれか (またはこの両方) を検索できます。これは、この関数が正常に終了した場合に設定された 2 つの引数によって実行されます。このうち1つは、呼び出し元の UNIX 資格が組み込まれた rpc_gss_ucred_t 構造 (存在する場合) へのポインタになります。

typedef struct {
    uid_t   uid;          /* ユーザー ID */
    gid_t   gid;          /* グループ ID */
    short   gidlen;       
    git_t   *gidlist;     /* グループのリスト */
} rpc_gss_ucred_t;

もう 1 つの引数は、次のような、rpc_gss_raw_cred_t 構造へのポインタです。

 
typedef struct {
		u_int 					version;															/* RPCSEC_GSS プログラムバージョン */
		char									*mechanism;
		char 								*qop;
		rpc_gss_principal_t		*client_principal;		/* クライアント主体名 */
		char									*svc_principal;			 								/* サーバー主体名 */
		rpc_gss_service_t			service; 											/* プライバシ、完全性 enum */
} rpc_gss_rawcred_t;
 
rpc_gss_principal_t 構造の解説と、作成方法については、「クライアント主体名の作成」を参照してください。 rpc_gss_rawcred_t にはクライアントとサーバーの両方の主体名が組み込まれているため、rpc_gss_getcred() は両方の名前を戻します。

例 4-33 は 1 つのサーバー側のディスパッチ手続きの例です。これにより、サーバーは呼び出し元の資格を入手します。この手続きでは、呼び出し元の UNIX 資格を入手してから、次に rpc_gss_rcred_t 引数内で検出された、メカニズム、QOP、サービスタイプを使用してユーザーの識別情報 (ID) を確認します。


例 4-33 資格の入手

static void server_prog(struct svc_req *rqstp, SVCXPRT *xprt)
{
		rpc_gss_ucred_t *ucred;
		rpc_gss_rawcred_t *rcred;
 
		if (rqst->rq_proq == NULLPROC) {
			svc_sendreply(xprt, xdr_void, NULL);
			return;
		}
		/*
		 * 他の全ての要求を認証する */
		 */
 
		switch (rqstp->rq_cred.oa_flavor) {
		case RPCSEC_GSS:
			/*
			 * 資格情報を取得する
			 */
			rpc_gss_getcred(rqstp, &rcred, &ucred, NULL);
			/*
			* 設定ファイルを参照してセキュリティパラメータを
			* 使用することでユーザーにアクセスが許可されている
			* ことを確認する
			*/
			if (!authenticate_user(ucred->uid, rcred->mechanism,
				rcred->qop, rcred->service)) {
				svcerr_weakauth(xprt);
				return;
			}
			break; 	/* ユーザーに許可する */
		default:
			svcerr_weakauth(xprt);
			return;
		} /* スイッチの終り */
 
		switch (rqstp->rq_proq) {
		case SERV_PROC1:
			. . .
		}
 
		/* 通常の要求処理 ; 応答を送る ... */
 
		return;
 
}

詳細については、rpc_gss_getcred(3NSL) のマニュアルページを参照してください。

Cookies

例 4-33 では、 rpc_gss_getcred()への最後の引数は、ユーザー定義の cookie です。このコンテキストの作成時にサーバーによってどのような値が指定されていても、このユーザー定義の値が戻されます。この cookie は 4 バイトの値で、そのアプリケーションに適したあらゆる方法で使用されます。RPC はこれを解釈しません。たとえば、 cookie は、コンテキストの起動元を示す構造へのポインタまたはインデックスになることができます。また、各要求ごとにこの値を計算する代わりに、サーバーがコンテキスト作成時にこの値を計算します。このため、要求の処理時間が削減されます。

コールバック

これ以外に cookie が使用される場所は、コールバックです。サーバーは、rpc_gss_set_callback() 関数を使用することにより、(ユーザー定義の) コールバックを指定してコンテキストが最初に使用された時を認知できます。コールバックは、コンテキストが指定されたプログラムとバージョン用に確立されたあとに、そのコンテキストがデータ交換に最初に使用された時に呼び出されます。

ユーザー定義のコールバックルーチンは、以下のような形式になります。

2 番めと 3 番めの引数 deleggss_context は、GSS-API データタイプで、現在はまだ公開されていません。そのため、コールバック関数はこれらを無視します。簡単に説明すると、プログラムが GSS-API オペレーションをこのコンテキスト上で実行する必要がある場合、すなわち受信条件のテストをする場合、deleg は代表されるピアの識別情報になり、一方 gss_context は GSS-API コンテキストへのポインタになります。cookie 引数については、すでに説明しました。

lock 引数は、以下のように rpc_gss_lock_t 構造へのポインタです。

 
typedef struct {
 	bool_t 						locked;
		rpc_gss_rawcred_t		*raw_cred;
} rpc_gss_lock_t;
このパラメータを使用すると、サーバーはセッションに対し強制的に特定の QOP とサービスを実行できます。例 4-33 に記載したように、QOP とサービスは、rpc_gss_rawcred_t 構造内で検出できます。 (サーバーは、サービスと QOP の値を変更する必要はありません。) ユーザー定義のコールバックが呼び出されると、locked フィールドは FALSE に設定されます。サーバーが、lockedTRUE に設定すると、QOP とサービスの値が、rpc_gss_rawcred_t 構造内の値と一致する要求だけが受理されます。

詳細は、 rpc_gss_set_callback(3NSL) のマニュアルページを参照してください。

最大データサイズ

rpc_gss_max_data_length()rpc_gss_svc_max_data_length() の 2 つの関数は、1 つのデータが、セキュリティ測度によって変換され「ワイヤを通じて」送信される前に、そのデータの大きさを判別する場合に便利です。つまり、暗号化などのセキュリティ変換により、通常、伝送される 1 つのデータのサイズは変更されます (通常は、大きくなる)。データが使用できるサイズ以上に大きくならないように、これら 2 つの関数 (前者はクライアント側バージョンで、後者はサーバー側バージョン) により、指定されたトランスポートの変換前の最大サイズが戻されます。

詳細については、rpc_gss_max_data_length(3NSL)rpc_gss_svc_max_data_length(3NSL) のマニュアルページを参照してください。

その他の関数

関数の中には、導入されたセキュリティシステムに関する情報を入手する場合に使用できるものもあります。

これらの関数を使用することによって、プログラマは、アプリケーション内のセキュリティパラメータのハードコード化を避けることができます (RPCSEC_GSS 関数については、表 4-8rpcsec_gss(3NSL) のマニュアルページを参照)。

関連ファイル

RPCSEC_GSS は各種のファイルを使用して情報を保存します。

gsscred テーブル

サーバーが要求に関連するクライアントの資格を検索すると、サーバーはクライアントの主体名 (rpc_gss_principal_t 構造ポインタの形式)、またはクライアントのローカル UNIX 資格 (UID) のいずれかを入手できます。NFS 要求などのサービス では、アクセス検査に必要なローカル UNIX 資格が必要ですが、他の資格は必要ありません。つまり、これらのサービスでは、たとえば主体名は、rpc_gss_principal_t 構造として直接、独自のアクセス制御リスト内に格納できるからです。


注 -

クライアントのネットワーク資格 (その主体名) とローカル UNIX 資格間の対応は自動的に行われません。これは、ローカルのセキュリティ管理者が明示的に設定する必要があります。


gsscred ファイルには、クライアントの UNIX 資格とネットワーク(たとえば、Kerberos V5) 資格の両方が入っています。後者は、rpc_gss_principal_t 構造の Hex-ASCII 表示です。これには、XFN を通じてアクセスするため、このテーブルは、ファイル、NIS、NIS+、あるいは XFN によってサポートされる将来のネームサービス上に導入できます。XFN 階層では、このテーブルは this_org_unit/service/gsscred として表示されます。 gsscred テーブルは、gsscred ユーティリティとともに保持されます。このユーティリティを使用すると、管理者はユーザーやメカニズムの追加および削除が行えます。

/etc/gss/qop と /etc/gss/mech

便宜上、RPCSEC_GSS では、メカニズムと保護の質 (QOP) パラメータを表示するためにリテラルの文字列を使用します。ただし、基本的なメカニズム自体では、メカニズムをオブジェクト識別子として、QOP は 32 ビット整数として表示する必要があります。また、各メカニズムごとに、そのメカニズムのサービスを実現する共有ライブラリを指定する必要があります。

/etc/gss/mech ファイルには、システム上に導入されたすべてのメカニズムに関する情報、メカニズム名 (ASCII 形式)、メカニズムの ODI、このメカニズムによって使用できるサービスを実現する共有ライブラリ、サービスを実現するカーネルモジュールが格納されます。次に例を示します。


kerberos_v5   1.2.840.113554.1.2.2    gl/mech_krb5.so gl_kmech_krb5

/etc/gss/qop ファイルには、導入されたすべてのメカニズム用に、各メカニズムがサポートするすべての QOP が、ASCII 文字列とそれに対応する 32 ビット整数の両方で格納されます。

/etc/gss/mech/etc/gss/qop は、両方とも指定されたシステムにセキュリティメカニズムが最初に導入されたときに作成されます。

カーネル内 RPC ルーチンは、通常、文字列にない値を使用してメカニズムと QOP を表すため、アプリケーションは、これらのカーネル内ルーチンを利用したい場合には、rpc_gss_mech_to_oid()rpc_gss_qop_to_num() 関数を使用してこれらのパラメータと同等の文字列にない値を入手します。

ポートモニタの使用

RPC サーバーは、inetdlisten のようなポートモニタから起動できます。ポートモニタは、要求が来ているかどうか監視し、要求が来ればそれに応じてサーバーを生成します。生成されたサーバープロセスには、要求を受信したファイル記述子 0 が渡されます。inetd の場合、サーバーは処理を終えるとすぐに終了するか、次の要求がくる場合に備えて指定された時間だけ待ってから終了します。

listen の場合は常に新たなプロセスが生成されるため、サーバーは応答を返したらすぐに終了しなければなりません。次に示す関数呼び出しでは、ポートモニタから起動されるサービスで使用する SVCXPRT ハンドルが作成されます。

transp = svc_tli_create(0, nconf, (struct t_bind *)NULL, 0, 0)

ここで、nconf は要求を受信したトランスポートの netconfig 構造体です。

サービスはポートモニタによりすでに rpcbind で登録されているので、登録する必要はありません。ただし、サービス手続きは次のように svc_reg() を呼び出して登録しなければなりません。

svc_reg(transp, PROGNUM, VERSNUM, dispatch,(struct netconfig *)NULL)

ここでは netconfig 構造体として NULL を渡し、svc_reg()rpcbind を呼び出してサービスを登録しないようにしています。


注 -

rpcgen が生成したサーバー側スタブプログラムを調べて、これらのルーチンの呼び出し順序を確認してください。


接続型トランスポートの場合は、次のルーチンにより下位レベルインタフェースが提供されます。

transp = svc_fd_create(0, recvsize, sendsize);

最初の引数ではファイル記述子 0 を指定します。recvsizesendsize には、適当なバッファサイズを指定できます。どちらの引数も 0 とすると、システムのデフォルト値が使用されます。自分で監視を行わないアプリケーションサーバーの場合は、svc_fd_create() を使用します。

inetd の使用

/etc/inet/inetd.conf のエントリ形式は、ソケットサービス、TLI サービス、RPC サービスによってそれぞれ異なります。RPC サービスの場合の inetd.conf のエントリ形式は次のようになります。

rpc_prog/vers endpoint_type rpc/proto flags user pathname args 

各エントリの内容を次に示します。

表 4-9 RPC inetd サービス

rpc_prog/vers

RPC プログラム名に / とバージョン番号 (またはバージョン番号の範囲) を付けたもの 

endpoint_type

dgram (非接続型ソケット)、stream (接続型ソケット)、tli (TLI 端点) のどれか

proto

サポートされているトランスポートすべてを意味する *、nettype、netid のどれか。または、nettype と netid をコンマで区切ったリスト 

flags

wait または nowait のどちらか

user

有効な passwd データベースに存在しているユーザー

pathname

サーバーデーモンへのフルパス名 

args

デーモンの呼び出し時に渡される引数 

エントリの例を次に示します。

rquotad/1 tli rpc/udp wait root /usr/lib/nfs/rquotad rquotad
 

inetd についての詳細は、inetd.conf(4) のマニュアルページを参照してください。

リスナの使用

次に示すように pmadm を使用して RPC サービスを追加します。


pmadm -a -p pm_tag -s  svctag -i id -v vers  ¥
 
	-m `nlsadmin -c command -D -R prog:vers`

引数の -a はサービスの追加を意味します。-p pm_tag ではサービスへのアクセスを提供するポートモニタに結合したタグを指定します。-s svctag はサーバーの ID コードです。-i id はサービス svctag に割り当てられた /etc/passwd 内のユーザー名です。-v vers はポートモニタのデータベースファイルのバージョン番号です。-m ではサービスを呼び出す nlsadmin コマンドを指定します。nlsadmin コマンドには引数を渡すことができます。たとえば、rusersd という名前の遠隔プログラムサーバーのバージョン 1 を追加する場合は、pmadm コマンドは次のようになります。


# pmadm -a -p tcp -s rusers -i root -v 4 ¥
 
-m `nlsadmin -c /usr/sbin/rpc.ruserd -D -R 100002:1`

このコマンドでは、root パーミッションが指定され、listener データベースファイルのバージョン 4 でインストールされ、TCP トランスポート上で使用可能になります。pmadm の引数やオプションは複雑であるため、RPC サービスはコマンドスクリプトでもメニューシステムでも追加できます。メニューシステムを使用するには、sysadm ports と入力して、port_services オプションを選択します。

サービスを追加した場合は、その後リスナを再初期化してサービスを利用可能にしなければなりません。そのためには、次のようにリスナを一度止めてから再起動します (このとき rpcbind が実行されていなければならないことに注意してください)。

# sacadm -k -p pmtag
# sacadm -s -p pmtag

リスナプロセスの設定などについての詳細は、listen(1M)pmadm(1M)sacadm(1M)sysadm(1M) のマニュアルページと『Solaris のシステム管理 (第 3 巻)』を参照してください。

サーバーのバージョン

一般に、プログラム PROG の最初のバージョンは PROGVERS_ORIG とし、最新バージョンは PROGVERS と命名します。プログラムのバージョン番号は続き番号で割り当てなければなりません。バージョン番号に飛ばされた番号があると、検索したときに定義済みのバージョン番号を探し出せないようなことが起こります。

プログラムのバージョン番号は、プログラムの所有者以外は決して変更しないでください。自分が所有していないプログラムのバージョン番号を追加したりすると、そのプログラムの所有者がバージョン番号を追加するときに重大な問題が起こります。バージョン番号の登録やご質問はご購入先へお問い合わせ下さい。

ruser プログラムの新バージョンが、int ではなく unsigned short を返すように変更されたとします。新バージョンの名前を RUSERSVERS_SHORT とすると、新旧の 2 つのバージョンをサポートするサーバーは二重登録することになります。次のように、どちらの登録でも同じサーバーハンドルを使用します。


例 4-34 同一ルーチンの 2 つのバージョンのためのサーバーハンドル

if (!svc_reg(transp, RUSERSPROG, RUSERSVERS_ORIG, 
 					nuser, nconf))
{
	fprintf(stderr, "can't register RUSER service¥n");
	exit(1);
}
if (!svc_reg(transp, RUSERSPROG, RUSERSVERS_SHORT, nuser,
			 		nconf)) {
	fprintf(stderr, "can't register RUSER service¥n");
 exit(1);
}

次のように、1 つの手続きで両バージョンを実行できます。


例 4-35 両バージョンを使用するサーバー

void
nuser(rqstp, transp)
	struct svc_req *rqstp;
	SVCXPRT *transp;
{
 unsigned int nusers;
	unsigned short nusers2;
 switch(rqstp->rq_proc) {
		case NULLPROC:
	 	if (!svc_sendreply( transp, xdr_void, 0))
				fprintf(stderr, "can't reply to RPC call¥n");
			return;
		case RUSERSPROC_NUM:
			/*
			 * ユーザー数を求めて変数 nusers に設定するコード
			 */
		switch(rqstp->rq_vers) {
 		case RUSERSVERS_ORIG:
				if (! svc_sendreply( transp, xdr_u_int, &nusers))
					fprintf(stderr, "can't reply to RPC call¥n");
 			break;
			case RUSERSVERS_SHORT:
				nusers2 = nusers;
				if (! svc_sendreply( transp, xdr_u_short, &nusers2))
 				fprintf(stderr, "can't reply to RPC call¥n");
				break;
 	}
		default:
			svcerr_noproc(transp);
 		return;
	}
	return;
}

クライアントのバージョン

異なるホストでは RPC サーバーの異なるバージョンが実行されている可能性があるので、クライアントはさまざまなバージョンに対応できるようにしなければなりません。たとえば、あるサーバーでは旧バージョン RUSERSPROG(RUSERSVERS_ORIG) が実行されており、別のサーバーでは最新バージョン RUSERSPROG(RUSERSVERS_SHORT) が実行されているとします。

サーバーのバージョンがクライアント作成ルーチン clnt_call() で指定したバージョン番号と一致しない場合は、clnt_call() から RPCPROGVERSMISMATCH というエラーが返されます。サーバーがサポートしているバージョン番号を取り出して、正しいバージョン番号をもつクライアントハンドルを作成することもできます。そのためには、例 4-36 のルーチンを使用するか、clnt_create_vers() を使用します。詳細については、rpc(3NSL) のマニュアルページを参照してください。


例 4-36 クライアント側での RPC バージョン選択

main()
{
	enum clnt_stat status;
 u_short num_s;
	u_int num_l;
	struct rpc_err rpcerr;
	int maxvers, minvers;
	CLIENT *clnt;
 
	clnt = clnt_create("remote", RUSERSPROG, RUSERSVERS_SHORT,
			             "datagram_v");
	if (clnt == (CLIENT *) NULL) {
 	clnt_pcreateerror("unable to create client handle");
		exit(1);
 }
	to.tv_sec = 10;							/* タイムアウト値を設定 */
	to.tv_usec = 0;
 
	status = clnt_call(clnt, RUSERSPROC_NUM, xdr_void,
	                  (caddr_t) NULL, xdr_u_short, 
                  (caddr_t)&num_s, to);
	if (status == RPC_SUCCESS) {			/* 最新バージョン番号が見つかった場合 */
		printf("num = %d¥n", num_s);
 	exit(0);
	}
	if (status != RPC_PROGVERSMISMATCH) {		/* その他のエラー */
		clnt_perror(clnt, "rusers");
 	exit(1);
	}
	/* 指定したバージョンがサポートされていない場合 */
 clnt_geterr(clnt, &rpcerr);
	maxvers = rpcerr.re_vers.high; 	/* サポートされている最新バージョン */
	minvers = rpcerr.re_vers.low;		/* サポートされている最も古いバージョン */
	if (RUSERSVERS_SHORT < minvers || RUSERSVERS_SHORT > maxvers)
{
			                       /* サポート範囲内にない場合 */
		clnt_perror(clnt, "version mismatch");
		exit(1);
	}
	(void) clnt_control(clnt, CLSET_VERSION, RUSERSVERS_ORIG);
	status = clnt_call(clnt, RUSERSPROC_NUM, xdr_void,
			 (caddr_t) NULL, xdr_u_int, (caddr_t)&num_l, to);
	if (status == RPC_SUCCESS)
 		               /* 識別できるバージョン番号が見つかった場合 */
		printf("num = %d¥n", num_l);
	else {
		clnt_perror(clnt, "rusers");
 	exit(1);
	}
}
 

一時的な RPC プログラム番号の使用

場合によっては、動的に生成される RPC プログラム番号をアプリケーションが使用すると便利なことがあります。たとえば、コールバック手続きを実装する場合などです。コールバックでは、クライアントプログラムは通常、動的に生成される、つまり一時的な RPC プログラム番号を使用して RPC サービスを登録し、これを要求とともにサーバーに渡します。次にサーバーは一時的な RPC プログラム番号を使用してクライアントプログラムをコールバックし、結果を返します。クライアントの要求を処理するのにかなりの時間がかかり、クライアントが停止できない (シングルスレッドであると仮定して) 場合などには、この機構が必要になります。このような場合、サーバーはクライアントの要求を認識し、あとで結果とともにコールバックを行います。コールバックを使用する別の例としては、サーバーから定期的なレポートを生成する場合があります。クライアントは RPC 呼び出しを行い、報告を開始します。そしてサーバーはクライアントプログラムが提供する一時的な RPC プログラム番号を使用して、定期的にレポートとともにクライアントをコールバックします。

動的に生成される一時的な RPC 番号は、0x40000000 から 0x5fffffff の範囲です。次に示すルーチンは指定されるトランスポートタイプ用に、一時的な RPC プログラムに基づいてサービスを作成します。サービスハンドルと一時的な rpc プログラム番号が返されます。呼び出し側はサービスディスパッチルーチン、バージョンタイプ、トランスポートタイプを提供します。


例 4-37 一時的な RPC プログラム - サーバー側

SVCXPRT *
register_transient_prog(dispatch, program, version, netid)
	void (*dispatch)(); /* サービスディスパッチルーチン */
	rpcproc_t  *program;    /* 一時的な RPC 番号が返される */
 rpcvers_t version;     /* プログラムバージョン */
	char *netid;        /* トランスポート id */
{
	SVCXPRT  *transp;
 struct netconfig *nconf;
	rpcprog_t prognum;
 if ((nconf = getnetconfigent(netid)) == (struct netconfig
*)NULL)
 	return ((SVCXPRT *)NULL);
	if ((transp = svc_tli_create(RPC_ANYFD, nconf,
				(struct t_bind *)NULL, 0, 0)) == (SVCXPRT *)NULL) {
		freenetconfigent(nconf);
		return ((SVCXPRT *)NULL);
	}
	prognum = 0x40000000;
 while (prognum < 0x60000000 && svc_reg(transp, prognum,
version,
 								dispatch, nconf) == 0) {
		prognum++;
 }
	freenetconfigent(nconf);
	if (prognum >= 0x60000000) {
		svc_destroy(transp);
		return ((SVCXPRT *)NULL);
	}
	*program = prognum;
 return (transp);
}

マルチスレッド RPC プログラミング

このマニュアルには、Solaris でのマルチスレッドプログラミングについては説明していません。次の項目については、『マルチスレッドのプログラミング』を参照してください。

TI-RPC は、Solaris 2.4 以降のマルチスレッド RPC サーバーをサポートします。マルチスレッドサーバーと シングルスレッドのサーバーの違いは、マルチスレッドサーバーがスレッドの技術を使用して複数のクライアント要求を同時に処理することです。マルチスレッドサーバーの方が、高度なパフォーマンスと可用性を備えています。

このリリースで新規に使用可能なインタフェースについては、まず、「マルチスレッドサーバーの概要」から読んでください。

マルチスレッドクライアントの概要

マルチスレッド対応のクライアントプログラムでは、RPC 要求が出されるたびにスレッドを 1 つ作成することができます。複数スレッドが同一のクライアントハンドルを共有する場合は、RPC 要求を発行できるのは一度に 1 つのスレッドだけです。その他のすべてのスレッドは、未処理の要求が終了するまで待たなければなりません。これに対して、複数スレッドがそれぞれ固有のクライアントハンドルを使用して RPC 要求を出す場合には、複数の要求が同時に処理されます。図 4-2 は、異なるクライアントハンドルを使用するクライアント側の 2 つのスレッドから成るマルチスレッド対応クライアント環境でのタイミングの例を示したものです。

例 4-38 は、クライアント側でマルチスレッド rstat プログラムを実行する場合を示します。クライアントプログラムは各ホストに対してスレッドを作成します。スレッドはそれぞれ、固有のクライアントハンドルを作成し、指定のホストにさまざまな RPC 呼び出しを行なっています。クライアント側の各スレッドは異なるハンドルを使用して RPC 呼び出しを行うため、RPC 呼び出しは同時に実行されます。

図 4-2 異なるクライアントハンドルを使用する 2 つのクライアントスレッド (リアルタイム)

Graphic


注 -

RPC マルチスレッド対応アプリケーションを作成する場合は常に、スレッドライブラリをリンクしなければなりません。コンパイルコマンドで -lthread を指定して、スレッドライブラリを最後にリンクするようにしなければなりません。


次のように入力して 例 4-38 のプログラムを作成します。


$ cc rstat.c -lnsl -lthread


例 4-38 マルチスレッド rstat に対するクライアント

/* @(#)rstat.c 2.3 93/11/30 4.0 RPCSRC */
/*
 * w コマンドと同様の形式で、遠隔ホストのステータスを表示する簡単な
 * プログラム
 */
 
#include <thread.h>		/* スレッドインタフェースの定義 */
#include <synch.h>	/* 相互排他的ロックの定義 */
#include <stdio.h>
#include <sys/param.h>
#include <rpc/rpc.h>
#include <rpcsvc/rstat.h>
#include <errno.h>
 
mutex_t tty;					/* printf のための tty の制御 */
cond_t cv_finish;
int count = 0;
 
main(argc, argv)
int argc;
char **argv;
{
 int i;
	thread_t tid;
	void *do_rstat();
 
	if (argc < 2) {
		fprintf(stderr, "usage: %s ¥"host¥" [...]¥n", argv[0]);
		exit(1);
	}
 
	mutex_lock(&tty);
 
	for (i = 1; i < argc; i++) {
		if (thr_create(NULL, 0, do_rstat, argv[i], 0, &tid) < 0) {
			fprintf(stderr, "thr_create failed: %d¥n", i);
			exit(1);
		} else
 		fprintf(stderr, "tid: %d¥n", tid);
	}
 
	while (count < argc-1) {
		printf("argc = %d, count = %d¥n", argc-1, count);
		cond_wait(&cv_finish, &tty);
 
	}
 
	exit(0);
}
 
bool_t rstatproc_stats();
 
void *
do_rstat(host)
char *host;
{
 CLIENT *rstat_clnt;
 statstime host_stat;
	bool_t rval;
	struct tm *tmp_time;
	struct tm host_time;
 struct tm host_uptime;
	char days_buf[16];
 char hours_buf[16];
 
	mutex_lock(&tty);
 printf("%s: starting¥n", host);
	mutex_unlock(&tty);
 
	/* rstat クライアントハンドル */
	rstat_clnt = clnt_create(host, RSTATPROG, RSTATVERS_TIME,
							"udp");
 if (rstat_clnt == NULL) {
		mutex_lock(&tty);				/* ty の制御権を取得 */
		clnt_pcreateerror(host);
		count++;
 	cond_signal(&cv_finish);
		mutex_unlock(&tty);	/* tty の制御権を解放 */
 
		thr_exit(0);
 
	}
 
	rval = rstatproc_stats(NULL, &host_stat, rstat_clnt);
	if (!rval) {
		mutex_lock(&tty);	/* tty の制御権を取得 */
 	clnt_perror(rstat_clnt, host);
		count++;
 	cond_signal(&cv_finish);
		mutex_unlock(&tty);	/* tty の制御権を解放 */
 
		thr_exit(0);
 
	}
 
	tmp_time = localtime_r(&host_stat.curtime.tv_sec,
                       &host_time);
 
	host_stat.curtime.tv_sec = host_stat.boottime.tv_sec;
 
	tmp_time = gmtime_r(&host_stat.curtime.tv_sec,
                    &host_uptime);
 
	if (host_uptime.tm_yday != 0)
		sprintf(days_buf, "%d day%s, ", host_uptime.tm_yday,
 	         (host_uptime.tm_yday > 1) ? "s" : "");
 else
		days_buf[0] = '¥0';
 
	if (host_uptime.tm_hour != 0)
		sprintf(hours_buf, "%2d:%02d,",
 		host_uptime.tm_hour, host_uptime.tm_min);
 
	else if (host_uptime.tm_min != 0)
		sprintf(hours_buf, "%2d mins,", host_uptime.tm_min);
	else
 
	 hours_buf[0] = '¥0';
 
	mutex_lock(&tty); /* tty の制御権を取得 */
	printf("%s: ", host);
 printf(" %2d:%02d%cm up %s%s load average: %.2f %.2f %.2f¥n",
 	(host_time.tm_hour > 12)  ? host_time.tm_hour - 12
 
	 : host_time.tm_hour,
		host_time.tm_min,
		(host_time.tm_hour >= 12) ? 'p'
		: 'a',
		days_buf,
 	hours_buf,
		(double)host_stat.avenrun[0]/FSCALE,
 	(double)host_stat.avenrun[1]/FSCALE,
		(double)host_stat.avenrun[2]/FSCALE);
 count++;
	cond_signal(&cv_finish);
	mutex_unlock(&tty); /* tty の制御権を解放 */
	clnt_destroy(rstat_clnt);
 
	sleep(10);
 thr_exit(0);
}
 
/* クライアント側の MT rstat プログラムの実行 */
/* clnt_control() を使用してデフォルトのタイムアウトを変更可能 */
static struct timeval TIMEOUT = { 25, 0 };
 
bool_t
rstatproc_stats(argp, clnt_resp, clnt)
	void *argp;
	statstime *clnt_resp;
 CLIENT *clnt;
{
 
	memset((char *)clnt_resp, 0, sizeof (statstime));
	if (clnt_call(clnt, RSTATPROC_STATS,
 	(xdrproc_t) xdr_void, (caddr_t) argp,
		(xdrproc_t) xdr_statstime, (caddr_t) clnt_resp,
		TIMEOUT) != RPC_SUCCESS) {
 	return (FALSE);
 
	}
	return (TRUE);
}
 

マルチスレッドサーバーの概要

Solaris 2.4 より前のバージョンでは、RPC サーバーはシングルスレッドでした。つまり、クライアント側から要求が来るごとに処理していました。たとえば、2 つの要求を同時に受け取り、最初の処理に 30 秒、次の処理に 1 秒かかるとすると、2 つめの要求を出したクライアントは最初の処理が完了するまで待たなければなりません。これは、各 CPU が異なる要求を同時に処理するマルチプロセッササーバー環境を利用できず、他の要求がサーバーによって処理することができるのに 1 つの要求の I/O の完了を待っている状態が生じ、望ましいものではありません。

Solaris 2.4 以降 の RPC ライブラリでは、サービス開発者がエンドユーザーにより高いパフォーマンスを提供するマルチスレッドサーバーを作成できる機能を追加しました。サーバーのマルチスレッドの 2 つのモード、自動マルチスレッドモードとユーザー・マルチスレッド・モードは、TI-RPC でサポートされます。

自動モードでは、サーバーは、クライアント要求を受信するごとに新規スレッドを自動的に作成します。このスレッドは要求を処理し、応答してから終了します。ユーザーモードでは、サービス開発者が、入って来るクライアント要求を同時に処理するスレッドを作成、管理します。自動モードはユーザーモードより使用はしやすいのですが、ユーザーモードの方が特別な要件を必要とするサービス開発者に対して柔軟性があります。


注 -

RPC マルチスレッド対応アプリケーションを作成する場合は常に、スレッドライブラリをリンクしなければなりません。コンパイルコマンドで -lthread を指定して、スレッドライブラリを最後にリンクするようにしなければなりません。


サーバー側のマルチスレッドをサポートする呼び出しでは、rpc_control()svc_done() がサポートされています。これらの呼び出しによってサーバー側でマルチスレッド処理が行えるようになりました。rpc_control() 呼び出しがマルチスレッドモードを設定するために、自動モードとユーザーモードの両方で使用されます。サーバーが自動モードを使用する場合には、svc_done() を呼び出す必要はありません。ユーザーモードの場合には、サーバーが要求処理からのリソースを再要求できるようにするため、svc_done() は各クライアント要求が処理されてから呼び出されなければなりません。さらにマルチスレッド RPC サーバーは、svc_run() をマルチスレッド対応で呼び出さなければなりません。svc_getreqpoll()svc_getreqset() は、MT アプリケーション対応ではありません。


注 -

サーバープログラムが新規インタフェース呼び出しを行わない場合には、デフォルトのモードのシングルスレッドモードのままです。


サーバーが使用しているモードに関係なく、RPC サーバー手続きはマルチスレッド対応にしなければなりません。通常これは、すべての静的変数とグロール変数が mutex ロックで保護される必要がある、ということです。相互排他と他の同期 API は、synch.h で定義されます。さまざまな同期インタフェースのリストは、condition(3THR)rwlock(3THR)mutex(3THR) を参照してください。

図 4-3 は、マルチスレッドモードのどちらかで実行されるサーバーの実行タイミングを示します。

図 4-3 マルチスレッド RPC サーバーのタイミング図

Graphic

サービス・トランスポート・ハンドルの共有

サービス・トランスポート・ハンドル、SVCXPRT には、引き数を復号化するための領域と結果をコード化するための領域である 1 つのデータ領域があります。したがって、デフォルトでは、シングルスレッドモードであり、この構造は、これらの操作を行う関数を呼び出すスレッド間では自由に共有することはできません。ただし、サーバーが、マルチスレッド自動モードまたはユーザーモードにある場合には、この構造のコピーは、同時要求処理を可能にするために、サービスディスパッチ用のプログラムに引き渡されます。これらの状況では、ルーチンのマルチスレッド対応ではない一部のルーチンがマルチスレッド対応となります。特別に注意書きがない場合には、サーバーインタフェースは通常、マルチスレッド対応です。サーバー側のインタフェースについての詳細は、rpc_svc_calls(3NSL) のマニュアルページを参照してください。

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

マルチスレッド自動モードでは、RPC ライブラリはスレッドを作成し、管理することができます。サービス開発者が新規インタフェース呼び出し、rpc_control() を呼び出し、svc_run() を呼び出す前にサーバーをマルチスレッド自動モードにします。このモードでは、プログラマはサービス手続きがマルチスレッド対応であることを確認するだけで十分です。

rpc_control() の使用によって、アプリケーションでグローバル RPC 属性を設定できます。現在はサービス側の操作しかサポートしていません。 表 4-10 は、自動モード用に定義された rpc_control() 操作を示します。追加の情報については、rpc_control(3NSL) のマニュアルページを参照してください。

表 4-10 rpc_control() ライブラリルーチン

RPC_SVC_MTMODE_SET()

マルチスレッドモードの設定 

RPC_SVC_MTMODE_GET()

マルチスレッドの取得 

RPC_SVC_THRMAX_SET()

最大スレッド数の設定 

RPC_SVC_THRMAX_GET()

最大スレッド数の取得 

RPC_SVC_THRTOTAL_GET ()

現在アクティブなスレッドの合計数 

RPC_SVC_THRCREATES_GET()

RPC ライブラリ作成のスレッドの累積数 

RPC_SVC_THRERRORS_GET()

RPC ライブラリ内の thr_create エラー数


注 -

表 4-10 の get 演算は、RPC_SVC_MTMODE_GET() 以外はすべて、自動マルチスレッドモードにだけ適用されます。マルチスレッド・ユーザー・モードまたはデフォルトのシングル・スレッド・モードで使用する場合には、演算の結果は定義されません。


デフォルトでは、RPC ライブラリが一度に作成できるスレッドの最大数は 16 です。サーバーが 16 以上のクライアント要求を同時に処理する必要がある場合には、スレッドの最大数を指定して設定する必要があります。このパラメータは、サーバーによっていつでも設定することができ、これによってサーバー開発者はサーバーによって使用されるスレッドリソースの上限を設定できます。例 4-39 は、マルチスレッド自動モードに作成された RPC プログラムの例です。この例では、スレッドの最大数は 20 に設定されています。

マルチスレッドのパフォーマンスは、関数 svc_getargs() が、NULLPROCS 以外の手続きによって呼び出されるごとに、引き数 (この場合には xdr_void())がない場合でも改善されていきます。これはマルチスレッド自動モードとマルチスレッドユーザーモード両方の場合においてです。詳細は、rpc_svc_calls(3NSL) のマニュアルページを参照してください。

例 4-39 は、マルチスレッド自動モードでのサーバーを示したものです。


注 -

RPC マルチスレッド対応アプリケーションを作成する場合は常に、スレッドライブラリ内でリンクしなければなりません。コンパイルコマンドで -lthread を指定して、スレッドライブラリを最後にリンクするようにしなければなりません。


次のように入力して例 4-39 のプログラムを作成します。


$ cc time_svc.c -lnsl -lthread


例 4-39 マルチスレッド自動モードのサーバー

		#include <stdio.h>
		#include <rpc/rpc.h>
 	#include <synch.h>
		#include <thread.h>
 	#include "time_prot.h"
 
		void time_prog();
 
	 main(argc, argv)
		int argc;
		char *argv[];
 	{
		int transpnum;
		char *nettype;
 	int mode = RPC_SVC_MT_AUTO;
		int max = 20;      /* スレッド最大数を 20 に設定 */
 
	 if (argc > 2) {
	 	fprintf(stderr, "usage: %s [nettype]¥n", argv[0]);
			exit(1);
 	}
 
		if (argc == 2)
			nettype = argv[1];
		else
			nettype = "netpath";
 
	 if (!rpc_control(RPC_SVC_MTMODE_SET, &mode)) {
			printf("RPC_SVC_MTMODE_SET: failed¥n");
			exit(1);
		}
 	if (!rpc_control(RPC_SVC_THRMAX_SET, &max)) {
			printf("RPC_SVC_THRMAX_SET: failed¥n");
			exit(1);
		}
 	transpnum = svc_create( time_prog, TIME_PROG, TIME_VERS,
 			nettype);
 
		if (transpnum == 0) {
 		fprintf(stderr, "%s: cannot create %s service.¥n",
			argv[0], nettype);	
			exit(1);
		}
	 svc_run();
	}
 
	/*
  * サーバーのディスパッチプログラムです。RPC サーバーライブラリは、
	 * サーバーのディスパッチャルーチン time_prog () を実行するスレッドを
	 * 作成します。RPC ライブラリがスレッドを廃棄した後に行われます。
  */
 
	static void
	time_prog(rqstp, transp)
		struct svc_req *rqstp;
		SVCXPRT *transp;
 {
 
		switch (rqstp->rq_proc) {
 		case NULLPROC:
				svc_sendreply(transp, xdr_void, NULL);
 			return;
			case TIME_GET:
				dotime(transp);
 			break;
			default:
				svcerr_noproc(transp);
 			return;
		}
	}
	dotime(transp)
 SVCXPRT *transp;
	{
	
		struct timev rslt;
		time_t thetime;
	
 	thetime = time((time_t *)0);
		rslt.second = thetime % 60;
 	thetime /= 60;
		rslt.minute = thetime % 60;
 	thetime /= 60;
		rslt.hour = thetime % 24;
 	if (!svc_sendreply(transp, xdr_timev,(caddr_t) &rslt)) {
 		svcerr_systemerr(transp);
		}
	}

例 4-40 は、サーバーの time_prot.h ヘッダーファイルを示します。


例 4-40 マルチスレッド自動モード : time_prot.h ヘッダーファイル

		include <rpc/types.h>
 
	 struct timev {
			int second;
			int minute;
 		int hour;
		};
 
		typedef struct timev timev;
		bool_t xdr_timev();
 
	 #define TIME_PROG 0x40000001
		#define TIME_VERS 1
 	#define TIME_GET 1
 

マルチスレッド・ユーザー・モード

マルチスレッド・ユーザー・モードでは、RPC ライブラリはスレッドを作成しません。このモードは、基本的には、シングルスレッド、またはデフォルトのモードのように作動します。唯一の違いは、データ構造のコピー (サービス・ディスパッチ・ルーチンへのトランスポートサービスなど) をマルチスレッド対応に引き渡す点です。

RPC サーバー開発者は、スレッドライブラリ全体のスレッドの作成と管理に対する責任を持ちます。ディスパッチルーチンでは、サービス開発者は、手続きの実行を新規作成のまたは既存のスレッドに割り当てることができます。thr_create() API は、さまざまな属性を持つスレッドを作成するために使用されます。すべてのスレッドのライブラリインタフェースは、thread.h で定義されます。詳細は、pthread_create(3THR) のマニュアルページを参照してください。

このモードは、サーバー開発者に幅広い柔軟性を提供しています。スレッドは、サービス要件に応じたスタックサイズを持ちます。スレッドは限定されます。異なる手続きは、異なる特長を持つスレッドによって実行されます。サービス開発者は、サービスの一部をシングルスレッドで実行できます。また、特定のスレッドに固有のシグナル処理を行うこともできます。

自動モードの場合と同じように、rpc_control() ライブラリは、ユーザーモードに切り換える場合に使用されます。表 4-10 に示した rpc_control() 演算 (RPC_SVC_MTMODE_GET() 以外) は、マルチスレッド自動モードにだけ適用されます。マルチスレッド・ユーザー・モードまたはシングルスレッドのデフォルトモードで使用すると、演算の結果が定義できません。

ユーザーモードでのライブラリリソースの解放

マルチスレッド・ユーザー・モードでは、サービス手続きは、戻しの前に svc_done() を呼び出さなければなりません。svc_done() は、クライアント要求が指定のサービス・トランスポート・ハンドルに向けたサービスに割り当てたリソースを解放しなければなりません。この機能は、クライアント要求がサービスされた後、あるいは応答の送信を妨げたエラーまたは異常な状態の後に呼び出されます。svc_done() が呼び出された後に、サービス・トランスポート・ハンドルは、サービス手続きによって参照されるべきではありません。例 4-41 は、マルチスレッド・ユーザー・モードでのサーバーを示します。


注 -

svc_done() は、マルチスレッド・ユーザー・モード内でだけ呼び出すことができます。詳細は、rpc_svc_calls(3NSL) のマニュアルページを参照してください。



例 4-41 マルチスレッド・ユーザー・モード : rpc_test.h

	#define	SVC2_PROG 0x30000002
	#define	SVC2_VERS 1
	#define SVC2_PROC_ADD 1)
	#define SVC2_PROC_MULT 2
 
	struct intpair {
		u_short a;
		u_short b;
	};
 
	typedef struct intpair intpair;
 
	struct svc2_add_args {
		int argument;
		SVCXPRT *transp;
 };
 
	struct svc2_mult_args {
	 intpair mult_argument;
		SVCXPRT *transp;
	};
 
	extern bool_t xdr_intpair();
 
	#define NTHREADS_CONST 500

例 4-42 は、マルチスレッド・ユーザー・モードでのクライアントです。


例 4-42 マルチスレッド・ユーザー・モードでのクライアント

#define	 _REENTRANT
#include <stdio.h>
#include <rpc/rpc.h>
#include <sys/uio.h>
#include <netconfig.h>
#include <netdb.h>
#include <rpc/nettype.h>
#include <thread.h>
#include "rpc_test.h"
void *doclient();
int NTHREADS;
struct thread_info {
	thread_t client_id;
	int client_status;
};
struct thread_info save_thread[NTHREADS_CONST];
main(argc, argv)
 int argc;
	char *argv[];
{
	int index, ret;
	int thread_status;
	thread_t departedid, client_id;
	char *hosts;
	if (argc < 3) {
 	printf("Usage: do_operation [n] host¥n");
		printf("¥twhere n is the number of threads¥n");
		exit(1);
 } else
		if (argc == 3) {
			NTHREADS = NTHREADS_CONST;
 		hosts = argv[1];  /* live_host */
		} else {
 		NTHREADS = atoi(argv[1]);
			hosts = argv[2];
 	}
	for (index = 0; index < NTHREADS; index++){
 	if (ret = thr_create(NULL, NULL, doclient,
		(void *)  hosts, THR_BOUND, &client_id)){
			printf("thr_create failed: return value %d", ret);
			printf(" for %dth thread¥n", index);
 		exit(1);
		}
		save_thread[index].client_id = client_id;
	}
	for (index = 0; index < NTHREADS; index++){
		if (thr_join(save_thread[index].client_id, &departedid,	
		(void *)
		&thread_status)){
 		printf("thr_join failed for thread %d¥n",
			save_thread[index].client_id);
 		exit(1);
		}
		save_thread[index].client_status = thread_status;
	}
}
	void *doclient(host)
 char *host;
{
	struct timeval tout;
 enum clnt_stat test;
	int result = 0;
	u_short mult_result = 0;
	int add_arg;
	int EXP_RSLT;
 intpair pair;
	CLIENT *clnt;
	if ((clnt = clnt_create(host, SVC2_PROG, SVC2_VERS, "udp"
==NULL) {
		clnt_pcreateerror("clnt_create error: ");
		thr_exit((void *) -1);
	}
 tout.tv_sec = 25;
	tout.tv_usec = 0;
	memset((char *) &result, 0, sizeof (result));
	memset((char *) &mult_result, 0, sizeof (mult_result));
	if (thr_self() % 2){
 	EXP_RSLT = thr_self() + 1;
		add_arg = thr_self();
 	test = clnt_call(clnt, SVC2_PROC_ADD, (xdrproc_t) xdr_int,
 	(caddr_t) &add_arg, (xdrproc_t) xdr_int, (caddr_t) &result,
 	tout);
	} else {
		pair.a = (u_short) thr_self();
 	pair.b = (u_short) 1;
		EXP_RSLT = pair.a * pair.b;
 	test = clnt_call(clnt, SVC2_PROC_MULT, (xdrproc_t)
		xdr_intpair,
 	(caddr_t) &pair, (xdrproc_t) xdr_u_short,
		(caddr_t) &mult_result, tout);
		result = mult_result;
 }
	if (test != RPC_SUCCESS) {
		printf("THREAD: %d clnt_call hav
		thr_exit((void *) -1);
	};
 thr_exit((void *) 0);
}
 

例 4-43 は、マルチスレッド・ユーザー・モードのサーバー側を示します。マルチスレッドパフォーマンスは、関数 svc_getargs()NULLPROC 以外の各手続きに呼び出される場合は、引き数 (この場合には xdr_void) がなくても改善されます。これは、マルチスレッド自動モードモードとマルチスレッドユーザーモードにおいてです。詳細は、rpc_svc_calls(3NSL) のマニュアルページを参照してください。


注 -

RPC マルチスレッド対応アプリケーションを作成する場合は常に、スレッドライブラリ内でリンクしなければなりません。コンパイルコマンドで -lthread を指定して、スレッドライブラリを最後にリンクするようにしなければなりません。



例 4-43 マルチスレッド・ユーザー・モードのサーバー側

#define 	_REENTRANT
#include <stdio.h>
#include <rpc/rpc.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/uio.h>
#include <signal.h>
#include <thread.h>
#include "operations.h"
 
SVCXPRT *xprt;
void add_mult_prog();
void *svc2_add_worker();
void *svc2_mult_worker();
main(argc, argv)
	int argc;
 char **argv;
{
	int transpnum;
 char *nettype;
	int mode = RPC_SVC_MT_USER;
       if(rpc_control(RPC_SVC_MTMODE_SET,&mode) == FALSE){
                printf(" rpc_control is failed to set AUTO mode¥n");
                exit(0);
        }
	if (argc > 2) {
		fprintf(stderr, "usage: %s [nettype]¥n", argv[0]);
 	exit(1);
	}
	if (argc == 2)
 	nettype = argv[1];
	else
		nettype = "netpath";
 transpnum = svc_create(add_mult_prog, SVC2_PROG,
 	SVC2_VERS, nettype);
	if (transpnum == 0) {
		fprintf(stderr, "%s: cannot create %s service.¥n", argv[0],
 		nettype);
 	exit(1);
	}
	svc_run();
}
void add_mult_prog (rqstp, transp)
	struct svc_req *rqstp;
 SVCXPRT *transp;
{
	int argument;
 u_short mult_arg();
	intpair mult_argument;
 bool_t (*xdr_argument)();
	struct svc2_mult_args *sw_mult_data;
 struct svc2_add_args *sw_add_data;
	int ret;
 thread_t worker_id;
	switch (rqstp->rq_proc){
 	case NULLPROC:
			svc_sendreply(transp, xdr_void, (char *) 0);
			svc_done(transp);
			break;
 	case SVC2_PROC_ADD:
			xdr_argument = xdr_int;
 		(void) memset((char *) &argument, 0, sizeof (argument));
 		if (!svc_getargs(transp, xdr_argument,
			(char *) &argument)){
 			printf("problem with getargs¥n");
				svcerr_decode(transp);
 			exit(1);
			}
			sw_add_data = (struct svc2_add_args *)
			malloc(sizeof (struct svc2_add_args));
 		sw_add_data->transp = transp;
			sw_add_data->argument = argument;
			if (ret = thr_create(NULL, THR_MIN_STACK + 16 * 1024,
			svc2_add_worker, (void *) sw_add_data, THR_DETACHED,
 			printf("SERVER: thr_create failed:");
				printf(" return value %d", ret);
				printf(" for add thread¥n");
 			exit(1);
			}
			break;
	 case SVC2_PROC_MULT:
			xdr_argument = xdr_intpair;
 		(void) memset((char *) &mult_argument, 0,
			sizeof (mult_argument));
 		if (!svc_getargs(transp, xdr_argument,
			(char *) &mult_argument)){
 			printf("problem with getargs¥n");
				svcerr_decode(transp);
 			exit(1);
			}
			sw_mult_data = (struct svc2_mult_args *)
			malloc(sizeof (struct svc2_mult_args));
 		sw_mult_data->transp = transp;
			sw_mult_data->mult_argument.a = mult_argument.a;
			sw_mult_data->mult_argument.b = mult_argument.b;
 		if (ret = thr_create(NULL, THR_MIN_STACK + 16 * 1024,
		 svc2_mult_worker, (void *) sw_mult_data, THR_DETACHED,
			&worker_id)){
 			printf("SERVER: thr_create failed:");
				printf("return value %d", ret);
				printf("for multiply thread¥n");
 			exit(1);
 
			break;
		default:
 		svcerr_noproc(transp);
			svc_done(transp);
 		break;
	}
}
 
u_short mult_arg();
int add_one();
void *svc2_add_worker(add_arg)
struct svc2_add_args *add_arg;
{	int *result;
	bool_t (*xdr_result)();
	xdr_result = xdr_int;
	result = *malloc(sizeof (int));
	*result = add_one(add_arg->argument);
 if (!svc_sendreply(add_arg->transp, xdr_result,
	(caddr_t) result)){
		printf("sendreply failed¥n");
	 svcerr_systemerr(add_arg->transp);
		svc_done(add_arg->transp);
 	thr_exit((void *) -1);
	}
	svc_done(add_arg->transp);
 thr_exit((void *) 0);
}
void *svc2_mult_worker(m_arg)
struct svc2_mult_args *m_arg;
{
	u_short *result;
 bool_t (*xdr_result)();
	xdr_result = xdr_u_short;
 result = (u_short *) malloc(sizeof (u_short));
	*result = mult_arg(&m_arg->mult_argument);
 if (!svc_sendreply(m_arg->transp, xdr_result,
	(caddr_t) result)){
 	printf("sendreply failed¥n");
		svcerr_systemerr(m_arg->transp);
 	svc_done(m_arg->transp);
		thr_exit((void *) -1);
 }
	svc_done(m_arg->transp);
	thr_exit((void *) 0);
}
u_short mult_arg(pair)
 intpair *pair;
{
	u_short result;
 result = pair->a * pair->b;
	return (result);}
int add_one(arg)
	int arg;
{
	return (++arg);
}
 

接続型トランスポート

例 4-44 に示すサンプルプログラムは、あるホストのファイルを別のホストにコピーするプログラムです。RPC の send() 呼び出しで標準入力から読み込まれたデータがサーバ ーreceive() に送信され、サーバーの標準出力に書き出されます。また、このプログラムでは、1 つの XDR ルーチンでシリアライズとデシリアライズの両方を実行する方法も示します。ここでは、接続型トランスポートを使用します。


例 4-44 遠隔コピー (両方向 XDR ルーチン)

/*
 * XDR ルーチン:
 *      復号化時にネットワークを読み取り fp に書き込む
 *      符号化時に fp を読み取りネットワークに書き込む
 */
#include <stdio.h>
#include <rpc/rpc.h>
 
bool_t
xdr_rcp(xdrs, fp)
	XDR *xdrs;
	FILE *fp;
{
 unsigned long size;
	char buf[BUFSIZ], *p;
 
	if (xdrs->x_op == XDR_FREE)                 /* 解放するものなし */
		return(TRUE);
 while (TRUE) {
		if (xdrs->x_op == XDR_ENCODE) {
 		if ((size = fread( buf, sizeof( char ), BUFSIZ, fp))
			    == 0 && ferror(fp)) {
				fprintf(stderr, "can't fread¥n");
				return(FALSE);
			} else
 			return(TRUE);
		}
		p = buf;
 	if (! xdr_bytes( xdrs, &p, &size, BUFSIZ))
			return(0);
 	if (size == 0)
			return(1);
		if (xdrs->x_op == XDR_DECODE) {
			if (fwrite( buf, sizeof(char), size, fp) != size) {
				fprintf(stderr, "can't fwrite¥n");
 			return(FALSE);
			} else
				return(TRUE);
 	}
	}
}

例 4-45例 4-46 には、例 4-44 に示した xdr_rcp()ルーチンだけでシリアライズとデシリアライズを行うプログラムを示します。


例 4-45 遠隔コピー : クライアント側ルーチン

/* 送信側のルーチン */
#include <stdio.h>
#include <netdb.h>
#include <rpc/rpc.h>
#include <sys/socket.h>
#include <sys/time.h>
#include "rcp.h"
 
main(argc, argv)
	int argc;
	char **argv;
{
	int xdr_rcp();
 
	if (argc != 2 7) {
		fprintf(stderr, "usage: %s servername¥n", argv[0]);
		exit(1);
	}
	if( callcots( argv[1], RCPPROG, RCPPROC, RCPVERS, xdr_rcp,
stdin,
 	  xdr_void, 0 ) != 0 )
		exit(1);
	exit(0);
}
 
callcots(host, prognum, procnum, versnum, inproc, in, outproc,
out)
 char *host, *in, *out;
	xdrproc_t inproc, outproc;
{
 enum clnt_stat clnt_stat;
	register CLIENT *client;
 struct timeval total_timeout;
 
	if ((client = clnt_create( host, prognum, versnum,
"circuit_v")
    == (CLIENT *) NULL)) {
		clnt_pcreateerror("clnt_create");
 	return(-1);
	}
	total_timeout.tv_sec = 20;
 total_timeout.tv_usec = 0;
	clnt_stat = clnt_call(client, procnum, inproc, in, outproc,
out,
	                       total_timeout);
	clnt_destroy(client);
 if (clnt_stat != RPC_SUCCESS)
		clnt_perror("callcots");
 return((int)clnt_stat);
}
 

例 4-46 では、受信側のルーチンを定義します。サーバー側では、xdr_rcp() がすべての処理を自動的に実行することに注意してください。


例 4-46 遠隔コピー : サーバー側ルーチン

/*
 * 受信側ルーチン
 */
#include <stdio.h>
#include <rpc/rpc.h
#include "rcp.h"
 
main()
{
 void  rcp_service();
	if (svc_create(rpc_service,RCPPROG,RCPVERS,"circuit_v") == 0) {
		fprintf(stderr, "svc_create: errpr¥n");
 	exit(1);
	}
	svc_run();                    /* この関数は戻らない */
	fprintf(stderr, "svc_run should never return¥n");
}
 
void
rcp_service(rqstp, transp)
	register struct svc_req *rqstp;
	register SVCXPRT *transp;
{
	switch(rqstp->rq_proc) {
 	case NULLPROC:
			if (svc_sendreply(transp, xdr_void, (caddr_t) NULL) == FALSE)
				fprintf(stderr, "err: rcp_service");
 		return;
		case RCPPROC:
			if (!svc_getargs( transp, xdr_rcp, stdout)) {
				svcerr_decode(transp);
 			return();
			}
			if(!svc_sendreply(transp, xdr_void, (caddr_t) NULL)) {
				fprintf(stderr, "can't reply¥n");
 			return();
			}
			return();
 	default:
			svcerr_noproc(transp);
			return();
 }
 
}


XDR によるメモリー割り当て

XDR ルーチンは通常、データのシリアライズとデシリアライズに使用します。XDR ルーチンは、多くの場合、メモリーを自動的に割り当て、そのメモリーを解放します。一般に、配列や構造体へのポインタの代わりに NULL ポインタを渡されると、XDR ルーチンはデシリアライズを行うときに自分でメモリーを割り当てるようにします。次の例の xdr_chararr1() では、長さが SIZE の固定長配列を処理するようになっており、必要に応じてメモリーを割り当てることができません。

xdr_chararr1(xdrsp, chararr)
   XDR *xdrsp;
   char chararr[];
{
   char *p;
   int len;
 
   p = chararr;
   len = SIZE;
   return (xdr_bytes(xdrsp, &p, &len, SIZE));
} 

chararr にすでに領域が確保されている場合は、サーバー側から次のように呼び出すことができます。

char chararr[SIZE];
 
svc_getargs(transp, xdr_chararr1, chararr);

XDR ルーチンや RPC ルーチンにデータを引き渡すための構造体は、基底アドレスが、アーキテクチャで決められた境界になるようなメモリーの割り当てにならなければなりません。XDR ルーチンでメモリーを割り当てるときも、次の点に注意して割り当てます。

次の例では、第 2 引数が NULL ポインタの場合、デシリアライズされたデータを入れるためのメモリーが割り当てられます。

xdr_chararr2(xdrsp, chararrp)
   XDR *xdrsp;
   char **chararrp;
{
 
  int len;
 
  len = SIZE;
  return (xdr_bytes(xdrsp, charrarrp, &len, SIZE));
}
 

これに対する RPC 呼び出しを次に示します。

char *arrptr;
arrptr = NULL;
svc_getargs(transp, xdr_chararr2, &arrptr);
/*
 * ここで戻り値を使用
 */
svc_freeargs(transp, xdr_chararr2, &arrptr);
 

文字配列は、使用後に svc_freeargs() を使用して解放します。svc_freeargs() は、第 2 引数に NULL ポインタを渡された場合は何もしません。

これまでに説明したことをまとめると、次のようになります。

TS-RPC から TI-RPC への移行について

トランスポート独立の RPC ルーチン (TI-RPC ルーチン) を使用すると、アプリケーション開発者はトランスポート層へのアクセスレベルを自由に選択できます。最上位レベルのルーチンは、トランスポートが完全に抽象化されて、本当の意味でトランスポート独立になっています。下位レベルのルーチンを使用すると、旧バージョンと同じように個々のトランスポートに依存したアクセスレベルになります。

この節は、トランスポート特定 RPC (TS-RPC) アプリケーションを TI-RPC へ移行するための非公式ガイドになっています。表 4-11 では、いくつかのルーチンを選んで相違点を示します。ソケットとトランスポート層インタフェース (TLI) の移行の問題点についての詳細は、『Transport Interfaces Programming Guide』を参照してください。

アプリケーションの移行

TCP または UDP に基づくアプリケーションはバイナリ互換モードで実行できます。すべてのソースファイルをコンパイルし直したり、リンクし直したりできるのは、一部のアプリケーションだけです。RPC 呼び出しだけを使用し、ソケット、TCP、UDP に固有の機能を使用していないアプリケーションがこれに当たります。

ソケットセマンティクスに依存していたり、TCP や UDP の固有の機能を使用しているアプリケーションでは、コードの変更や追加が必要な場合があります。ホストアドレス形式を使用したり、バークレイ UNIX の特権ポートを使用するアプリケーションがこれに当たります。

ライブラリ内部や個々のソケット仕様に依存していたり、特定のトランスポートアドレスに依存するアプリケーションは、移行の手間も大きく、本質的な変更が必要な場合もあります。

移行の必要性

TI-RPC へ移行することの利点を次に示します。

RPC の場合の IPv6 の考慮事項

IPv6 は、IPv4 の後継バージョンで、今日のインターネットテクノロジーにおいて、最も一般的に使用される階層 2 のプロトコルです。また、IPv6 は IP の次世代 (IPng) とも呼ばれています。詳細については、『Solaris のシステム管理 (第 3 巻)』を参照してください。

ユーザーは、IPv4 と IPv6 の両方を使用できます。COTS (コネクション型のトランスポートサービス) を使用する場合、アプリケーションにより、使用する「スタック」が選択されます。この場合、TCP または CLTS (コネクションレス型のトランスポートサービス) を選択できます。

次の図では、IPv4 または IPv6 プロトコルスタックを経由して実行される、典型的な RPC アプリケーションを示しています。

図 4-4 RCP アプリケーション

Graphic

IPv6 がサポートされるのは、TI-RPC アプリケーションだけです。現在、TS-RPC では、IPv6 をサポートしていません。TI-RPC におけるトランスポートの選択は、NETPATH 環境変数、または /etc/netconfig のいずれかによって制御されます。IPv4 または IPv6 の代わりに、TCP または UDP を選択するかどうかは、/etc/netconfig 内の該当エントリの順序によって決まります。/etc/netconfig には、IPv6 に関連する新しいエントリが 2 つあります。また、デフォルトでは、これらのエントリは、ファイルの最初の 2 つのエントリです。まず、TI-RPC が IPv6 を試行し、失敗すると、IPv4 を試行します。この場合、RPC アプリケーション自体に変更が無いこと (つまり、トランスポートに関する情報がまったく無く、トップレベルのインタフェースを使用して書かれたものであること) が必要条件です。

clnt_create()  

svc_create() 

clnt_call() 

clnt_create_timed() 

このインタフェースでは、/etc/netconfig 内の最初のエントリが IPv6 である場合、IPv6 が自動的に選択されます。

IPv6 を使用すると、アプリケーションは、RPCBIND プロトコルの V3 と V4 だけを使用して、サービスバーと番号を配置できます。

clnt_tli_create()  

svc_tli_create() 

clnt_dg_create() 

svc_dg_create() 

clnt_vc_create() 

svc_vc_create() 

上記のいずれかのインタフェースを使用する場合は、コードを移植する必要があります。

特殊事項

libnsl ライブラリ

ネットワーク関数は libc から外されました。コンパイル時には libnsl を明示的に指定して、ネットワーク・サービス・ルーチンをリンクする必要があります。

旧インタフェース

旧インタフェースの多くは libnsl ライブラリでもサポートされていますが、TCP と UDP でしか使用できません。それ以外の新たなトランスポートを利用するには、新インタフェースを使用する必要があります。

名前 - アドレス変換機能

トランスポート独立にするには、アドレスを直接使用できません。すなわち、アプリケーションでアドレス変換を行う必要があります。

TI-RPC と TS-RPC の相違点

トランスポート独立型の RPC とトランスポート特定の RPC との主な相違点を表 4-11 に示します。TI-RPC と TS-RPC の比較については、「旧バージョンとの比較」 のサンプルプログラムを参照してください。

表 4-11 TI-RPC と TS-RPC の相違点

項目 

TI-RPC 

TS- RPC 

デフォルトのトランスポート選択 

TI-RPC では TLI インタフェースを使用する。 

TS-RPC ではソケットインタフェースを使用する。 

RPC アドレス結合 

TI-RPC ではサービスの結合に rpcbind() を使用する。rpcbind() はアドレスを汎用アドレス形式で扱う。

TS-RPC ではサービスの結合に portmap を使用する。

トランスポート情報  

トランスポート情報はローカルファイル /etc/netconfig に保存する。netconfig で指定したトランスポートはすべてアクセス可能になる。

トランスポートは TCP と UDP だけをサポートする。 

ループバックトランスポート  

rpcbind サービスではサーバー登録に安全なループバックトランスポートが必要。

TS-RPC サービスではループバックトランスポートは不要。 

ホスト名の解決 

TI-RPC のホスト名の解決順序は、/etc/netconfig で指定した動的ライブラリのエントリ順で決定される。

ホスト名の解決はネームサービスが実行する。順序は hosts データベースの状態で設定される。

ファイル記述子 

ファイル記述子は TLI 端点とみなす。 

ファイル記述子はソケットとみなす。 

rpcgen

TI-RPC の rpcgen では、複数引数、値渡し、サンプルクライアントとサンプルサーバーファイルのサポートを追加。

SunOS 4.1 と直前のリリースは TI-RPC rpcgen に対して一覧表示された特徴はサポートしない。

ライブラリ 

TI-RPC では、アプリケーションが libnsl ライブラリにリンクしていることを必要とする。

TS-RPC の機能はすべて libc で提供される。

マルチスレッドのサポート 

マルチスレッド RPC クライアントとサーバーがサポートされる。 

マルチスレッド RPC はサポートされない。 

関数の互換性のリスト

この節では、RPC ライブラリ関数を機能別にグループ化して示します。各グループ内では、旧バージョンと同じ関数、機能が追加された関数、旧バージョンにはなかった新規関数に分けて示します。


注 -

アスタリスクの付いた関数は、新バージョンへの移行期間はサポートされていますが、Solaris の将来のバージョンではなくなる可能性があります。


クライアントハンドルの作成

次の関数は、旧バージョンと同じで、現在の SunOS で使用できます。

clnt_destroy
clnt_pcreateerror
*clntraw_create
clnt_spcreateerror
*clnttcp_create
*clntudp_bufcreate
*clntudp_create
clnt_control
clnt_create
clnt_create_timed
clnt_create_vers
clnt_dg_create
clnt_raw_create
clnt_tli_create
clnt_tp_create
clnt_tp_create_timed
clnt_vc_create
 

サービスの作成と廃棄

次の関数は、旧バージョンと同じで、現在の SunOS で使用できます。

svc_destroy
svcfd_create
*svc_raw_create
*svc_tp_create
*svcudp_create
*svc_udp_bufcreate
svc_create
svc_dg_create
svc_fd_create
svc_raw_create
svc_tli_create
svc_tp_create
svc_vc_create
 
 

サービスの登録と登録削除

次の関数は、旧バージョンと同じで、現在の SunOS で使用できます。

*registerrpc
*svc_register
*svc_unregister
xprt_register
xprt_unregister
rpc_reg
svc_reg
svc_unreg
 
 

SunOS 4.x との互換性呼び出し

次の関数は、旧バージョンと同じで、現在の SunOS で使用できます。

*callrpc
clnt_call
*svc_getcaller - IP に基づくトランスポートでのみ使用可
rpc_call
svc_getrpccaller 

ブロードキャスト

次の関数の機能は旧バージョンと同じです。旧バージョンとの互換性を保つためにだけサポートされています。

*clnt_broadcast 

clnt_broadcast()portmap サービスにだけブロードキャストできます。

portmaprpcbind の両方にブロードキャストできる次の関数が現在の SunOS で使用できます。

rpc_broadcast 

アドレス管理

TI-RPC ライブラリ関数は、portmaprpcbind の両方で使用できますが、それぞれサービスが異なるため、次のように 2 組の関数が提供されています。

次の関数は portmap と共に使用します。

pmap_set
pmap_unset
pmap_getport
pmap_getmaps
pmap_rmtcall 

次の関数は rpcbind と共に使用します。

rpcb_set
rpcb_unset
rpcb_getaddr
rpcb_getmaps
rpcb_rmtcall
 

認証

次の関数の機能は旧バージョンと同じです。旧バージョンとの互換性を保つためにだけサポートされています。

authdes_create
authunix_create
authunix_create_default
authdes_seccreate
authsys_create
authsys_create_default 

その他の関数

現バージョンの rpcbind ではタイムサービス (主として、安全な RPC のためにクライアント側とサーバー側の時間を同期させるときに使用) が提供されており、rpcb_gettime() 関数で利用できます。pmap_getport()rpcb_getaddr() は、登録サービスのポート番号を取り出すときに使用します。サーバーでバージョンが 2、3、4 の rcpbind が実行されている場合には、rpcb_getaddr() を使用します。pmap_getport() はバージョン 2 が実行されている場合しか使用できません。

旧バージョンとの比較

例 4-47例 4-48では、クライアント作成部分が TS-RPC と TI-RPC とでどう違うかを示します。どちらのプログラムも次のことを実行します。


例 4-47 TS-RPC におけるクライアント作成

	struct hostent *h;
	struct sockaddr_in sin;
 int sock = RPC_ANYSOCK;
	u_short port;
	struct timeval wait;
 
	if ((h = gethostbyname( "host" )) == (struct hostent *) NULL)
{
		syslog(LOG_ERR, "gethostbyname failed");
		exit(1);
	}
 sin.sin_addr.s_addr = *(u_int *) hp->h_addr;
	if ((port = pmap_getport(&sin, PROGRAM, VERSION, "udp")) == 0) {
	 syslog (LOG_ERR, "pmap_getport failed");
		exit(1);
 } else
		sin.sin_port = htons(port);
	wait.tv_sec = 25;
	wait.tv_usec = 0;
	clntudp_create(&sin, PROGRAM, VERSION, wait, &sock);

TI-RPC では、UDP トランスポートは netid udp を持つものとみなします。netid はよく知られた名前でなくてもかまいません。


例 4-48 TI-RPC でのクライアント作成

	struct netconfig *nconf;
	struct netconfig *getnetconfigent();
	struct t_bind *tbind;
 struct timeval wait;
 
	nconf = getnetconfigent("udp");
 if (nconf == (struct netconfig *) NULL) {
		syslog(LOG_ERR, "getnetconfigent for udp failed");
		exit(1);
 }
	fd = t_open(nconf->nc_device, O_RDWR, (struct t_info *)NULL);
 if (fd == -1) {
		syslog(LOG_ERR, "t_open failed");
 	exit(1);
	}
	tbind = (struct t_bind *) t_alloc(fd, T_BIND, T_ADDR);
	if (tbind == (struct t_bind *) NULL) {
 	syslog(LOG_ERR, "t_bind failed");
		exit(1);
 }
	if (rpcb_getaddr( PROGRAM, VERSION, nconf, &tbind->addr, "host")
								== FALSE) {
		syslog(LOG_ERR, "rpcb_getaddr failed");
		exit(1);
	}
 cl = clnt_tli_create(fd, nconf, &tbind->addr, PROGRAM, VERSION,
                       0, 0);
	(void) t_free((char *) tbind, T_BIND);
	if (cl == (CLIENT *) NULL) {
		syslog(LOG_ERR, "clnt_tli_create failed");
		exit(1);
	}
 wait.tv_sec = 25;
	wait.tv_usec = 0;
	clnt_control(cl, CLSET_TIMEOUT, (char *) &wait);
 

例 4-49例 4-50では、ブロードキャスト部分が旧バージョンと SunOS 5.x とでどう違うかを示します。SunOS 4.xclnt_broadcast() は SunOS 5.xrpc_broadcast() とほぼ同じです。大きく異なるのは、collectnames() 関数で重複アドレスを削除し、ブロードキャストに応答したホスト名を表示する点です。


例 4-49 TS-RPC におけるブロードキャスト

statstime sw;
extern int collectnames();
 
clnt_broadcast(RSTATPROG, RSTATVERS_TIME, RSTATPROC_STATS,         
    	xdr_void, NULL, xdr_statstime, &sw, collectnames);
	...
collectnames(resultsp, raddrp)
	char *resultsp;
	struct sockaddr_in *raddrp;
{
	u_int addr;
	struct entry *entryp, *lim;
	struct hostent *hp;
	extern int curentry;
 
	/* 重複アドレスはカット */
 addr = raddrp->sin_addr.s_addr;
	lim = entry + curentry;
 for (entryp = entry; entryp < lim; entryp++)
		if (addr == entryp->addr)
			return (0);
	...
 /* ホスト名がわかればホスト名、わからなければアドレスを表示 */
	hp = gethostbyaddr(&raddrp->sin_addr.s_addr, sizeof(u_int),
	    AF_INET);
	if( hp == (struct hostent *) NULL)
		printf("0x%x", addr);
	else
 	printf("%s", hp->h_name);
}
 

例 4-50 は、TI-RPC におけるブロードキャストを示します。


例 4-50 TI-RPC におけるブロードキャスト

statstime sw;
extern int collectnames();
 
rpc_broadcast(RSTATPROG, RSTATVERS_TIME, RSTATPROC_STATS,
     xdr_void, NULL, xdr_statstime, &sw, collectnames, (char *)
0);
	...
collectnames(resultsp, taddr, nconf)
	char *resultsp;
	struct t_bind *taddr;
	struct netconfig *nconf;
{
 struct entry *entryp, *lim;
	struct nd_hostservlist *hs;
 extern int curentry;
	extern int netbufeq();
 
	/* 重複アドレスはカット */
	lim = entry + curentry;
	for (entryp = entry; entryp < lim; entryp++)
		if (netbufeq( &taddr->addr, entryp->addr))
			return (0);
 ...
	/* ホスト名がわかればホスト名、わからなければアドレスを表示 */
	if (netdir_getbyaddr( nconf, &hs, &taddr->addr ) == ND_OK)
 	printf("%s", hs->h_hostservs->h_host);
	else {
 	char *uaddr = taddr2uaddr(nconf, &taddr->addr);
		if (uaddr) {
			printf("%s¥n", uaddr);
			(void) free(uaddr);
 	} else
			printf("unknown");
	}
}
netbufeq(a, b)
	struct netbuf *a, *b;
{
	return(a->len == b->len && !memcmp( a->buf, b->buf, a->len));
}