ここでは、次の内容について説明します。
TxRPC の目的は、あるアドレス空間にあるクライアントと別のアドレス空間にあるサーバとの間で、透過的なプロシージャ コールを提供することです。クライアントとサーバは、別のマシンに存在する場合もあります。ただし、クライアントとサーバのアドレス空間が異なるので、次のような点に注意する必要があります。
クライアントとサーバが異なるアドレス空間に存在するので、また別のマシンに存在する場合もあるので、メモリの共有を想定することはできません。プログラムの状態 (例 : オープン ファイル記述子) やグローバル変数をクライアントとサーバ間で共有できません。必要な状態情報は、クライアントからサーバに渡す必要があり、その後の呼び出しでサーバからクライアントに返す必要があります。
作業をクライアントとサーバ間で分割すると、ソフトウェアのモジュール性が高まり、必要なリソースの近くで作業が行えるようになるといった利点があります。ただし、通信の問題やクライアントとサーバのいずれかが個別に利用できなくなるといった分散処理に関する問題を処理する複雑さが増加することも意味します。分散処理に伴う複雑さに起因するエラーには、ローカル プロシージャ コール用インタフェースで発生するエラーとは異なった処理が必要です。通信やリモート プロセスに関係したエラーの処理については、次のトピックで扱います。
X/OPEN RPC 仕様では、アプリケーション以外のエラーは状態パラメータまたは状態復帰で返されます。RPC サーバに障害があった場合には fault_status
値が返され、通信に障害があった場合には comm_status
値が返されます。状態復帰を指定するには、IDL ファイルでオペレーションの戻り値または error_status_t
型の [out]
パラメータを定義します。さらに ACF ファイルで、そのオペレーションまたはパラメータが [fault_status]
または [comm_status]
のいずれか、あるいは両方の属性を持つことを宣言します。
たとえば、IDL ファイル内で次のようにオペレーションを定義します。
error_status_t op([in,out]long *parm1, [out]error_status_t *commstat);
対応する ACF ファイル内での定義は次のようになります。
[fault_status]op([comm_status]commstat);
サーバからのエラーは、オペレーションの戻り値に、通信エラーは 2 番目のパラメータに返されます。クライアント コードでは、以下のようにエラーを処理します。
if (op(&parm1, &commstat) != 0 ││ commstat != 0) /* エラーの処理 */
状態復帰を使用する利点は、エラー発生個所で対処が行え、きめ細かなエラー回復処理を実行できることです。
状態復帰の欠点は、ローカル版の関数には必要のないパラメータをリモート関数に付加することです。さらに、きめ細かなエラー回復処理は冗長になりがちで、エラーを引き起こす傾向があります (たとえば、場合分けに見落としが生じるなど)。
DCE は第 2 のメカニズムとして例外処理を定義しています。この例外処理は C++ のものと類似しています。
C や C++ のアプリケーション コードを TRY
、CATCH
、CATCH_ALL
、および ENDTRY
ステートメントを用いて、例外が生じる可能性のあるブロックに区切ります。TRY
はブロックの開始を示します。CATCH
は、特定の例外用の処理ブロックを示します。CATCH_ALL
は、対応する CATCH
ステートメントを持たない例外を処理するために使われます。ENDTRY
でブロックを終了します。例外処理が低いレベルでは行なえず、RERAISE
ステートメントを用いて高いレベルのブロックで例外処理を行う場合、TRY
ブロックはネストされます。例外処理ブロックの外部で例外が生じた場合は、プログラムはログにメッセージを記録して終了します。例外処理マクロの詳細と使用例については、『Oracle Tuxedo C リファレンス』の TRY(3c)
を参照してください。
RPC コールに対して通信とサーバで生成される例外のほかに、さらに低いレベルの例外、特に、オペレーティング システムのシグナルも生成されます。このような例外の詳細については、『Oracle Tuxedo C リファレンス』の TRY(3c)
を参照してください。
X/OPEN 仕様では、100 を越える膨大な数のランタイム サポート関数が定義されています。これらの関数を、X/OPEN TxRPC IDL-only 環境ですべてサポートする必要はありません。これらの関数の大部分は、Oracle Tuxedo のクライアントやサーバに対して透過的に行われるバインドと管理に関連したものです。
アプリケーションの移植性に影響を及ぼす事柄の 1 つに、スタブ用の入出力パラメータと戻り値に割り当てられるメモリの管理があります。Stub Memory Management ルーチンは、スレッドを扱う 2 つの関数を除き TxRPC ランタイムでサポートされています。状態を返す関数には以下のものがあります。
rpc_sm_allocate
rpc_sm_client_free
rpc_sm_disable_allocate
rpc_sm_enable_allocate
rpc_sm_free
rpc_sm_set_client_alloc_free
rpc_sm_set_server_alloc_free
rpc_sm_swap_client_alloc_free
同等の例外を返す関数には以下のものがあります。
rpc_ss_allocate
rpc_ss_client_free
rpc_ss_disable_allocate
rpc_ss_enable_allocate
rpc_ss_free
rpc_ss_set_client_alloc_free
rpc_ss_set_server_alloc_free
rpc_ss_swap_client_alloc_free
これらの関数の詳細については、『Oracle Tuxedo C リファレンス』を参照してください。
ランタイム関数は、libtrpc
ファイルに含まれます。RPC クライアントとサーバの作成方法については次トピックで解説します。
メモリ管理を効果的に行う方法を次に示します。
デフォルトでは、Oracle Tuxedo クライアントはクライアント スタブを呼び出すときに malloc
と free
を使います。オペレーションの戻り値での暗黙的な [out]
ポインタを含めて、[out]
ポインタに割り当てられた領域以外の全領域はクライアント スタブからの復帰時に解放されます。[out]
ポインタの解放を容易にするために、rpc_ss_enable_allocate
() を呼び出します。さらに、RPC を呼び出す前に、rpc_ss_set_client_alloc_free
() を呼び出して、alloc
/free
をそれぞれ rpc_ss_alloc
()/rpc_ss_free
() に設定します。その後、関数 rpc_ss_disable_allocate
() を用いて割り当てたメモリをすべて解放します。たとえば、クライアント スタブから返される領域の解放を簡素にするには次のように使用します。
rpc_ss_set_client_alloc_free(rpc_ss_allocate, rpc_ss_free); ptr = remote_call_returns_pointer(); /* 返されたポインタをここで使用 */ ... rpc_ss_disable_allocate(); /* ptr を解放 */
アプリケーション操作を呼び出す Oracle Tuxedo サーバ スタブの実行時には、サーバ スタブ内では rpc_ss_allocate
を用いたメモリ割り当てが常に有効です。ACF ファイル内の属性 [enable_allocate]
は無効です。クライアントに応答を返す前に全メモリはサーバ内で解放されます。DCE では、メモリ割り当てが有効になるのは、[ptr]
フィールドまたはパラメータが存在する場合と、プログラマが [enable_allocate]
を明示的に指定したときだけです。
サーバ スタブがアプリケーション オペレーションを呼びだし、さらにそのアプリケーション オペレーションがクライアント スタブを呼び出す場合、つまり RPC を呼び出すことで、サーバがクライアントとして動作するときは、関数 rpc_ss_set_client_alloc_free
() を呼び出してメモリ割り当ての準備をする必要があります。その結果、オペレーションからの復帰時に割り当てられた領域がすべて解放されるようになります。次の呼び出しを行います。
rpc_ss_set_client_alloc_free(rpc_ss_allocate, rpc_ss_free);
関数 rpc_ss_allocate
() または関数 rpc_sm_allocate
() を呼び出すときには、設定しているポインタのデータ型に一致するように出力をキャストするようにしてください。次に例を示します。
long *ptr; ptr = (long *)rpc_ss_allocate(sizeof(long));
DCE/RPC と Tuxedo/TxRPC 両方からのスタブが同一環境で確実にコンパイルできるようにするため、Tuxedo/TxRPC の実装で用いるヘッダ ファイル名は異なるものにします。これらのヘッダ ファイルは IDL コンパイラが生成するインタフェース ヘッダ ファイルに自動的にインクルードされるので、アプリケーション プログラマには影響を与えません。ただし、型や関数の定義方法を調べるために、アプリケーション プログラムでこれらのヘッダ ファイルを表示することは可能です。以下に、RPC における新しいヘッダ ファイル名のリストを示します。
dce/nbase.h
、dce/nbase.idl
- rpc/tbase.h
および rpc/tbase.idl
を名称変更したヘッダ ファイル。定義済みの型、error_status_t
、ISO_LATIN_1
、ISO_MULTI_LINGUAL
、および ISO_UCS
の宣言を含みます。
dce/idlbase.h
- rpc/tidlbase.h
を名称変更したヘッダ ファイル。IDL の仕様に準拠した基本型 (例 : idl_boolean
、idl_long_int
) を含みます。さらにスタブ関数用の関数プロトタイプも含みます。
dce/pthread_exc.h
- rpc/texc.h
を名称変更したヘッダ ファイル。TRY
/ CATCH
例外処理マクロを含みます。
dce/rpcsts.h
- rpc/trpcsts.h
を名称変更したヘッダ ファイル。RPC インタフェース用の例外値と状態値の定義を含みます。
ヘッダ ファイルは $TUXDIR/include/rpc
ディレクトリにあります。デフォルトでは、Tuxedo/TxRPC IDL コンパイラは「システム IDL ディレクトリ」として $TUXDIR/include
を検索します。
IDL コンパイラからの出力は、多数の環境でコンパイルできるように作成されます。コンパイル作業の説明は次章を参照してください。ただし、環境によっては動作に支障が起きる構成もあります。既知の問題点を以下に示します。
ANSI に準拠しない旧形式の C 言語を使用してコンパイルする場合、「配列へのポインタ」は許可されません。次に例を示します。
typedef long array[10][10]; func() { array t1; array *t2; t2 = &t1; /* & ignored, invalid assignment */ func2(&t1); /* & ignored */ }
このため、移植を考慮した場合、「配列へのポインタ」をパラメータとして、オペレーションに渡すことが難しくなります。
文字列属性がマルチ バイト構造体に適用されるところに、文字列の配列を使用すると、コンパイラが構造体に文字を埋め込むときに、期待する結果が得られないことがあります。これは通常は起こり得ません。ほとんどのコンパイラは文字フィールドのみを含む構造体には文字の埋め込みは行いません。しかし、この現象は少なくとも一度確認されています。
デフォルトでは、定数値の実装は各定数に対して #define
を作成することで行われます。このため、定数に用いる名前は、IDL ファイル内やインポートされたすべての IDL ファイル内のほかのすべての名前と同じ名前にしてはいけません。ANSI C 環境でこの問題を回避するには、tidl
コンパイラの TxRPC 固有のオプション _use_const
を使用します。このオプションを使うと、#define
定義の代わりに、const
宣言が作成されます。定数値はクライアント スタブとサーバ スタブ内で宣言されます。ヘッダ ファイルをインクルードするほかのすべてのソース ファイルでは、単に extern const
宣言が使用されます。こうしないと、クライアント スタブとサーバ スタブを同一の実行ファイルにコンパイルできないという制限を生じるか、二重定義エラーが発生することに注意してください。
C++ 環境では、次のような制限があります。
typedef
の名前と、構造体やユニオンのタグに用いる名前を同一にしないでください。typedef
の名前が struct
名や union
名と一致している場合は、この制限はありません。
struct t1 { long s1; }; typedef struct t1 t1; /* 正常 */ typedef long t1; /* エラー */
構造体やユニオン タグの宣言をほかの構造体やユニオンの内部で行い、それを外部で参照することはできません。
struct t1 { struct t2 { long s2; } s1; } t1; typedef struct t3 { struct t2 s3; /* t2 は未定義エラー */ } t3;
一部のコンパイラでは警告メッセージが生成される場合があります。例を次に示します。
宣言された自動変数が使われないことを示す警告
以下の場合のように変数が sizeof()
関数で参照されているときなど、変数が設定される前に使用されていることを示す警告。
long *ptr; ptr = (long *)malloc(sizeof(*ptr) * 4);
クライアントとサーバのアプリケーション ソフトウェアをコーディングしているときは、IDL コンパイラが作成したデータ型、すなわち rpc/tidlbase.h
内で定義されるデータ型を使う必要があります (rpc/tidlbase.h 内での定義とは、次の表に「発行されるマクロ」として記載されているものです)。たとえば、idl_long_int
の代わりに long
を使うと、データ型はプラットフォームによっては 32 ビットであったり 64 ビットであったりします。一方、idl_long_int
は全プラットフォームで 32 ビットです。生成されるデータ型の一覧を表 3-1 に示します。
表 3-1 生成されるデータ型
IDL の型 | サイズ | 発行されるマクロ | C の型 |
---|---|---|---|
boolean |
8 ビット |
idl_boolean |
unsigned char |
char |
8 ビット |
idl_char |
unsigned char |
byte |
8 ビット |
idl_byte |
unsigned char |
small |
8 ビット |
idl_small_int |
char |
short |
16 ビット |
idl_short_int |
short |
long |
32 ビット |
idl_long_int |
32 ビット長のマシンの場合 : |
hyper |
64 ビット |
idl_hyper_int |
32 ビット長のマシンの場合 : ビッグ エンディアン struct { long high; unsigned long low; } リトル エンディアン struct { unsigned long low; long high; } 64 ビット長のマシンの場合 : long |
unsigned small |
8 ビット |
idl_usmall_int |
unsigned char |
unsigned short |
16 ビット |
idl_ushort_int |
short |
unsigned long |
32 ビット |
idl_ulong_int |
32 ビット長のマシンの場合 : |
unsigned hyper |
64 ビット |
idl_uhyper_int |
32 ビット長のマシンの場合 : ビッグ エンディアン struct { unsigned long high; unsigned long low; } リトル エンディアン struct { unsigned long low; unsigned long high; } 64 ビット長のマシンの場合 : unsigned long |
float |
32 ビット |
idl_short_float |
float |
double |
64 ビット |
idl_long_float |
double |
void * |
ポインタ |
idl_void_p_t |
void * |
handle_t |
ポインタ |
handle_t |
handle_t |
C の場合と同様、IDL でも複数のクラスの識別子があります。スコープやネームスペースなどのクラス内では、名前をユニークにする必要があります。
定数、typedef
、オペレーション、列挙用メンバー名は、1 つのネームスペース内にあります。
構造体、ユニオン、および列挙用のタグは、別のネームスペース内におかれます。
構造体やユニオンのメンバー名で同じレベルのものは、定義が行われた構造体やユニオンの中でユニークにする必要があります。
パラメータ名は、定義が行われたオペレーション プロトタイプ内ではユニークにする必要があります。
タグを持たず、typedef
の一部として定義されていない匿名の構造体またはユニオンは、オペレーションの戻り値やパラメータとして使用できないことに注意してください。
TxRPC 実行プログラムは Oracle Tuxedo システムを使って RPC 通信を行います。その他の Oracle Tuxedo のインタフェースや通信メカニズムは、RPC 呼び出しを使用している同一のクライアントおよびサーバ内で使用できます。このため、単一のクライアントが要求/応答呼び出し (例 : tpcall(3c)
、tpacall(3c)
、tpgetrply(3c)
)、会話型呼び出し (tpconnect(3c)
、tpsend(3c)
、tprecv(3c)
、tpdiscon(3c)
) を行い、安定キューにアクセス (tpenqueue(3c)
および tpdequeue(3c)
) することが可能です。クライアントが、Oracle Tuxedo ソフトウェアを最初に呼び出すときに、自動的にアプリケーションに参加します。クライアントが最初に行う呼び出しは、RPC 呼び出し、その他の通信呼び出し、ATMI 呼び出し (例 : バッファ割り当て、任意通知など) です。ただし、アプリケーションがセキュリティをオンにした状態で実行していたり、あるいはクライアントが特定のリソース マネージャ グループの一部として稼働する必要がある場合は、関数 tpinit(3c)
を明示的に呼び出してアプリケーションに参加しなければなりません。明示的に設定できるオプションの詳細と一覧については、『Oracle Tuxedo C リファレンス』の tpinit(3c)
を参照してください。アプリケーションが Oracle Tuxedo システムを使用した作業を完了する場合には、関数 tpterm(3c)
を明示的に呼び出してアプリケーションを終了し、関連のあるリソースをすべて解放する必要があります。ワークステーション以外のネイティブなクライアントに対してこの明示的な呼び出しが行われなかった場合、モニタがこれを検知し、userlog(3c)
に警告を出力し、リソースを解放します。クライアントがワークステーションの場合は、リソースは解放されず、結果的にワークステーションのリスナまたはハンドラがリソースを使い果たし、新たなクライアントを受け付けることができなくなります。
クライアントと同様に、サーバはクライアントのロール内の任意の通信パラダイムを使用できます。ただし、同一サーバ内では会話型サービスと RPC サービスの両方を提供 (宣言) することはできません。後で説明するように、RPC サーバは非会話型として指定する必要があります。ATMI 要求/応答と RPC サービスを同一サーバ内で混用することは可能ですが、お勧めできません。その他の制限として、RPC オペレーションは関数 tpreturn(3c)
や関数 tpforward(3c)
を呼び出すことはできません。RPC オペレーションは、ローカルに呼び出された場合と同様の復帰動作で復帰する必要があります。RPC オペレーションから関数 tpreturn(3c)
や関数 tpforward(3c)
の呼び出しを試みると、その呼び出しはインターセプトされ、クライアントにエラーが返されます (例外 rpc_x_fault_unspec
または状態 rpc_s_fault_unspec
)。
サーバでは利用できますが、クライアントでは利用できない関数が 2 つあります。関数 tpsvrinit(3c)
と関数 tpsvrdone(3c)
がこれにあたります。それぞれサーバの起動時と停止時に呼び出されます。サーバは、TxRPC オペレーション要求を受け取る前に、tx_open(3c)
を呼び出す必要があるので、tpsvrinit()
で tx_open を呼び出すことをお勧めします。デフォルトの tpsvrinit()
関数は関数 tx_open()
を呼び出しています。
TX 関数は、トランザクションの境界用のインタフェースを提供します。関数 tx_begin(3c)
と、tx_commit(3c)
または tx_rollback(3c)
は、通信を含むトランザクション内のすべての作業をカプセル化します。その他のプリミティブとして、トランザクションのタイムアウト設定、連鎖または非連鎖としてのトランザクションの宣言、トランザクション情報の取得も提供されています。以上についての詳細は、X/OPEN TX 仕様に記載され、また X/OPEN TxRPC 仕様にも解説されています。X/OPEN TxRPC 仕様には、TX と RPC の相互動作についての解説があります。その要点を次に述べます。
インタフェースまたはオペレーションは、属性 [transaction_optional]
を持つことができます。この属性は、あるトランザクション内で RPC を呼び出した場合、呼び出されるオペレーション内で行われる作業は、そのトランザクションの一部になることを示します。
インタフェースまたはオペレーションは、属性 [transaction_mandatory]
を持つことができます。この属性は、RPC 呼び出しはトランザクション内で行う必要があることを示します。そうでなければ例外 txrpc_x_not_in_transaction
が返されます。
上記 2 つの属性がいずれも指定されない場合には、呼び出されるオペレーション内での作業は、呼び出し側でアクティブになるトランザクションの一部にはなりません。
サーバ内で TxRPC オペレーションが呼び出され、関数 tx_open(3c)
が呼び出されていなければ、例外 txrpc_x_no_tx_open_done
が呼び出し側に返されます。
TxRPC では、オペレーションが関数 tx_rollback(3c)
を呼び出すことを認めています。同関数を呼び出すことで、トランザクションがロールバックのみとしてマークされ、トランザクションとして行われた作業は、すべてロールバックされます。この場合、アプリケーションも呼び出し側にアプリケーション レベルのエラーを返し、トランザクションがロールバックされることを示すことをお勧めします。
TxRPC 仕様で定義された IDL に対するその他の変更と制限については、IDL に関する項で既に説明しました。