10 RMIワイヤー・プロトコル
10.1 概要
RMIプロトコルは、ワイヤー上の形式のために、他に2つのプロトコルを利用します。Javaオブジェクト直列化プロトコルとHTTPプロトコルです。 オブジェクト直列化プロトコルは、呼出しと戻りのデータを整列化するために使用します。 HTTPプロトコルは、リモート・メソッド呼出しを「POST」し、条件が許せば戻り値を取得するために使用します。 それぞれのプロトコルは、別々の文法としてドキュメント化されます。 生成規則の非ターミナル・シンボルは、別のプロトコルが制御する規則を参照します(オブジェクト直列化またはHTTP)。 プロトコルの境界を越える場合は、それ以後の生成規則は、組み込まれたプロトコルを使用します。
文法表記についてのノート
- ここで使用する表記方法はJava言語仕様(Java Language Specification)で使用されているものとほぼ同じです。
- ストリームの制御コードは、16進数のリテラル値で表現します。
- 文法上の非ターミナル・シンボルは、メソッド呼出しで与えられるアプリケーションに特有な値であることを表します。 このような非ターミナルの定義はJava言語のデータ型から構成されます。 それぞれの非ターミナルからそれぞれの型へのテーブル・マッピングは、文法規則に従います。
10.2 RMIトランスポート・プロトコル
RMIのワイヤー形式はStream
で表現されます。 ここで採用している用語は、クライアントからみたものです。 Out
は出力メッセージを表し、In
は入力メッセージを表します。 トランスポート・ヘッダーの内容は、オブジェクト直列化を使用した形式ではありません。
Stream:
Out
In
RMIの入力と出力ストリームは対になっています。 各Out
ストリームは、対応するIn
ストリームを持ちます。 Out
ストリームは、文法的にはソケットの出力ストリームにマッピングします(クライアントから見た場合)。 In
ストリームは、文法的には、対応するソケットの入力ストリームと対になります。 出力ストリームと入力ストリームは対になっているので、入力ストリームで必要になるヘッダー情報は、プロトコルを認識できたかどうかの確認のみです。それ以外のヘッダー情報(マジック番号やバージョン番号)は、ストリーム対のコンテキストに含めることが可能です。
10.2.1出力ストリームの形式
RMIにおける出力ストリームは、トランスポートHeader
情報とそれに続くMessages
の並びから構成されます。 交互に出力ストリームはHTTPプロトコルに組み込まれた呼出しを含めることができます。
Out:
Header Messages
HttpMessage
Header:
0x4a 0x52 0x4d 0x49 Version Protocol
Version:
0x00 0x01
Protocol:
StreamProtocol
SingleOpProtocol
MultiplexProtocol
StreamProtocol:
0x4b
SingleOpProtocol:
0x4c
MultiplexProtocol:
0x4d
Messages:
Message
Messages Message
Messages
は特定のプロトコル内で、Protocol
が指定する方法に従ってラップされます。 SingleOpProtocol
の場合は、Header
に続くMessage
が1つしかない場合があり、Message
がラップされる追加データが存在しないことがあります。 SingleOpProtocol
はHTTP要求に組み込まれた呼出しのために使用されます。単一の要求と応答以上の相互作用はありません。
StreamProtocol
とMultiplexProtocol
の場合、サーバーはプロトコルのサポートを確認するバイト0x4e
で応答し、サーバーが見ることができるホスト名とポート番号を含むEndpointIdentifier
はクライアントによって使用されています。 この情報を使ってクライアントは、セキュリティ上の理由でできない場合もありますが、自分のホスト名を知ることができます。 クライアントはそのあと、もう1つのEndpointIdentifier
で応答する必要があり、応答の中には接続を受け付けるときのデフォルトの終点が含まれます。 MultiplexProtocol
では、サーバーはこの情報からクライアントを特定できます。
StreamProtocol
の場合は、このエンド・ポイントのネゴシエーションのあとに、それ以上のデータのラッピングを行うことなしにMessages
が出力ストリームへ送られます。 MultiplexProtocol
では、ソケット接続は、多重接続の具体的な接続として使用されます(「セクション10.6、"RMI多重化プロトコル"」で説明されています)。 この多重化された接続上で開始された仮想接続は、次に説明される一連のMessages
から構成されます。
現在、入力メッセージには、Call
、Ping
およびDgcAck
という3つのタイプがあります。 Call
は、メソッド呼出しをエンコードします。 Ping
は、トランスポートレベルのメッセージであり、リモート仮想マシンの状態をテストします。 DgcAck
は、サーバーからの戻り値のリモート・オブジェクトがクライアントによって受信されたことを示すサーバー分散ガベージ・コレクタに送られる確認応答です。
Message:
Call
Ping
DgcAck
Call:
0x50 CallData
Ping:
0x52
DgcAck:
0x54 UniqueIdentifier
10.2.2入力ストリームの形式
現在、入力メッセージには、ReturnData
、HttpReturn
、およびPingAck
の3種類があります。 ReturnData
は「通常の」RMI呼出しの結果です。 HttpReturn
はHTTPプロトコルに組み込まれた呼出しに対する戻り値です。 PingAck
はPing
メッセージに対する肯定応答です。
In:
ProtocolAck Returns
ProtocolNotSupported
HttpReturn
ProtocolAck:
0x4e
ProtocolNotSupported:
0x4f
Returns:
Return
Returns Return
Return:
ReturnData
PingAck
ReturnData:
0x51 ReturnValue
PingAck:
0x53
10.3 RMIにおけるオブジェクト直列化プロトコルの使用
RMI呼出しにおける呼び出しと戻りのデータは、Javaオブジェクト直列化プロトコルに従って整形されます。 それぞれのメソッド呼出しのCallData
は、Javaオブジェクトの出力ストリームに書き込まれ、それにはObjectIdentifier
(呼出しの対象)、Operation
(呼び出されるメソッドを表す数値)、Hash
(クライアント・スタブとリモート・オブジェクト・スケルトンが共通なスタブ・プロトコルを使うことを確認する数値)、およびそれに続くArguments
(ない場合もある)が含まれます。
JDK 1.1のスタブ・プロトコルでは、Operation
はrmic
が割り当てたメソッド番号を表し、Hash
はスタブのインタフェース・ハッシュであるスタブおよびスケルトンのハッシュを表しています。 Java 2のスタブ・プロトコル(Java 2のスタブはrmic
で-v1.2
オプションを使用して生成される)では、Operation
は -1という値を持ち、Hash
は呼び出されるメソッドを表すハッシュになります。 ハッシュについては、セクション"RemoteRef
インタフェース"で説明しています。
CallData:
ObjectIdentifier Operation Hash Arguments[opt]
ObjectIdentifier:
ObjectNumber UniqueIdentifier
UniqueIdentifier:
Number Time Count
Arguments:
Value
Arguments Value
Value:
Object
Primitive
RMI呼出しのReturnValue
は、正常か例外かを示すリターン・コード、戻り値にタグ付けするUniqueIdentifier
(必要ならばDGCAck
を送るために使用する)、そして返される結果である、戻り値Value
またはスローされたException
が続く構成になっています。
ReturnValue:
0x01 UniqueIdentifier Value[opt]
0x02 UniqueIdentifier Exception
ノート: ObjectIdentifier
、UniqueIdentifier,
およびEndpointIdentifier
は、デフォルトの直列化を使用して書き出されませんが、それぞれ独自の特殊なwrite
メソッド(これはオブジェクトの直列化で使用されるwriteObject
メソッドではありません)を使用します。各タイプの識別子のwrite
メソッドは、そのコンポーネント・データを連続して出力ストリームに追加します。
10.3.1クラスの注釈およびロード
RMIは、ObjectOutputStream
のannotateClass
メソッドおよびObjectInputStream
のresolveClass
メソッドをそれぞれオーバーライドします。 各クラスには、コード・ベースのURL (クラスをロードする元の場所)を使用して注釈が付けられています。 annotateClass
メソッドでは、クラスをロードしたクラス・ローダーに対し、そのクラス・ローダーのコード・ベースのURLを問い合わせます。 クラス・ローダーが非null
で、非null
コード・ベースを持っている場合は、そのコード・ベースは、ObjectOutputStream.writeObject
メソッドを使用してストリームに書き込まれます。それ以外の場合は、writeObject
メソッドを使用して、ストリームにnull
が書き込まれます。 ノート: 最適化のため、「java
」パッケージ内のクラスには、ノートが付けられません。これは、これらのクラスは受信側が常に利用できるからです。
クラスの注釈は、直列化復元中にObjectInputStream.resolveClass
メソッドを使用して解決されます。 resolveClass
メソッドは、最初にObjectInputStream.readObject
メソッドを使用して、注釈を読み取ります。 注釈(コード・ベースURL)がnull
でない場合は、そのURLのクラス・ローダーを取得して、クラスをロードしようとします。 クラスは、クラス・バイトを取り出すためにjava.net.URLConnection
を使用してロードされます。これは、Webブラウザのアプレット・クラス・ローダーが使用するメカニズムと同じです。
10.4 RMIにおけるHTTP POSTプロトコル
プロキシ経由のファイアウォール経由のRMI呼び出しの実装は、JDK 9では削除されています。
10.5 RMIのアプリケーション固有の値
この表では、RMIで使用されるアプリケーション固有の値を表現する非ターミナル・シンボルを示します。 この表では、それぞれのシンボルがそれぞれの持つ型に対応しています。 各シンボルは、それが埋め込まれるプロトコルを使用して整形されます。
記号 | type |
---|---|
Count |
short |
Exception |
java.lang.Exception |
Hash |
long |
Hostname |
UTF |
Number |
int |
Object |
java.lang.Object |
ObjectNumber |
long |
Operation |
int |
PortNumber |
int |
Primitive |
byte , int , short , long ... |
Time |
long |
10.6 RMIの多重プロトコル
多重化の目的は、エンド・ポイントの一方だけがほかの機能(TCP接続など)を使って双方の接続を開くことができる環境において、2つのエンド・ポイントが相互に、他端に対して多重の全二重接続を開くことができるモデルを提供することです。 RMIにより、この単純な多重化プロトコルを使って、ほかの方法では不可能な状況においても、クライアントがRMIサーバー・オブジェクトに接続できるようになります。 たとえば、アプレット環境のセキュリティ・マネージャの中には、入ってくる接続を受信するためのサーバー・ソケットの作成を禁止して、アプレットがRMIオブジェクトをエクスポートして直接ソケット接続からのリモート呼出しをサービスできないようにするものがあります。 アプレットがcodebaseホストへの通常のソケット接続を開くことができる場合には、その接続上で多重プロトコルを使うことにより、そのアプレットがエクスポートしたRMIオブジェクトのメソッドを、codebaseホストが呼び出せるようになります。 このセクションでは多重プロトコルの形式と規則を説明します。
10.6.1定義
このセクションでは、後述のプロトコルの説明で使用される用語を定義します。
エンド・ポイントとは、多重プロトコルを使って接続されている2人のユーザーの一方を指します。
多重プロトコルは、双方向の、信頼できる既存のバイト・ストリームの上位層になります。また、このバイト・ストリームは、エンド・ポイントの一方が他方に対して開いたものです。 現在のRMIの使用法では、これは常にjava.net.Socket
オブジェクトで作られたTCP接続です。 この接続のことを具象接続と呼びます。
多重プロトコルでは、仮想接続機能を使用できます。仮想接続は、双方向の信頼できるバイト・ストリームであり、2つのエンド・ポイント間の特定のセッションを表現するものです。 2つのエンド・ポイント間の1つの具象接続上の仮想接続のセットが多重接続を構成します。 多重プロトコルを使用して、どちらのエンド・ポイントからでも仮想接続のオープンとクローズが行えます。 仮想接続の、あるエンド・ポイントに関する状態は、具象接続上でやり取りされる多重プロトコルの要素により定義されます。 この状態には、接続のオープンまたはクローズ、行き来する実際のデータ、および関連するフロー制御メカニズムが含まれます。 特に他の説明がない場合、この項のこれ以降の部分では接続という用語は仮想接続の意味で使用されます。
ある多重接続の中の仮想接続は16ビット整数で特定され、これを接続識別子と呼びます。 したがって、1つの多重接続上には、最大65,536の仮想接続が存在することになります。 同時に使用できる仮想接続の数は実装によって制限されることがあります。
10.6.2接続状態とフロー制御
接続は、種々の多重プロトコルにより定義されたオペレーションにより操作されます。 プロトコルで定義されたオペレーションには、OPEN、CLOSE、CLOSEACK、REQUEST、およびTRANSMITという名前のものがあります。 すべての操作の正確な形式と規則は、「セクション10.6.3、"プロトコル・フォーマット"」で詳しく説明しています。
OPEN、CLOSE、およびCLOSEACKオペレーションは接続を開いたり閉じたりし、REQUESTとTRANSMITオペレーションは開いた接続上でフロー制御メカニズムの制限下でのデータ伝送に使われます。
接続状態
仮想接続は、エンド・ポイントがOPENオペレーションをその接続に対して送り、その接続に対するOPENオペレーションを受け取ると(ただし、そのあとにクローズされていない場合)、その特定のエンド・ポイントに対してオープンした状態になります。 種々のプロトコル・オペレーションを次に説明します。
仮想接続は、エンド・ポイントが接続にCLOSEオペレーションを送ったが、まだそれに続くCLOSEまたはCLOSEACKを受け取っていないときに、そのエンド・ポイントに対してペンディング・クローズの状態にあります。
仮想接続は開かれなかったとき、あるいはCLOSEやCLOSEACKオペレーションを受け取ったときに(そしてそれ以後オープンされていないならば)、その特定のエンド・ポイントに対してクローズの状態にあります。
フロー制御
多重プロトコルでは、単純パケット・フロー制御メカニズムを使用することにより、同じ具象接続上に複数の仮想接続が同時に存在することが認められます。 フロー制御メカニズムにおける高度な要件として、仮想接続の状態は他から独立していなければならないという要件があります。つまり、1つの接続の状態は、他の接続に対して影響を及ぼしません。 たとえば、ある接続から入ってくるデータを扱うバッファがいっぱいになったとしても、これが他の接続のデータ伝送やデータ処理を妨げることはありません。 このことは、1つの接続の進行が、他の接続の使用の終了に依存する場合に必要です。たとえば、RMIの再帰的な呼出しなどの場合です。 したがって、実際問題として、接続の実装は具象接続上に入ってくる多重プロトコル・データ(この仕様を満たしていると仮定して)を常に消費および処理できる必要があります。
各エンド・ポイントは、それぞれの接続に関して2つの状態値を持ちます。1つ目の状態値は、エンド・ポイントが何バイトのデータを要求し、そのうちの何バイトをまだ受け取っていないか(入力要求カウント)を示し、2つ目の状態値は、エンド・ポイントが何バイトのデータを要求し、その中でこちらのエンド・ポイントが何バイトを残しているか(出力要求カウント)を示します。
1つのエンド・ポイントの出力要求カウントは、もう一方のエンド・ポイントからREQUESTオペレーションを受けるたびに増加し、TRANSMITオペレーションを送ると減少します。 1つのエンド・ポイントの入力要求カウントは、それがREQUESTオペレーションを送ると増加し、TRANSMITオペレーションを受信すると減少します。 どちらかの数値がマイナスになるとプロトコル違反です。
1つのエンド・ポイントが、入力要求カウントを、現時点でブロックされることなく処理できるバイト数以上に、増加させてしまうほど大きなREQUESTオペレーションを送ることはプロトコル違反です。 しかし、接続しているユーザーがデータを読み込むために待機している状態では、入力要求カウントは必ずゼロ以上の値でなければなりません。
1つのエンド・ポイントが、出力要求カウントを超えたTRANSMITオペレーションを送るのはプロトコル違反です。 送出されるデータが、その接続のユーザーが明示的にフラッシュを要求するまで、バッファリングされる可能性があります。 明示的なフラッシュ要求、または実装の出力バッファがいっぱいであるために、その接続を経由してデータを送られなければならない場合は、接続上のユーザーはTRANSMITオペレーションが十分に進行するまでブロックされる可能性があります。
ここで説明した規則以外でも、適切と判断される範囲内で自由にREQUESTやTRANSMITオペレーションを実行する実装が可能です。 たとえば、エンド・ポイントは自分の受信バッファが空でなかったとしても接続に対して追加データを要求できます。
10.6.3プロトコルの形式
多重プロトコルのバイト・ストリーム形式は、可変長レコードが隣接して繋がったものです。 レコードの最初のバイトは、オペレーション・コードであり、そのレコードに対するオペレーションを明示し、それ以後の内容の形式を決定します。 許されるオペレーション・コードは次のとおりです。
value | name |
---|---|
0xE1 | OPEN |
0xE2 | CLOSE |
0xE3 | CLOSEACK |
0xE4 | REQUEST |
0xE5 | TRANSMIT |
レコードの最初のバイトが上に定義されたオペレーション・コード以外のときは、プロトコル違反になります。 次のセクションでそれぞれのオペレーション・コードに対するレコード形式を説明します。
OPEN操作
OPENオペレーションの形式を次に示します。
サイズ(バイト) | name | description |
---|---|---|
1 | opcode | オペレーション・コード(OPEN) |
2 | ID | 接続識別子 |
エンド・ポイントは、OPENオペレーションを送ることによって、指定された接続を開きます。 IDが指示する接続がすでにオープンしていたり、送り側のエンド・ポイントに対してペンディング・クローズの状態でこのコマンドを送ったりするのは、プロトコル違反です。 接続がオープンされたあとでは、その接続に対する入力および出力の要求カウントの状態は、両側のエンド・ポイントでゼロになっています。
OPENオペレーションの受信は、他端のエンド・ポイントが指定の接続を開こうとしていることを示します。 接続がオープンされたあとでは、その接続に対する入力および出力の要求カウントの状態は、両側のエンド・ポイントでゼロになっています。
両側のエンド・ポイントでの識別子の競合を防止するために、有効接続識別子のためのスペースはMost significant bitの値によって2つに分かれています。 それぞれのエンド・ポイントは、高位ビットが特定の値を持っている場合にだけ、接続を開くことが許されます。 具象接続を開始しようとするエンド・ポイントは、最高位ビットが設定された識別子でだけ接続を開くことができ、他端のエンド・ポイントは、最高位ビットがゼロに設定された識別子で接続を開かなければなりません。 たとえば、サーバー・ソケットを作成できないRMIアプレットが自分のcodebaseホストに対して多重化接続を開始しようとするときは、アプレットは仮想接続を識別子0x8000-7FFFの範囲で開くことができ、サーバーは識別子の範囲0-0x7FFFの範囲で仮想接続を開くことができます。
CLOSE操作
CLOSEオペレーションの形式を次に示します。
サイズ(バイト) | name | description |
---|---|---|
1 | opcode | オペレーション・コード(OPEN) |
2 | ID | 接続識別子 |
エンド・ポイントは、CLOSEオペレーションを送ることによって、指定の接続を閉じます。 IDが指示する接続が現在閉じていたり、送信側に対してペンディング・クローズになっているとプロトコル違反になります(この接続に対してCLOSEオペレーションを送っていると、受信側に対してペンディング・クローズになっている可能性がある)。 CLOSEを送ると、その接続は送信側のエンド・ポイントに対してペンディング・クローズ状態になります。 したがって、接続の他端からCLOSEまたはCLOSEACKを受け取ってからでないと、再度接続を開くことはできません。
CLOSEオペレーションの受信は、接続の他端がその接続を閉じたことを意味し、受信側にとっても接続は閉じられます。 受信側エンド・ポイントは、この接続に対してこれ以上の(再度オープンされないかぎり)オペレーションを行う必要はありませんが、実装の入力バッファにたまっているデータは、接続のユーザーへ渡さなければなりません。 接続がペンディング・クローズではなく前もってオープンされているならば、受信側エンド・ポイントはその接続に対してCLOSEACKで応答する必要があります。
CLOSEACK操作
CLOSEACKオペレーションのレコード形式を次に示します。
サイズ(バイト) | name | description |
---|---|---|
1 | opcode | オペレーション・コード(OPEN) |
2 | ID | 接続識別子 |
エンド・ポイントは、受信側エンド・ポイントからのCLOSEを認識したことを知らせるためにCLOSEACKオペレーションを送ります。 IDが指定する接続の受信側が、このオペレーションを受信したときにペンディング・クローズの状態になっていない場合は、プロトコル違反になります。
CLOSEACKオペレーションの受信は、その接続がペンディング・クローズからクローズへ状態変化したことを示します。したがって、これ以後はその接続を再オープンできます。
REQUEST操作
REQUESTオペレーションのレコード形式を次に示します。
サイズ(バイト) | name | description |
---|---|---|
1 | opcode | オペレーション・コード(OPEN) |
2 | ID | 接続識別子 |
4 | count | 要求された追加バイト数 |
エンド・ポイントは、REQUESTオペレーションを送ることによって、その接続の入力要求カウントを増加させます。 IDが指定する接続が、送信側のエンド・ポイントから見てオープンされていない場合は、プロトコル違反になります。 エンド・ポイントの入力要求カウントは、countが示す値だけ増加します。 countは、符号付きの32ビット整数であり、マイナスまたはゼロの場合はプロトコル違反です。
REQUESTオペレーションの受信は、その接続の出力要求カウントをcountが示す値だけ増加させます。 受信側にとってその接続がペンディング・クローズ状態であるときは、いかなるREQUESTオペレーションも無視されます。
TRANSMIT操作
TRANSMITオペレーションのレコード形式を次に示します。
サイズ(バイト) | name | description |
---|---|---|
1 | opcode | オペレーション・コード(OPEN) |
2 | ID | 接続識別子 |
4 | count | 伝送バイト数 |
count | data | 伝送データ |
エンド・ポイントは、TRANSMITオペレーションにより、指定の接続を介して実際のデータ伝送を行います。 IDが指定する接続が、送信側のエンド・ポイントから見てオープンされていない場合は、プロトコル違反になります。 エンド・ポイントの出力要求カウントは、countの値だけ減少します。 countは、符号付きの32ビット整数であり、マイナスまたはゼロの場合はプロトコル違反です。 また、TRANSMITオペレーションで送信側のエンド・ポイントの出力要求カウントがマイナスになるのも、プロトコル違反です。
TRANSMITオペレーションの受信は、countバイトのデータがキューに追加され、接続から読める状態になったことを示します。 受信側エンド・ポイントの入力要求カウントはcountの値だけ減少します。 この結果、入力要求カウントがゼロになり、かつ接続のユーザーがさらにデータを読み込む必要がある場合は、エンド・ポイントは追加のREQUESTオペレーションで応答しなければなりません。 接続が受信側のエンド・ポイントから見てペンディング・クローズならば、いかなるTRANSMITオペレーションも無視されます。
プロトコル違反
上述したプロトコル違反が発生した場合、または具象接続で通信エラーが発生した場合は、多重接続はシャットダウンされます。 実際の接続が終了し、すべての仮想接続も即時に閉じられます。 仮想接続上ですでに読込み可能になっていたデータは接続のユーザーによって読みとり可能です。