JiniRMI入門(その1)

 RMIとはRemote Method Invocationの略であり、Javaの分散プログラミングのための仕掛けである。この仕掛けをつかうことによって、いろいろなマシンにオブジェクトのインスタンスを生成し、これらの間でRMIを使って他のマシンのオブジェクトのメソッドを呼び出すことによって、分散システムを構築することができる。基本的には分散システムをプログラミングするためにはTCP/IPUDPなど低レベルの通信レイヤを使つかう。しかし、いちいち、機能ごとにプロトコルを設計して、通信しなくてはならない。このプロトコルを関数呼び出しに抽象化したのが、RPC(remote procedure call)である。有名なものとしてSUN RPCがあるが、現在これを使って、Unixのシステムのいろいろな機能が実装されている。RMIは、オブジェクト指向言語でのRPCであり、オブジェクト指向の概念で分散システムをプログラムできるようにする。C++などの言語については、CORBAなどが有名であり、RMIのほかにJavaに対しても、CORBA実装もある。

 Jiniはこの分散オブジェクトプログラミングをベースに、いろいろなコンピュータ、家電に入っているプロセッサからスーパーコンピュータまで、ネットワーク上のあらゆる機器(コンピュータ)を「連合(federation)」させるための仕組みを提唱したものである。たとえば、いろいろな家電製品にはいまやプロセッサが入っているが、これをネットワークにつなぎ、RMI(というか、RMIで提供されている標準のプロトコルとJiniによって提供されるサービスの検索機能)でつなぐことによって、いろいろな家電を統一的に制御したり、利用したりできるようになる。Jiniのもっとも重要な概念として「サービス」がある。ネットワーク上に接続されているコンピュータを単なるデータを交換する対象と考えるのではなく、なんらかのサービスを提供する対象と考える。そのサービスをお互いに交換することによって、分散システムはなんらかの仕事をする。これまで、いわゆるサーバはサービスを提供する担い手であり、クライアントはそのサーバからサービスを受ける形態が一般的であったが、Jiniが想定しているのはネットワーク上の分散システムを構成するコンピュータがお互いにサービスを提供することによって協調作業をするシステムを想定している。

 Jiniでサービスをネットワーク上のどこからでも利用できる。サービスはネットワーク上を移動するオブジェクトによって提供される。いろいろなサービスがあるとするJiniでは、そのサービスを見つけるための機構「Lookupサービス」が提供されている。これによって、ネットワーク上に提供されているサービスを検索し、そのサービスを利用できる。これについては、たとえばDHPCを考えるとわかりやすい。いまでは、ノートPCを単にケーブルを接続するだけで、ネットワークに参加できるが、ケーブルを接続したときにまず、ネットワークのアドレスを管理しているDHPCサーバを検索し(これがLookup、つまりDHCPのサービスの検索)、標準のプロトコルでアドレスやネットマスク、DNSなどのアドレスを取得する。また、サービスを提供する側は、Lookupサービスに登録することをJoinと呼んでいる。

 RMIは個々のコンピュータで提供するオブジェクトを管理する(registry)機能を提供しているが、Jiniはこれをネットワーク全体に拡張し、すべてのコンピュータで提供されている機能を検索する機能を提供するものいうこともできる。

ネットワーク上のオブジェクトの転送

 プログラミングという観点からみれば、TCP/IPがもっとも基本的で低レベルの通信手段である。このレベルでは単なるバイナリのデータの転送が提供される。Javaでは、以下のようにしてプログラミングする。CのレベルのSocketよりもだいぶ簡略化されている。

サーバー側:

 ServerSocket ss = new ServerSocket(port);

  Socket s = ss.accept();

  DataOutputStream out = new DataOutputStream(s.getOutputStream());

  x = out.writeInt();  /* write …*/

クライアント側:

 Socket s = new Socket(host, port);

 DataInputStream in = new DataInputStream(s.getInputSteram());

 y = in.readInt(); /* … read …*/

Javaでは、オブジェクトそのものを書き出すSerialization機能を持っている。これをつかえば、Serializableインタフェースを実装しているオブジェクトそのものを転送することができる。

 ObjectOutputStream out = new ObjectOutputStream(s.getOutputStream());

 out.writeObject(obj);

 ObjectInputStream in = new ObjectInputStream(s.getInputStream());

 Object obj = in.readObject();

ここで、readObjectから返されるのはすべてのオブジェクトのsuperClassであるObjectとして返されるため、適当なクラスにcast              して用いる。このオブジェクトの転送では「データ」のみがネットワークに送信されることに注意。異なるマシンの間で転送する場合には、転送されるオブジェクトのクラス情報(つまり、プログラム)は両方のマシンで同じプログラムをもっていなくてはならない。

 オブジェクトを転送する場合、転送先では少なくともオブジェクトを利用するわけであるから、オブジェクトの詳しい内容をしらなくても、何のメソッドが使えるかは知っているはずである。Javaでは、このことは内部の実装はしらなくても、どのようなメソッドがあるか、つまり、インタフェースだけをしっていると考える。ここで、例として、時刻を返すオブジェクトを考えると、

