Jini・RMI入門’(その2)
前回は、javaの分散環境でのオブジェクトの転送について説明した。その要点は、
l 転送先でオブジェクトを参照するためには、インタフェースのみを共有しておけばよい。これは、Javaのinterfaceを用いて実現されている。実際のコード(の実装)に関しては転送される側は知る必要はない。
l Javaのオブジェクトの転送機構であるObjectStreamはオブジェクトのクラス名とデータのみを転送する。したがって、転送されたオブジェクトを実際に動作させる(例えば、メソッドを呼び出す)場合にはコードを転送する必要がある。
l コードを転送するためにクラスファイルを転送する機構を用意する必要がある。通常、このためにhttpサーバを用いる。これを自動的に行うクラスがMarshalledObjectStreamである。実行時にjava.rmi.server.codebaseに指定する。
これらの機構は、Javaの特徴的な機構であり、オブジェクトをネットワーク中で自由に転送することを可能にしている。RMIの引数や結果の転送に利用されている。
RMIの概要
RMIとはRemote Method Invocationの略であり、Javaの分散プログラミングのための仕掛けである。この仕掛けをつかうことによって、いろいろなマシンにオブジェクトのインスタンスを生成し、これらの間でRMIを使って他のマシンのオブジェクトのメソッドを呼び出すことによって、分散システムを構築することができる。
オブジェクトの転送では転送されたオブジェクトのメソッドを呼び出し、いろいろな操作をするものであるが、RMIはリモートにあるオブジェクトのメソッドを呼び出す。以下の手順で行う。
1.
インタフェースをRemoteインタフェースをextendして定義する。これをクライアント、サーバ、双方に置く。
2.
サーバ側にはリモートのオブジェクトを管理するプロセスであるrmiregistryを起動しておく。
3.
また、サーバ側に仲介するプログラムであるstubを生成するプログラムであるrmicをつかって、stubを生成しておく。このプログラムは、Remoteインタフェースから、スタブをプログラムを生成する。スケルトン_Skel.class とスタブ_Stub.classが生成される。
4.
サーバー側のオブジェクトは、UnicastRemoteObjectをsuperクラスとして作成し、サーバ側ではリモートのオブジェクトを登録する。
5.
クライアント側では登録されているオブジェクトを取り出し、インタフェースを使って呼び出す。
サーバ側のプログラムでは、リモートのオブジェクトを登録するために、
ShowDateImpl sdi =
new ShowDateImp();
Naming.rebind(“//localhost/TimeSever”,sdi)
で、登録している。このプログラムでは、前の例のようにcodebaseやpolicyを指定して、起動しなくてはならない。例えば、
java
–Djava.rmi.sever.codebase=file:/home/msato/java/my-jini/
-Djava.security.policy=policy.txt ShowDateImpl
というように、コードのベースを指定する。これはhttpを含むURLでもよい。
クライアントプログラムでは、
obj = (ShowDate) Naming.lookup(“rmi://localhost/TimeSever”);
として、登録されているオブジェクトへの参照を得ることができる。これに対し、obj.getMills()と呼び出すことによって、サーバー側に登録されているリモートのオブジェクトのメソッドが起動されて、それらの引数、結果はオブジェクトとして転送される。内部では、指定されているホスト(ここではlocalhost)で実行されているrmiregistryに接続し、TimeSeverという名前で登録されているリモートオブジェクトから、スケルトンのクラスをクライアントに転送する。このスタブは同じインタフェースをもち、引数をMarshallObjectとしてリモートオブジェクトに転送する。その後に対応するスタブを通じて、オブジェクトのメソッドを呼び出している。
Activation
前の例では、サーバー側のプログラムがrmiregistryに登録されるとリモートの呼び出しをずっと待つために待機している。しかし、いろいろなサービスを考えるといろいろなプロセスを起動しておかなくてはならなくなり、不便である。そこで、UnicastRemoteの代わりにjava.rmi.activation.Activatableというクラスを使えば、デーモンrmidを通じて、呼び出し時に起動させることができる。以下の手順で作る。
l
java.rmi.activation.Actvatableをextendsしてクラスを作る。
l
コンストラクタとして、IDと引数データを引数とするコンストラクターを定義する。
l
activationGroupのインスタンスを生成する。これは、policyや実行環境を定義するものである。
l
activation
groupに登録し、IDを取得し、これを使ってグループを生成する。デフォールトのグループに登録。
l
activation
descriptorを生成する。これには、クラスの名前、クラスがロードされるべきcodebase、コンストラクタに渡される引数を指定する。activationGroupが指定しない場合にはデフォールトのgroupが使われる。
l
descriptorをrmidに登録する。ここにstubが返される。
l
これをName.bindで、rmiregistryに登録する。
l
あとは、プログラムは終了してよい。
このプログラムではrmidデーモンを用いるが、このデーモンがidとの対応をとり、ファイルに登録されているオブジェクトを起動する。rmidにもpolicyをしてしておくことを忘れずに。
Jini
Jiniは以下の3つの部分からなっている。
1.
JCP(The
Jini Technology Core Platform)
2.
JXP (The
Jini Technology Extended Platform)
3.
JSK(The
Jini Technology Software Kit)
4.
JSTK(The
JavaSpace Technology Kit)
ここでは、上の3つを用いる。Jiniを使う前に、以下の手順でLookupサービスを立ち上げる。
l
HTTPサーバの立ち上げ:Jini自体のプログラムもいろいろなところで動かすことができるようにするために、httpdからロードできるようにhttpサーバを立ち上げておく。
l
RMI
Activation Demon: Lookupサービスはactivationをつかっているので、rmidを立ち上げておく。
l
Jini
Lookupサービスの立ち上げ:Jiniではいくつかのlookupサービスのサーバがあるが、その一つであるreggieを以下のようにして立ち上げる。
Java –jar
reggie.jar http://hostname/reggie-dl.jar
policy log_file myName
ここで、http:は上のhttpdが立ち上がっているホストを指定する。
このほかに、Lookupサービスを立ち上げるためのGUIや、状態を確認するBrowserがある。
Jiniの中核になるアイデアはいろいろなサービスを検索して、必要なサービスを利用できる環境を提供することである。さて、実際のプログラムをみてみることにしよう。最初の例は、TimeServiceオブジェクト(以前、ShowDateと読んでいたものと同じ)を登録し、それを利用する例である。まず、サーバ側は、
l
登録するLookupサーバに対して、LookupLocatorを作成し、ここから、Registrarを取得する。
l
TimeServiceとプロパティからServiceItemを作成し、このregistrarに対し、登録する。
このサーバーを動かすためには、このプログラムをコンパイルし、起動する際に、このプログラムのクラスを供給するhttpサーバを用意しておかなくてはならない。このプログラムをLookupサービスがもちいているhttpサーバと共用してもいいが、別でもよい。例えば、別にして8081にこのクラスのためのhttpサーバーを立ち上げているとすると以下のようにして起動する。
Java
–Djava.security.policy=policy.txt
–Djava.rmi.server.codebase=http:/HOSTNAME:8081/ setup
クライアント側は、
l
まず、指定されたURLから、LookupLocatorをつくり、ここから、Registrarを取得する。
l
検索するクラス(インタフェース)を指定して、テンプレートを作成する。
l
このテンプレートより、検索し、オブジェクトを取得する。
LookupLocatorは、Lookupサービスが立ち上がっている場所を指定するオブジェクトである。しかし、Lookupサービスをそのものではない。Lookupサービスの本体は、ServiceRegistrarであり、これに対し、以下のようにして取得する。
LookupLocator
locator = new LookupLocator(“jini://myhost”);
ServiceRegistrar registrar =
locator.getRegistrar();
オブジェクトを登録するには、サービスのデータを作成して、登録する。
ServiceItem
sit = new ServiceItem(…);
ServiceRegisteration sre = registrar.regsiter(sit,Lease.FOREVER);
ServiceItemは、サービス名、バージョン番号などを含んだ属性とオブジェクトを元に作る。
クライアント側では、検索するテンプレートを作成し、これを元に検索する。
ServiceTemplate
tmpl = new ServiceTemplate(…);
Object service =
registrar.lookup(tmpl);
テンプレートには、検索するクラスを指定する他、属性からサービスを探すこともできる。詳細は省略する。
また、これまでのプログラムにpolicyファイルを指定して実行するが、これはどのような人がどのような権限で実行できるかを細かくしていするものであるが、詳細は省略する。
Lookupサーバの検索
前述のLookupLocatorの例は、Lookupサーバがわかっている場合を想定している。Lookupサーバから取得したServiceRegistrar内で、いろいろなタイプのサービスを検索できるようになっている点は、RMIと違うものの、RMIと大差がないとも言える。
Jiniの想定する環境では、クライアントはLookupサーバがどこにあるのかを情報を持っていないことがありうる。このためのクラスが、LookupDiscoveryである。次の例はこのクラスを使う例である。どこにあるかわからないサーバーを探すために、Multicastのプロトコルを使っている。
JiniのLookupサービスには「グループ」という概念がある。一つのLookupサービスは複数のグループに属することができるし、複数のLookupサービスが同一のグループに属することができる。グループについては、文字列の配列で表現している。但し、空の文字列の場合は特定のグループには属さないと解釈される。
LookupDiscoveryがMulitcastを使ってdiscoveryを行うドメインをJiniでは、djinn(ジン)と呼んでいる。discoveryには、3つのプロトコルが使われている。
multicast requestプロトコル:自分が属するdjinnに対して、マルチキャストLookupがないかを問い合わせる。
multicast announce プロトコル:Lookupサービスが、自分の存在をアナウンスするためのプロトコル。新しいサービスが加わったり、ネットワーク障害等で切断したり、復旧したりするときに用いる。
unicast discoveryプロトコル:特定のLookupサービスと通信するプロトコル。上の例。
LookupDiscoveryオブジェクトは、グループを指定して作成される。
lookupDiscovery = new LookupDiscovery(groups);
Lookupサーバが見つかったときのインタフェースとして、イベントリスナーモデルを使っている。そのため、リスナーとして登録するオブジェクトは、DiscoveryListernerのインタフェースを定義していなくてはならない。
public class
TimeServiceImpl implements DiscoveryListener …. {
…..
lookupDiscovery.addDiscoveryListerner(thisObj);
….
public void discovered(DiscoveryEvent
ev){ …./* 見つかった時のaction */ }
public void discarded(DiscoveryEvent
ev) { … /* 切れた時のaction */ }
…. }
discoveredはLookupサービスが見つかったときに呼び出されるメソッドである。ここで、引数になっているイベントから、ServiceRegistrarを取り出す。これについては、複数のLookupサービスがありうるので、配列になっていることに注意。
ServiceRegistrar[] regs = ev.getRegistrars();
サーバ側では、これを用いて、TimeServiceImplをTimeServiceとして登録している。登録の手順は前のUnicastのものと同等である。逆に、クライアント側ではどれか一つのServiceRegistrarから、オブジェクトを取得している。
ちなみに、このプログラムではListenerに設定してから、検索が終了し、リスナーが呼び出されるまでに時間がかかるため、設定後、sleepして待っている。
Jini: RMIの場合
次の例は、RMIをサービスとして登録するものである。違いは、サービスがRmoteをextendして定義し、RMIできるようにしてある。この場合、Remoteインタフェースを持つオブジェクトは、クライアント側に移動するのではなく、サーバ側にとどまって、サーバ側で実行される。そのため、このプログラムを実行する時には、rmicによって、スタブを生成しておくことが必要である。また、このプログラムではリスナーが見つかり登録するための部分を別のスレッドにして実行している。
リースの概念、その他
Jiniのプログラミングの大きな特徴の一つに「リース」という考え方がある。つまり、あるオブジェクトが他のオブジェクトに貸し出す期間を設定し、その期限が過ぎると使えなくなるというものである。
RMIやRPCはリモートの呼び出しもあたかもローカルなもののように見せることによって、プログラミングを容易にするものである。しかし、Jiniではこの点をあえてユーザに見せるようにしている。例えば、分散環境ではリモートのコンピュータが壊れたり、ネットワークに障害が起こるかもしれない。このような環境ではあらかじめこのようなことを起こることを想定するプログラミングが必要となる。Jiniのリースでは、あらかじめ決められた時間が来るとサービスは終了し、リソースは自動的に開放される。リースの期限が期限になる前に、更新するかどうかの対処を行う。リソースの管理を一定時間ごとにできることだけでなく、これにより障害等に強いソフトウエアを作成することができる。
このほかにも、JiniにはJavaでのイベントリスナーモデルを分散環境に拡張した分散イベント(distributed event)の仕組みがある。イベントも分散オブジェクトとして登録され、lookupサービスを通じてやり取りが行われる。また、データベースの更新などの同期を取るために、トランザクションをサポートする機構などもサポートされている。
Jiniは発表されてから、数年しかたっていないが、webをはじめネットワーク技術の急激な進歩により、分散プログラミング環境として、さらに新しい概念、アイデアが生み出されている。最終回は、それらの動向について概観する。