RtORBの動作について
ここでは、RtORBの動作について書いていきます。但し、現在OpenRTM-aistをC言語で書き起こしていますが、CORBAの通信部分をなるべく小さくしたいと思っていますので、このドキュメントはその途中段階を含んでいます。
ただし、このドキュメントのほとんどは、現在公開してるRtORBの動作ですので、参考にはなるかと思います。
このドキュメントの内容
上記の内容は、こちらにPDFとしてまとめております。
添付ファイル:RtORB_current.pdf
また、下記の内容は、PDFにしておりますので、こちらをダウンロードされ、閲覧された方がよいと思います。(若干修正しています)
添付ファイル:CORBA_OverView.pdf
CORBAの概要
今更ながらCORBAの説明には、多くのサイトで紹介されているのでいうまでもないと思いますが、Wikipediaでは
Common Object Request Broker Architecture(コモン オブジェクト リクエスト ブローカー アーキテクチャー、略称CORBA)とは、Object Management Group(OMG)が定義した標準規格であり、様々なコンピュータ上で様々なプログラミング言語で書かれたソフトウェアコンポーネントの相互利用を可能にするものである。
と記載されていました。これでは、あまりイメージがわかないのですが、要するに、CORBAというのを使うと、別のマシンで動いているアプリケーションと簡単に通信ができて、あたかも自分のプログラムでは、ライブラリを利用するように関数コールができてしまうというものです。
この機能は、UNIXではよく使われるTCPソケットを使った通信ポートを開いて、データを通信させているのをCORBAライブラリで隠蔽しているだけです。そのため、自分でSocketを作って、データのやり取り方法を考えてプログラムを実装するよりも、簡単で安全であることが期待できると思います。
しかし、なぜかCORBAと聞くと多くの人が「CORBAは重くて…」というのをよく耳にします。これは、CORBAの実装方法と利用方法にあると思います。また、CORBAでは、負荷分散機能も実装している場合があり、そのような実装を使えば、自分で通信プログラムを書くよりもはるかに効率的で柔軟なプログラミングが出来るはずです。
このサイトでは、CORBAで最低限の機能を提供できるように、そのサブセットを実装していますので、その動作について解説をしたいと思います。
CORBAの基本的な動作
CORBAは、TCPソケットを介して他のソフトウェアと通信していると述べましたが、ORBの部分を簡単に書くと下記の図のようになっています。この図で、クライアントであるStubというオブジェクトに、実装されているメソッドを実行させたい場合には、通常のオブジェクトと同じように
''インスタンス名.メソッド名(引数,,,)''
の形でプログラムに記載すれば、ORBの部分を通じて、自分のプロセス内に実体があれば、それを呼び出し、他のプロセス(他のマシン上も含む)にあれば、Stub内に書かれているURLにしたがって、TCPソケット接続、データ送信、リモートでメソッド実行をさせて結果をもらうという動作をします。
''インスタンス名.メソッド名(引数,,,)''
の形でプログラムに記載すれば、ORBの部分を通じて、自分のプロセス内に実体があれば、それを呼び出し、他のプロセス(他のマシン上も含む)にあれば、Stub内に書かれているURLにしたがって、TCPソケット接続、データ送信、リモートでメソッド実行をさせて結果をもらうという動作をします。
この引数や結果を送受信するときに、データを何らかの形で符号化しないといけませんので、それを規定しているのがGIOPです。IIOPとは、TCPソケットを使った通信路でGIOPを動かしているのでこのような名前になっています。また、この時、様々なデータを符号化するためにCDR(Common Data Representation)が使われています。この方法は、32ビットのUNIX OSで使われるデータ構造に非常に近くなっていますが、送受信するためにデータを符号化しなければいけないため、データ構造によっては、非常に時間がかかることになります。
すなわち、CORBAを使用する場合に、送受信するデータ構造をどうするかをよく考えて実装すると比較的高速な通信ができるようになります。
RtORBでは、ORB部分は、自分のプロセス内のCORBAオブジェクト管理テーブルで管理しており、内部にあれば実装関数をコールし、そうでなければ、IIOPで対象CORBAオブジェクトと通信するようになっていますので、下記の図の方が実装になっていると思います。但し、各CORBAオブジェクトは、Treadでの動作せず、ORBのイベントループで通信を制御されています。
このような実装は、本来の分散オブジェクトではありません。私の実装では、負荷分散ができないからです。本来、分散オブジェクトの目的の1つに動的な負荷分散機能も視野に入れられていると思います。最初の図のように実装していれば、ORBの部分で、負荷のすくないCORBAオブジェクトに処理を依頼するといったことも実現できると思います。RtORBでは、OpenRTM-aistの動作に必要な部分を最低限で実装したいという思いがあり、このような実装になっています。
スケルトンとスタブ
前述の図で、クライアントとなるオブジェクトに Stub、サーバーとなるオブジェクトにSkeletonと描いています。これらは、それぞれ次のような意味があります。
- Stub(スタブ)
- スタブとは、クライアントアプリケーションからみた時に、サーバーのオブジェクトがあたかも、自分のところで動作しているように見せる代理(Proxy)オブジェクトです。
- Skeleton(スケルトン)
- スケルトンとは、サーバ側の実装オブジェクト(図ではImpl)を外部のスタブオブジェクトから見えるように公開されたインターフェースオブジェクトです。
これらのスタブとスケルトンが、協調して動作することで、外部プロセス内のオブジェクトがあたかも自分のところで動いているように見せることができます。また、内部のオブジェクトであっても、これらの2つのオブジェクト経由でアクセスすることで、どこに実装があっても同じようにアクセスすることが可能になっています。
これらのオブジェクトは、IDLと呼ばれるインターフェース記述ファイルから、各CORBAライブラリに対応したIDLコンパイラによって生成されるプログラムです。サーバー側は、スケルトンのプラグラムファイル、クライアント側はスタブのプログラムファイルをコンパイルしリンクすることで利用することができます。
IDLファイルには、CORBAの通信で使うためのデータ型の定義、CORBAオブジェクトのクラス定義等が含まれ、IDLコンパイラによって、スタブファイル、スケルトンファイル、各ヘッダーファイルを出力します。このスタブファイルやスケルトンファイル等には、ユーザ定義のデータ型をCORBA内部で使うためにTypeCode型を新たに生成したり、通信時の符号化、復号化(marshaling/unmarshaling)のための変換プログラムコードが含まれるために、IDLコンパイラによっては、非常に大きなファイルを出力しています。RtORBでは、C言語とC++言語との両方に対応するコードが含まれますので、IDLファイルに比べて非常に大きなファイルが生成されます。また、オプションとして、実装クラスの雛形ファイルである、Steleton Implファイルも出力するものもあります。
RtORBでは、現在のところORBit2のIDLコンパイラを改変して利用しています。そのため、IDLファイルをコンパイルすると下記のファイルが出力されます。
- StubとSkeletonの共通ヘッダーファイル(C,C++共通)
- Stub用のプログラムファイル (-stubs.c)(C,C++共通)
- Skeleton用のプログラムファイル (-skels.c)(C,C++共通)
- StubとSkeletonの共通プログラムファイル (-common.c)(C,C++共通)
- Skeleton Impl用のテンプレートファイル (-skelimpl.c)(C言語では、これに実装を加えて、C++では、これと別に実装ファイルが必要)オプション指定で出力する。
GIOPとIIOPについて
GIOPとIIOPに関しては、いろいろな書籍やWebでも非常にたくさんの情報があります。詳細は、そちらを参照していただきたいのですが、簡単に言えば、GIOPは、CORBAでリモート呼び出しを行うときのプロトコルであり、IIOPは、GIOPをTCP/IP上に実装したものである。
RtORBでは、IIOPのみを実装しています。
ちなみに、GIOPは、''General Inter-ORB Protocol''であり、IIOPは、''Internet Inter-ORB Protocol''である。
GIOPの定義
OMGの定義では、 GIOP を以下の3つに分かれています。
- ''The Common Data Representation (CDR)の定義''
OMG IDL のデータ型を ORB 間などの低レベルな通信用表現にマッピング。引数や返値を送受信するためのmarshaling/unmarshaling に関する定義 - ''GIOP Message Formats''
CORBAクライアントとCORBAサーバー間で情報のやりとりを行うためのメッセージ様式。8つの形式(GIOP1.0では、7つの形式)のメッセージがあり、その中の2つはリモートプロシージャコールで必須のメッセージ(read/write)であり、それ以外は、メッセージのコントロールや最適化や信頼性を向上させるための補助メッセージである。 - ''GIOP Transport Assumptions''
GIOPメッセージの通信路がどのような機能をもつべきか、また、メッセージの送受信時における順序に関する規定。
これらの三つに加えて、IIOPでは、
- ''Internet IOP Message Transport''
GIOPメッセージを送受信するためのTCP/IP通信路をどのように開設し、メッセージを送受信するかの規定。がある。
また、''The Interoperable Object Reference (IOR)'' は、リモートオブジェクトへの参照の形式も規定されている。IOR は、マジックナンバー、バイトオーダー。タグ付きのプロファイル、プロトコルのバージョン、サーバのアドレスとポート番号、リモートオブジェクトを識別するバオブジェクトキー、コンポーネントのプロファイルを含んだバイト列になっている。要は、実装されているオブジェクトへのアクセスポート(IPアドレスとポート番号)オブジェクトキー、CORBAの実装などの情報がバイト列でひょうげんされたものであるので、これがわかれば、リモートアクセスができることになる。CORBAのNameServerに登録したり、入手するのはこの情報であり、CDR時のオブジェクトであると言える。
GIOPとIIOPは、全く別に規定されているものではなく、むしろGIOPのTCP/IP上の実装としてIIOPは存在している。現在では、CORBAの実装によっては、マルチキャスト上にGIOPを実装したMIOPなどもある。
GIOP Message Format
上述のように、GIOPメッセージには、8つのメッセージがある。これらのメッセー時によって、オブジェクト間のリクエストや実装している場所を探したり、通信経路を設定したりすることができる。GIOPのメッセージは、”GIOP"の文字列から始まる12バイトのヘッダーとメッセージ本体からなっており、メッセージ本体は、CDRで符号化された各種メッセージヘッダーと本体からなっている。GIOPヘッダーは、マジックナンバー、バージョン情報、CDR符号化処理時のバイトオーダー、メッセージのタイプ、メッセージ本体の長さからなっている。このヘッダーは固定長になっているので、通信時にはヘッダーを受信し、その後、リモート呼び出しを行うメソッド名や引数を受信し、処理後に応答を返すことになる。
RtORBでは、GIOP_execute_request関数でGIOPのメッセージに対応する処理を行っている。また、RtORBで実装しているGIOPメッセージを下記に示す。
RtORBでは、GIOP_execute_request関数でGIOPのメッセージに対応する処理を行っている。また、RtORBで実装しているGIOPメッセージを下記に示す。
typedef struct { octet major; octet minor; } GIOP_Version; typedef struct{ char magic[4]; GIOP_Version version; octet flags; /// GIOP1.0 --> byte_code octet message_type; unsigned long message_size; } GIOP_MessageHeader;
ここで、GIOPの本体のメッセージサイズは、flagに示されたバイトオーダーによって書かれているために、自身のバイトオーダーと異なる場合にが、逆順に並べ替える必要がある。
GIOP1.0では、flagはメッセージの符号化のバイトオーダーのみを表していたが、GIOP1.1以降では、flagの最下位のビットがバイトオーダーを表しており(0:BigEndian, 1:LittleEndian)、その上のビットは、分割されたメッセージが後に続いているかどうかをしてしている。(0であれば、メッセージの最後のバート、1であれば、分割されたメッセージが続いていることをあわらしている)GIOPのメッセージの種類は、下記のように定義されており、それぞれのメッセジージ対応する処理を実装していなければならない。
GIOP1.0では、flagはメッセージの符号化のバイトオーダーのみを表していたが、GIOP1.1以降では、flagの最下位のビットがバイトオーダーを表しており(0:BigEndian, 1:LittleEndian)、その上のビットは、分割されたメッセージが後に続いているかどうかをしてしている。(0であれば、メッセージの最後のバート、1であれば、分割されたメッセージが続いていることをあわらしている)GIOPのメッセージの種類は、下記のように定義されており、それぞれのメッセジージ対応する処理を実装していなければならない。
enum GIOP_MsgType{ GIOP_Request, // 0 Client GIOP_Reply, // 1 Server GIOP_CancelRequest, // 2 Client GIOP_LocateRequest, // 3 Client GIOP_LocateReply, // 4 Server GIOP_CloseConnection, // 5 Servar(or Client, from GIOP1.2) GIOP_MessageError, // 6 Client or Server GIOP_Fragment, // 7 Client or Server, from GIOP1.1 GIOP_MsgType_END // 8 };
上記のようにGIOPには、いくつかのバージョンが存在し、拡張されてきている。(現在でのその拡張は続いている。)
しかしながら、GIOP1.2等を実装する場合には、GIOP1.0, GIOP1.1といった以前のバージョンも実装することが義務付けられている。そのため、GIOP1.0を実装しておけば、最新のCORBAと通信可能になっている。
しかしながら、GIOP1.2等を実装する場合には、GIOP1.0, GIOP1.1といった以前のバージョンも実装することが義務付けられている。そのため、GIOP1.0を実装しておけば、最新のCORBAと通信可能になっている。
Request Message
Request Messageは、CORBAのリモートプロシージャコールの基本となる一つで、クライアントからサーバーへオペレーションの実行要求を送る場合に使われる。すなわち、原則として、Request Messageの符号化はクライアント、復号化はサーバーで行うことになる。原則というのは、GIOP1.2からBidirectional IIOP(双方向の送受信)ができるようになっており、コールバック関数を起動するときには、逆になるからである。
Request Messageは、下記の構造になっている。
12-bytes GIOP Header | Variable-length GIOP Request Header | Variable-length GIOP Request Body |
Request Message Headerは、CORBAの仕様書では、下記のように定義されてる。
struct RequestHeader_1_0 { // Renamed from RequestHeader IOP::ServiceContextList service_context; unsigned long request_id; boolean response_expected; IOP::ObjectKey object_key; string operation; CORBA::OctetSeq requesting_principal; }; typedef octet RequestReserved[3]; struct RequestHeader_1_1 { IOP::ServiceContextList service_context; unsigned long request_id; boolean response_expected; RequestReserved reserved; // Added in GIOP 1.1 IOP::ObjectKey object_key; string operation; CORBA::OctetSeq requesting_principal; }; // GIOP 1.2, 1.3 typedef short AddressingDisposition; const short KeyAddr = 0; const short ProfileAddr = 1; const short ReferenceAddr = 2; struct IORAddressingInfo { unsigned long selected_profile_index; IOP::IOR ior; }; union TargetAddress switch (AddressingDisposition) { case KeyAddr: IOP::ObjectKey object_key; case ProfileAddr: IOP::TaggedProfile profile; case ReferenceAddr: IORAddressingInfo ior; }; struct RequestHeader_1_2 { unsigned long unsigned long request_id octet response_flags; RequestReserved reserved // Added in GIOP1.1 TaggedAddress target; string operation; IOP::ServiceContextList service_context; // rquesting_principal not in GIOP 1.2 and 1.3 }; typedef RequestHeader_1_2 RequestHeader_1_3;
上記の構造からわかるように、Request Message Header は、GIOP1.2以降変更されている。また、各要素は次のように定義されている。
- request_id
- このIDは、Reply Messageの時のヘッダーに付与されるIDと同じものであり、クライアントが返信メッセージを同定するために使うために生成しているものである。このIDは、同一コミュニケーション中に対して再利用してはいけない。
- response_flags
- この要素は、メッセージが同期型なのかどうかを表している、この値が、0x00の時には、onewayのオペレーションのように、返信を要求しないようなリクエストになる。0x01の場合は、メッセージ本体をなしの返信をするといういみであり、このメッセージのReply Messageには、Reply Bodyがない。また、0x03の時には、返信を期待するという意味であり、通常の同期型リモートプロシジャーコールと同等である。この0x03は、GIOP1.0,1.1でのresponse_expected == TURE(1)と同値であり、0x00が response_expected == FALSE(0)と同値であることに注されたい。
- reserved
- この要素は、3バイト長であり、通常0が代入されている。現時点では使われていない。
- object_key
- この要素は、GIOP1.0,1.1でリモートプロシジャーコールをするオブジェクトを表すIDになる。通常は、サーバー内で対象オブジェクトを一意に同定するための文字列である。
- target
- この要素は、GIOP1.2以降でリモートプロシジャーコールをするオブジェクトを表すものである。object_keyがサーバー内で使っているIDに対して、この要素は、3通りの表現方法を提供する。この要素の型は、共用体になっており、''KeyAddr''の時は、GIOP1.0,1.1と同じようにサーバー内でのIDを表し、''ProfileAddr''の時には、IORと同値のTaggedProfile型の要素となる。''IORAddressingInfo''の場合には、完全なIORとなり、IORAddressingInfo型の要素となる。この要素のselected_profile_indexは、iorの何番目のProfileを用いるかを表しており、最初の要素の時は、0になる。
- operation
- この要素は、リモートプロシジャーコールの時のメソッドに対応する文字列になる。また、通常、オブジェクトのattributeの内容を得たり、変更する場合には、_get_<attribute>, _set_<attribute>をそれぞれ使用する。その他、GIOP1.1以前の仕様で実装されたCORBAの中には_non_existentのオペレーションが_not_existentになっている場合がある。これは、以前の資料で綴りミスであったために起こっている。以前のCORBAとの完全な互換性を確保する場合には、_non_existent,_not_existentの両方のメソッドを実装した方がよい。
- service_context
- これは、ServiceContextを表しており、クライアントからサーバーに対して渡される。通常、文字列のコーディングやセッション情報を渡されることがある。(RtORBでは、何も送らないようにしていますし、もし渡されても何も処理していません。)
- requesting_principal
- これは、BOAで呼び出しを行うクライアントを同定するために付け加えていたものであり、GIOP1.2以降は、省略されている。これは、ServiceContext でのOMG Security Serviceで同様なことができるためである。
GIOP1.0,1.1では、CDRを使って符号化されたRequest Message Bodyは、ヘッダーに続いて格納されれるが、GIOP1.2以降では、Request Message Bodyは、8 octetのAlignmentがある。Request Message Bodyは、IDLで規定されているinとinoutのすべてのパラメータと必要であれば、Content疑似オブジェクトをCDRで符号化したものが含まれている。通常の平易なリクエストでは、Context疑似オブジェクトは含まれない。
Reply Message
Reply Messageは、CORBAのリモートプロシージャコールの基本となる一つで、Request Messageに対する応答処理であり、サーバーからクライアントへ実行結果を返送する場合に使われる。そのため、Request Messageとは逆に、原則として、Request Messageの符号化はサーバー、復号化はクライアントで行うことになる。このメッセージが発行されるのは、Request Message でresponse_flagsまたはrequest_expectedが0x00でない場合である。
Reply Messageは、基本的に下記のような構造になっている。
12-bytes GIOP Header | Variable-length GIOP Reply Header | Variable-length GIOP Reply Body |
Reply Message Headerは、CORBAの仕様書では、下記のように定義されてる。
#if MAX_GIOP_MINOR_VERSION < 2 // GIOP 1.0 and 1.1 enum ReplyStatusType_1_0 { // Renamed from ReplyStatusType NO_EXCEPTION, USER_EXCEPTION, SYSTEM_EXCEPTION, LOCATION_FORWARD }; // GIOP 1.0 struct ReplyHeader_1_0 { // Renamed from ReplyHeader IOP::ServiceContextList service_context; unsigned long request_id; ReplyStatusType_1_0 reply_status; }; // GIOP 1.1 typedef ReplyHeader_1_0 ReplyHeader_1_1; // Same Header contents for 1.0 and 1.1 #endif // MAX_GIOP_VERSION_NUMBER #if MAX_GIOP_MINOR_VERSION >= 2 // GIOP 1.2, 1.3 enum ReplyStatusType_1_2 { NO_EXCEPTION, USER_EXCEPTION, SYSTEM_EXCEPTION, LOCATION_FORWARD, LOCATION_FORWARD_PERM,// new value for 1.2 NEEDS_ADDRESSING_MODE // new value for 1.2 }; struct ReplyHeader_1_2 { unsigned long request_id; ReplyStatusType_1_2 reply_status; IOP::ServiceContextList service_context; }; typedef ReplyHeader_1_2 ReplyHeader_1_3; #endif // MAX_GIOP_VERSION_NUMBER
また、各要素は次のように定義されている。
- request_id
- これは、Request Messageの''request_id''に対応するもので、同じ値になる。
- reply_status
- これは、リクエスト終了時の状態であり、正常終了なのか例外が発生しているかを示している。この値が''NO_EXCEPTION''と等しければ、正常終了を表しており、返り値等を含んだReply Message Bodyを持つことになる。それ以外であれば、例外の内容、再実行のためのサーバーのオブジェクト参照などが含まれる。
- service_context
- これは、サーバーからクライアントに送られるサービスデータである。このヘッダーに続いてReply Message Bodyが格納される。
GIOP1.0,1.1では、Reply Message Headerに続いて、CDRで符号化されたReply Message Bodyが格納される。GIOP1.2以降では、Reply Message Bodyも8-octet Boundaryがあるために、Alignment をそろえる必要がある。また、Reply Message Headerには、reply_statuに応じて下記のようなデータが含まれる。
- ''NO_EXCEPTIONの場合'':
この場合は、リモートプロシジャーコールの正常終了を意味し、返り値とIDLで規定したinout,outのが順にCDRで符号化されたものが格納されている。 - ''USER_EXCEPTIONまたはSYSTEM_EXCEPTIONの場合'':
この場合には、リモートプロシジャーコールの時に、例外が発生したことを意味し、例外情報が含まれる。'SYSTEM_EXCEPTION''のときには、下記の構造体のデータが含まれるが、USER_EXCEPTIONの場合には、IDLで規定されてたException構造体になる。struct SystemExceptionReplyBody { string exception_id; unsigned long minor_code_value; unsigned long completion_status; };
この構造体で、minor_code_valueの上位20ビットは、Vendor Minor Codeset ID(VMCID)であり、下位12ビットには、minor codeが格納される。VMCIDは、ベンダーに対するIDであり、OMGで一意にきめられたもので、4096までのminor codeを使用することになる。VMCIDが0は、予約されていない任意のベンダーになるが、発行ベンダーがわからなくなるために正式には、その仕様は奨励されていない。 - ''LOCATION_FORWARDの場合'':
この場合には、IORがその内容に含まれる。ここで示されたIORは、同じリクエストをこのオブジェクトに対して再送することが期待されている。 - ''LOCATION_FORWARD_PERMの場合'':
これは、GIOP1.2以降に追加された。これは、LOCATION_FORWARDに似ているが、リクエストはもここで返されたIORも同時に有効であるが、将来的に、ここで返されたIORを使うことを奨励すると言うことを意味している。 - ''NEED_ADDRESSING_MODEの場合'':
これも、GIOP1.2以降に追加された。この返信には、GIOP::AddressingDisposition型のデータが含まれており、クラインとは、これをもとにしたリクエストを再送することが期待されている。
CancelRequest Message
このメッセージは、GIOP1.0, 1.1では、クライアントからサーバに対してのみ送られるものであったが、GIOP1.2以降からは、双方向で送られることになった。CaneclRequest Messageは、RequestやLocateRequestを中止したいとき(relplyを受けたくないとき)に送信する。基本的な構造は、下記のようになっており、GIOP HeaderとCancelRequest Headerからなるメッセージである。
12-bytes GIOP Header | 4-bytes GIOP CancelRequest Header |
struct CancelRequestHeader { unsigned long request_id; };
LocateRequest Message
このメッセージは、クライアントからサーバーに対して送られるメッセージであり、対象オブジェクトがそこに存在するか銅貨を確認することができる。Request Messageを発行しても、受けるオブジェクトが存在しなければ、代替IORを送信する機能はあるのだが、その場合にはクライアントに再送機能がついていなければならない。その機能を実装していないCORBAを利用する場合には、まず、このメッセージで対象オブジェクトの所在を確認して、Requestする必要がある。
LocateRequest Messageは、下記の構造を持っており、object_keyまたはtargetがLocationRequest Headerに埋め込まれ、本体はない。これらの要素の意味は、Request Message Headerに記載したものと同じである。
12-bytes GIOP Header | Variable-length GIOP LocationRequest Header |
// GIOP 1.0 struct LocateRequestHeader_1_0 {// Renamed LocationRequestHeader unsigned long request_id; IOP::ObjectKey object_key; }; // GIOP 1.1 typedef LocateRequestHeader_1_0 LocateRequestHeader_1_1; // Same Header contents for 1.0 and 1.1 // GIOP 1.2, 1.3 struct LocateRequestHeader_1_2 { unsigned long request_id; TargetAddress target; }; typedef LocateRequestHeader_1_2 LocateRequestHeader_1_3;
LocateReply Message
このメッセージは、前述のLocateRequest Messsageの返信として発行される。そのため、サーバーからクライアントに対してのみ発行される。このメッセージの構造は、下記のようになっている。
12-bytes GIOP Header | 8-bytes GIOP LocationReply Header | Variable-length GIOP LocationReply Body |
LocateReply Message Headerは、仕様書に下記のように記載されている。LocateReply Message Headerには、LocateRequest Messageで使ったrequest_idとLocateStatusのIDが格納されている。
#if MAX_GIOP_MINOR_VERSION < 2 // GIOP 1.0 and 1.1 enum LocateStatusType_1_0 {// Renamed from LocateStatusType UNKNOWN_OBJECT, OBJECT_HERE, OBJECT_FORWARD }; // GIOP 1.0 struct LocateReplyHeader_1_0 {// Renamed from LocateReplyHeader unsigned long request_id; LocateStatusType_1_0 locate_status; }; // GIOP 1.1 typedef LocateReplyHeader_1_0 LocateReplyHeader_1_1; // same Header contents for 1.0 and 1.1 #else // GIOP 1.2, 1.3 enum LocateStatusType_1_2 { UNKNOWN_OBJECT, OBJECT_HERE, OBJECT_FORWARD, OBJECT_FORWARD_PERM, // new value for GIOP 1.2 LOC_SYSTEM_EXCEPTION, // new value for GIOP 1.2 LOC_NEEDS_ADDRESSING_MODE // new value for GIOP 1.2 }; struct LocateReplyHeader_1_2 { unsigned long request_id; LocateStatusType_1_2 locate_status; }; typedef LocateReplyHeader_1_2 LocateReplyHeader_1_3; #endif // MAX_GIOP_VERSION_NUMBER
LocateStatusは、次のような意味をもつ
- ''UNKNOWN_OBJECTの場合'':
これは指定されたオブジェクトに関して、サーバーがわからない場合に発行される。このときLocateReply Message Bodyの中身はない。 - ''OBJECT_HEREの場合'':
これは、サーバー上に指定されたオブジェクトが存在していることを示している。このときもLocateReply Message Bodyの中身はない。 - ''OBJECR_FORWARDまたはOBJECT_FORWARD_PERMの場合'':この場合にはLocateReply Message Bodyが存在し、その内容は、対象となるオブジェクトのIORである。これは、Reply Messageの時と同じである。
- ''LOC_SYSTEM_EXCEPTIONの場合'':この場合にはLocateRequest Messageの実行に対して例外が発生したことを示している。このとき、LocateReply Message Bodyが存在し、その内容は、GIOP::SystemExceptionReplyBodyになる。
- ''LOC_NEEDS_ADDRESSING_MODEの場合'':この場合にはLocateReply Message Bodyが存在し、その内容は対象となるオブジェクトの所在をGIOP::AddressingDispositionで表現されたものであり、Reply MessageのNEED_ADDRESSING_MODE時と同じである。
CloseConnection Message
CloseConnection Messageは、GIOP1.1以前のバージョンでは、サーバーのみが使用するメッセージであり、通信路を遮断するためにクライアントへ送るメッセージである。このメッセージは、GIOP Headerのみからなる。GIOP 1.2以降のバージョンでは、双方向の通信をサポートするために、サーバーとクライアントの両方で使用する。
MessageError Message
MessageError Messageは、全てのGIOPメッセージの応答として発行されるものである、バージョン番号やメッセージタイプが受け入れられなかったり、ヘッダーの不備(マジックナンバーがことなるなど)の時に発行される。このメッセージもGIOPヘッダーのみならなる。
Fragment Message
Fragment Messageは、GIOP1.1から付け加えられた機能である。これは、CORBAの送受信バッファが限られている場合に、GIOPメッセージを分割して送って良いように決められたものである。このFragmentを使う場合のは、逐次的にデータを復号する場合に、全体のメッセージを受信する前に復号処理を始めることが可能になり、より省資源の計算機上でも実装できるようになっている。但し、これは、CORBAの実装に依存しており、RtORBでは、現在のところ実装されていない。
GIOP1.1では、RequestとReplyの2つのメッセージについてFragmentメッセージが許可されていた。この場合、最初のメッセージは、通常のRequstまたはReplyメッセージであり、そのGIOP Message Headerのflagのfragment bit(下から2番目のビット)がTRUE(1)になっており、続くFragmentメッセージで最後のFragmentを除いては、最初のメッセージと同様にflagのfragment bitがTRUE(1)になっている。最後のメッセージのみFALSE(0)にすることで、メッセージの終端を表している。受信側は、すべてのメッセージを受け取った後に、RequestまたはReplyの処理を開始する。
また、途中でCancelRequest Messageを送信側に送れば、それ以降のFragment Message は送信されないし、8バイト以下の基本データは、分割することはできない。
GIOP1.1では、それぞれのFragmentメッセージ中のデータのAlignmentは、全体の(すべて結合されたときの)Alignmentではなく、Fragment MassageごとのAligmentを行う。
GIOP1.2以降では、RequestとReplyの他にLocateRequestとLocateReplyのFragmentも可能になった。そのメッセージの構造は、下記のようになっている。
12-bytes GIOP Header | 4-bytes GIOP Fragment Header | Variable-length GIOP Fragment Body |
また、FragmentHeaderは、仕様書には下記のように記載されている。
struct FragmentHeader_1_2 { unsigned long request_id; }; typedef FragmentHeader_1_2 FragmentHeader_1_3;
このように、GIOP1.2以降では、FragmentHeaderがGIOP Message HeaderとFragmanet Messageの間に挿入されている。このFragmentHeaderのrequest_idは、Fragmentの最初のデータのrequest_idである。また、GIOP1.2以降では、Fragment Messageの長さは、最後のFragment Maessage 以外は、必ず8バイトの倍数のなっているので、メッセージデータを復元するのにAlignmentの調整は不要である。(CDRのAlignmentの最小公倍数が8であるため)
つまり、GIOP1.1の場合のみ、Fragmentメッセージを復号化するには、オリジナルのrequest_idが不明になり、Alignmentも逐次処理を進めなければならないため、少し手間がかかることになる。Javaのソースコードを見てみると、GIOP1.1でFragmentのサポートがされていないのは、このためなのだろうか?(GIOP1.1でFragment Messageを送ると SystemErrorになるようである)
Common Data Representation(CDR)
CDRは、OMGのGIOPの仕様で規定されている。データの表現情報の一つである。このCDRの表現を用いて、リモートオブジェクトへのデータの受渡しや結果の受信をおこなっている。このCDRは、送受信に使われるために、marshalingとunmarshalingによる、符号化と復号化に関する規定である。
RtORBでは、CDRに関しては、giop-marshal.cの中に記述されている。
データ型
データの符号化と復号化の説明の前に、CORBAで使用するデータタイプについて述べる。CORBAで扱うデータは、charやintのようなプリミティブ型とstructやunion,sequenceなどのようにIDLで設定するコンストラクト型に大別される。これの他には、データ型を表すTypeCodeやCORBAオブジェクト、すべての型をあらわすany型などのような擬似オブジェクト型がある。プリミティブ型はCORBAの実装で予め決められているデータ表現であり、バイト列に符号化する時に置くことができる場所が決められている。これはAlignmentと呼ばれており、データの符号化、復号化のプログラムを書くときに気をつけなければならない。
CORBAで扱うデータ長とAlignmentは、基本的に32ビットのUnixOSのデータ構造であると考えてほぼ間違いがない。(「ほぼ」と述べたのは、Sequence型やString型などには、データ本体の前にデータ長の32ビット整数を入れたり、struct型では、それぞれのデータ型を順に符号化するので、厳密にはコンピュータ上のデータ構造とは異なる。)そのため、LongDouble型のデータでもAlignmentは16ではなく8になっている。(すなわち、Alignmentは8を上限としている)
したがって、最近の64ビットのマシン上でCORBAを実装するには注意する必要がある。
プリミティブ型
プリミティブ型には、下記ようにAlignmentが決められている。 各型のRtORBへのマッピングも示す。
TYPE | データ長 | Alignment | RtORBにおけるマッピング |
---|---|---|---|
char | 1 | 1 | char |
wchar | 2 | 1, 2 or 4 for GIOP 1.1 1 for GIOP 1.2 and lator | unsigned short |
octet | 1 | 1 | char |
short | 2 | 2 | short |
unsigned short | 2 | 2 | unsigned short |
long | 4 | 4 | int |
unsigned long | 4 | 4 | unsigned int |
long long | 8 | 8 | なし |
unsigned long long | 8 | 8 | なし |
float | 4 | 4 | float |
double | 8 | 8 | double |
long double | 16 | 8 | なし |
boolean | 1 | 1 | char |
上記のことから、例えば、charとlongの2つのデータをバイト列に符号化する場合には、
- char,longの順:1バイト目にchar, 5バイト目から8バイト目にlongのデータが格納される。
0 1 2 3 4 5 6 7 c long
- long,charの順:1バイト目にlong, 5バイト目にcharのデータが格納される。
0 1 2 3 4 long c
のように符号化した場合のデータ長が変わってくる。
コンストラクト型
コンストラクタ型のデータ型では、Alignmentは特に決まっていません。これは、符号化する際に、どの様なデータに並べられているかによるからです。各データ型のAlignmentは、それを符号化の際に、その中のプリミティブ型のAlignmentに従うと思っていればよいと思います。
String型とWstring型
String型とWstring型は、通常、C言語では、それぞれ"unsigned char*"と”wchar_t *"にマッピングされる。RtORBでは、Wstring型を"unsigned short *"にマッピングしているが、これは、仮対応であるので正しい動作はしない。String型のデータは、バイト列への符号化する場合には、''文字列の長さ+文字列'' となる。''文字列の長さ''および''文字列''には、終端記号である''\0''が含まれるので、注意すべきである。このことからString型では、Alignmentは、longと同じ ”4” になる。
Wstring型に関しては、少し複雑であるためにC言語を使う場合には、現在のところ該当するコードが実装されていない。CORBAにおける文字列の扱いは、仕様書のCord Set Conversionに詳しく書いてある。簡単に言えば、文字列には、2種類あり、バイト単位で扱えるもの(byte-oriented)とそうでないもの(non-byte-oriented)がある。バイト単位で扱えるものには、single-byteのASCII,ISAO 8859-1 ,EBCDIC等とmuiti-byteのUTF-8,eucJP,Shift-JIS, JIS等がある。これらのコードを扱うだけであれば、String型のサポートでCord Set Conversionは、何もしないように動作させればよい。バイト単位で扱えない文字列は、Wstring型がそうであるが、ISO 10646 USC-2(Unicode)やUTF-16などがこれにあたる。この文字列を取り扱う場合には、クライアントとサーバー間でCord Set Conversionをどうするかを事前に決めておく必要がある。さらに、文字列の取り扱いについては、GIOPの1.2以降でその仕様が変更されているために、すべてのものを実装しても、使用頻度とアプリケーションを考えると、無理に実装する必要ないかと思っています。どうしても必要な場合は、UTF-16に対する対応ぐらいは考えてもよいと思っています。
詳細は、OMGから公開されてる仕様書を読んでいただく方がよいと思います。
Struct型
構造体は、C言語へのマッピングは、ほぼそのままです。すなわち構造体の各データ型をそのまま置き換えるということです。そのため、Alignmentも構成されたデータ型によって変わります。
RtORBのC++のラッパーでは、オブジェクト呼出の引数のタイプ(in, out, inout)によって振舞が変わりますし、_var, _ptr 等のデータ型もありますので、アクセス用のテンプレートクラスの定義をcorba-struct.hhで行っています。詳細は、ソースコードを参照して下さい。
OpenRTM-aistでは、データの送受信時に、すべてのデータをCDRで符号化しています。OpenRTM-aistのソースコードを読めば分かるのですが、その中では、CORBAの実装であるomniORBのcdrStreamクラスを利用しています。RtORBでも、このcdrStreamとほぼ同じ機能を再実装していますので、同じ機能を実現した関数が、giop-marshal.cとcdrStream.cpp, cdrStream.hに入っています。今後は、ソースコードを整理す必要があるかもしれません。
Union型
共用体は、C言語の標準マッピングでは、そのままunionとなっています。当初は、RtORBでもunion型へマッピングしていましたが、OpenRTM-aistの下位層で使用するために、格納されたデータ型の種類を表すindexと構成要素を各要素とした構造体になる。
例えば、
enum NumericType { SHORT_TYPE, LONG_TYPE, FLOAT_TYPE, DOUBLE_TYPE }; union Numeric switch (NumericType){ case SHORT_TYPE: short short_value; case LONG_TYPE: long long_value; case FLOAT_TYPE: float float_value; case DOUBLE_TYPE: double double_value; };
とIDLで定義さえていれば、IDLコンパイラにより
enum { SHORT_TYPE, LONG_TYPE, FLOAT_TYPE, DOUBLE_TYPE }; typedef int NumericType; typedef Numeric_type Numeric; struct Numeric_type{ NumericType __d; struct{ CORBA_short short_value; CORBA_long long_value; CORBA_float float_value; COBRA_double double_value; } _u; };
となる。これは、C++のラッパーを作成するときに、コンストラクタをもつ構造体に共用体を内部に持つことが禁止されているためである。
Union型を符号化する場合には、''現在値が入っている型を表すindex(__d)'' + ''データ(_u.xx)'' になる。
Sequence型
シーケンス型は、いわゆる可変長配列である。C++では、 vectorクラスとよく似ている。例えば、
struct NmaeValue{ string name; any value; }; typedef seqnece<NameValue> NVList;
は、IDLコンパイラによって下記のようになる。
typedef struct NameValue_type NameValue; struct NameValue_type{ CORBA_string name; CORBA_any value; }; typedef struct CORBA_sequence_NameValue_type{ CORBA_unsigned_long _maximum, _length; NameValue *_buffer; CORBA_unsigned_long _release; } CORBA_sequence_NameValue; typedef CORBA_sequence_NameValue NVList;
Seqeunce型を符号化する場合には、''Sequenceの長さ'' + ''構成された値の列'' であり、String型を汎用化したものと考えて良い。Sequence型のAlignmentは、長さ、値のそれぞれのAlignmentに従うことになる。
Enum型
Enum型は、CORBAでは同じenumにマッピングされる。C言語の場合には、int にtypedef されている。Enum型の符号化は、longの場合と同じになる。
擬似オブジェクト型
擬似オブジェクト型は、CORBAにおける”型”を表す TypeCode、すべてのデータ型を表せすAny,例外を表すException、CORBAオブジェクトを表すObjectReferenceなどがある。この他にPrincipalやContextなどがある。
TypeCode型
TypeCodeは、CORBAで使用する”型”を表す。RtORBでは、TypeCodeを表す構造体をcorba-defs.hに下記のように記載されている。
typedef struct CORBA_TypeCode_struct{ void * parents; CORBA_TCKind kind; CORBA_RepositoryId repository_id; CORBA_Identifier identifier; unsigned long member_count; /* struct, union, enum, value, exception, event */ CORBA_Identifier *member_name; /* struct, union, enum, value, exception, event */ struct CORBA_TypeCode_struct **member_type; /* struct, union, value, exception, event */ long *member_label; /* union */ struct CORBA_TypeCode_struct *discriminator; /* union */ long default_index; /* union */ unsigned long length; /* string, wstring, sequence, array */ struct CORBA_TypeCode_struct *content_type; /* sequence, array, value_box, alias */ unsigned short fixed_digits; /* fixed */ short fixed_scale; /* fixed */ CORBA_Visibility *member_visibility; /* value, event */ CORBA_ValueModifier type_modifier; /* value, event */ struct CORBA_TypeCode_struct *concrete_base_type; /* value, event */ short size; short alignment; #ifdef __cplusplus const char *id() const { return repository_id; } const char *name() const { return identifier; } #endif }CORBA_TypeCode_struct;
このTypeCodeの定義自体は、ORBit2で用いられているものとほぼ同じである。これは、基本的に、RtORBのIDLコンパイラがORBit2で用いられてものを流用し、独自部分を付加するように作成したためである。また、上記の定義からわかるが、TypeCodeの一意性は、kind, repository_id, identifierで表されている。
TypeCodeの符号化については、その型によって変わる。先ず、kindのみの符号化で済むのは、tk_null, tk_void, tk_short, tk_long, tk_ushort,tk_ulong, tk_float, tk_double, tk_boolean, tk_char, tk_octet,tk_any, tk_TypeCode, tk_Principal, tk_longlong, tk_ulonglong, tk_longdoubleである。また、kind + (long)0 で符号化されるのは,tk_string, tk_wstringである。それ以外のTypeCodeは、それぞれ符号化の方法が異なっている。
Any型
Any型は、C言語で言えば汎用ポインタ(void *)になるが、CORBAでは、内容の型を表すTypeCodeと値を表す汎用ポインタからなる。Any型の定義は、corba-defs.hに下記のように記載されてる。
typedef union { CORBA_boolean val_bool; CORBA_octet val_octet; CORBA_char val_char; CORBA_unsigned_long val_ulong; char * val_str; struct CORBA_Object_struct * val_obj; void * val_except; /* tk_struct */ struct val_t { int len; char * data; } val_encoded; } CORBA_any_val; typedef struct CORBA_any { struct CORBA_TypeCode_struct *_type; CORBA_any_val *_val; CORBA_unsigned_long _release; #ifdef __cplusplus CORBA_any(); CORBA_any(const CORBA_any &o); ~CORBA_any(); CORBA_boolean hasData(struct CORBA_TypeCode_struct *) const; void setData(struct CORBA_TypeCode_struct *tc, char *data, int len); void copy_val(CORBA_TypeCode_struct *, CORBA_any_val *); void set_val(CORBA_TypeCode_struct *, CORBA_any_val *); void alloc(CORBA_TypeCode_struct *); void free_(); CORBA_any & operator=(const CORBA_any &o); CORBA_any * duplicate(); CORBA_TypeCode_struct * type() { return _type; } struct from_char { from_char(CORBA_char v) : val(v) {}; CORBA_char val; }; struct from_boolean { from_boolean(CORBA_boolean v) : val(v) {}; CORBA_boolean val; }; struct from_octet { from_octet(CORBA_octet v) : val(v) {}; CORBA_octet val; }; struct from_string { from_string(const char *v) : val(v) {} const char *val; }; struct from_object { from_object(CORBA::Object_ptr &ptr_); CORBA::Object_ptr &ptr; }; struct from_any { from_any(CORBA_any *v) : val(v) {} CORBA_any * val; }; void operator<<=(from_char); void operator<<=(from_boolean); void operator<<=(from_octet); void operator<<=(from_string); void operator<<=(from_object); void operator<<=(from_any); void operator<<=(CORBA_unsigned_long); struct to_char { to_char(CORBA_char &buf_) : buf(buf_){} CORBA_char &buf; }; struct to_octet { to_octet(CORBA_octet &buf_) : buf(buf_){} CORBA_octet &buf; }; struct to_boolean { to_boolean(CORBA_boolean &buf_) : buf(buf_){} CORBA_boolean &buf; }; struct to_string { to_string(char *& buf_) : buf(buf_){} to_string(const char *& buf_) : buf((char *&)buf_){} char * & buf; }; struct to_object { to_object(CORBA::Object_ptr &ptr_) : ptr(ptr_) {} CORBA::Object_ptr &ptr; }; CORBA_boolean operator>>=(to_char o) const; CORBA_boolean operator>>=(to_boolean o) const; CORBA_boolean operator>>=(to_octet o) const; CORBA_boolean operator>>=(to_string o) const; CORBA_boolean operator>>=(to_object o) const; CORBA_boolean operator>>=(CORBA_any &o) const; CORBA_boolean operator>>=(CORBA_unsigned_long &n) const; #endif } CORBA_any ;
この表現は、完全ではないが、OpenRTM-aistで用いるには、十分であるように実装されている。OpenRTM-aistでは、現在Any型を非常に限定された場所でしか使用していないため、限定的にさらに簡易な実装も可能である。
Any型の符号化では、まずそのデータのTypeCodeが符号化され続いて、そのデータが符号化される。RtORBにおいてのAny型の実装に関しては、OpenRTM-aistで利用している範囲では、ほぼ正常に動作しているが、完全ではない。
Exception型
Exceptionは、CORBAの関数呼び出し時に発生する例外を表す。RtORBでは、Exceptionは、それを一意にあらわる文字列をもつ構造体としている。
Object Reference
これは、CORBAオブジェクトを表す。オブジェクトリファレンスは、The Interoperable Object Reference (IOR)のことであり、マジックナンバー、バイトオーダー。タグ付きのプロファイル、プロトコルのバージョン、サーバのアドレスとポート番号、リモートオブジェクトを識別するバオブジェクトキー、コンポーネントのプロファイルを含んだ印字可能な文字列になっている。
オブジェクトリファレンスは、文字列であるが、通常の文字列と異なり、その長さを符号化しない。これは、オブジェクトリファレンスがバイト列は、カプセル化されたものになっているからであり、その情報のみで復号可能だからである。
ちなみに、CORBAオブジェクトのリファレンス表現でよく出てくる IOR:..... という表現は、”IOR:” 以降の文字列をバイト列に変換して復号化すればよい。つまり ”01000000” という文字列は、 01000000 というバイト列になる。
データの符号化(marshling)
データの符号化は、CORBAオブジェクトのメソッド呼出の時、クライアントとサーバーの双方で、データの受け渡しをするときに行われる。CORBAでは、すべてのデータは、バイト列に符号化され、上述のように符号化するデータの型によってAlignmentが予め決められている。また、データの符号化は、通常、符号化するコンピュータ上のデータ形式で行われるために、メッセージの送受信時には、ヘッダー等に符号化したときのendian情報が付加される。
データの復号化(unmarshaling)
データの復号化は、上述のバイト列で送られてきたデータを、元の形に復元することである。送られてきたデータは、通常、送信元のデータ形式で符号化されているために、ヘッダー等に付加された endianフラグに基づき復号化が行われる。
CORBAを実装するには…
ここまで、CORBAの概要について、述べてきました。では、CORBAを自分で実装するには、どうしたらよいでしょうか?
上で述べたように、CORBAは、IIOPのみをかんがえるとと、TCPソケットストリームを使ってのGIOPメッセージの交換であり、そのメッセージの内容に応じて、クライアントやサーバーの振舞うものであると言えます。
そのため、従来のクライアントーサーバー型のプログラム(Webサーバーなど)にGIOPメッセージの解釈ルーチン、データの符号化と復号化、オブジェクトの管理などを加えればできそうです。
RtORBでは、この考えに基づき、クライアントーサーバーのプログラムにオブジェクト管理の部分や、GIOPメッセージ、データの処理部分を実装しています。クライアントーサーバーのプログラムに関しては、いろんな実装があると思いますが、以前、事情通ロボットの研究で使っていたライブラリを基本に実装を積み重ねています。そのため、極力Threadを使わない実装をしていますが、そのためにリモート呼び出しに制限が付いています。 (Pthreadが実装されているOSがでは、この制限をなくすこともできますが…)
私の場合には、たまたまC言語を選択して実装を行いましたが、基本的な考え方はクライアントーサーバープログラミングの応用ですので、クライアントーサーバーのネットワークプログラミングを書くことができる言語であれば、どれでも実装できると思います。