public class ShowDateImpl implements Serializable, ShowDate {

  public ShowDateImp() { … } /* constructor */

  public long getCurrentMillis() { … } /* 現在の時刻を返すメソッド */

  public long getMillis() { … } /* オブジェクトが生成された時刻を返すメソッド */

public interface ShowDate {

   public long getCurrentMillis();

   public long getMillis();

}

転送先では実際のプログラムであるShowDateImplは知らなくてもよく、そのインタフェースであるShowDateのみをしっていればいいことになる。そこで、

送信側: ShowDateImpl obj = new ShowDate();

          out.writeObject(obj)

受信側: ShowDate obj = (ShowDate)in.readObject();

          obj.getMillis();

とすればいいはずである。しかし、これをすると、obj.getMillis()のところで、実際のプログラムがない(ClassNotFoundError ShowDateImpl)というエラーになってしまう。obj.getMillis()を受信側で実行するためにはインタフェースだけでは不十分で、実際のプログラムShowDateImplが必要となる。

クラス情報の転送

 そこで、クラス情報の転送する方法を考える。まず、クラスを転送するサーバを作る。これは.classのファイルを送信するサーバである。これに接続して、受信側でクラス情報をもらうプログラムがNetworkClassLoaderである。このプログラムでは、転送されたクラスファイルをClassLoaderdefineClassを使って、転送された.classファイルの内容をクラスとして使えるようにする。これによって、ShowDateImplをつくっておけば、上のプログラムは動作するようになる。

 実際、ObjectInputStreamでは、resolveClassというメソッドを定義してやれば、ここで不明なクラス(定義されていないクラス)について、NetworkClassLoaderをつかってクラスをロードすることによって解決することができる。

RMIでのオブジェクトの転送

 RMIでは、MarshalledObjectを使って、オブジェクトの転送をしている。プログラムにsun.rmi.severにあるMarshalOutputStreamMarshalInputStreamを使えば、ObjectInputStreamObjectInputStreamでのプログラムと同じような方法で同様なことができる。だだし、ここで、セットアップとして以下のことをしなくてはならない。

1.        まずあらかじめ、ネットワークのクラスサーバー(webサーバーでもよい)を立ち上げてておく。(http://localhost:8081)

2.        送るべきプログラムをjarファイルにしておく。(dl.jar)

3.        送信側のプログラムには、どこからクラスをロードするか(codebase)を指定する。

4.        双方のプログラムについて、セキュリティマネジャーを設定し、起動時にはセキュリティポリシーを指定する。

MarhalOutputStreamでは、オブジェクトをネットワークに送り出すときに、オブジェクトの復元に利用するべきクラス情報を含んだホストとディレクトリ情報(codebase)をURL形式で、埋め込み、送りだす。受信側のMarshallInputStreamでは、そこから必要なクラスをロードしてオブジェクトを復元することになる。

送り手側のプログラムでは、以下のように指定する。

 java –Djava.rmi.sever.codebase=http://localhost:8081/dl.jar –Djava.security.policy=policy ObjectSever

 MarshalledObjectを利用すれば、同じようなことができる。

送信側: ShowDateImpl obj = new ShowDate();

          out.writeObject( new MarshalledObject(obj))

受信側: MarshalledObject mo = (MarshalledObject)in.readObject();

          ShowDate obj = (ShowDate)mo.get();

          obj.getMillis();

RMIの概要

 さて、オブジェクトの転送では転送されたオブジェクトのメソッドを呼び出し、いろいろな操作をするものであるが、RMIはリモートにあるオブジェクトのメソッドを呼び出す。以下の手順で行う。

1.        インタフェースをRemoteインタフェースをextendして定義する。これをクライアント、サーバ、双方に置く。

2.        サーバ側にはリモートのオブジェクトを管理するプロセスであるrmiregistryを起動しておく。

3.        また、サーバ側に仲介するプログラムであるstubを生成するプログラムであるrmicをつかって、stubを生成しておく。

4.        サーバ側ではリモートのオブジェクトを登録する。

5.        クライアント側では登録されているオブジェクトを取り出し、インタフェースを使って呼び出す。

サーバ側のプログラムでは、リモートのオブジェクトを登録するために、

 ShowDateImpl sdi = new ShowDateImp();

  Naming.rebind(“//localhost/TimeSever”,sdi)

で、登録している。このプログラムでは、前の例のようにcodebasepolicyを指定して、起動しなくてはならない。

クライアントプログラムでは、

 obj = (ShowDate) Naming.lookup(“rmi://localhost/TimeSever”);

として、登録されているオブジェクトへの参照を得ることができる。これに対し、obj.getMills()と呼び出すことによって、サーバー側に登録されているリモートのオブジェクトのメソッドが起動されて、それらの引数、結果はオブジェクトとして転送される。

 

次回は、RMIの詳細、Jiniについて進んでいくことにする。