Java-RMIの代替RMIライブラリlipermimodの紹介

2018年7月9日

※弊社製オープンソースソフトはオープンソースソフトウェアとしてまとめているので参照されたい。

Javaプログラムどうしの通信インフラとしては、元々Java-RMIというものがあるのだが、これは非常に使いづらい。その理由については、検索してもらいたいのだが、その代替RMIライブラリとして、http://lipermi.sourceforge.net/というものがある。これは既に10年以上前のものだが、これを元に改良したものが、本ライブラリlipermimodである。

リポジトリ

リポジトリは以下にある。前者はソース、後者はアーティファクトのMavenリポジトリになる。

基本的なサンプル

ソースリポジトリに最も単純なサンプルがあるが、これは以下のようなものである。

※特に二つのマシンやら、二つのプロセス等は必要無い。このプログラム一つを起動すれば、クライアント・サーバ間の通信が行われる。

package com.cm55.lipermimod.sample;

import java.io.*;

import com.cm55.lipermimod.*;

public class Basic {

  /** 接続に使用するポート番号。何でも良いがオリジナルのlipermiでは4455になっている */
  public static final int PORT = 4455;

  /** サーバが公開するオブジェクトの名称 */
  public static final String GLOBAL = "serverGlobal";

  public static void main(String[]args) throws IOException {

    // サーバを作成し、グローバルオブジェクトを作成して公開し、4455でクライアントの接続を待つ
    Server server = new Server();
    server.registerGlobal(GLOBAL, new ServerGlobalImpl());
    server.bind(PORT);

    // クライアントを作成し、サーバに接続する。当然ながらサーバはlocalhost
    // サーバの公開するオブジェクトを取得し、そのメソッドを呼び出す。
    Client client = new Client();    
    client.connect("localhost", PORT);    
    ServerGlobal serverGlobal = client.getGlobal(GLOBAL);
    System.out.println("client side:" + serverGlobal.hello("hello"));
  }

  /** サーバの公開するオブジェクトのインターフェース。必ずIRemoteを実装する必要がある */
  public static interface ServerGlobal extends IRemote {
    public String hello(String value);
  }

  /** サーバによる公開オブジェクトの実装 */
  private static class ServerGlobalImpl implements ServerGlobal {
    public String hello(String value) {
      System.out.println("server side:" + value);
      return value + " world";
    }
  }
}

これを走行させると、以下の表示がされる。

server side:hello
client side:hello world

基本的な考え方

エクスポートされるインターフェース

エクスポートされ、外部から呼び出されるインターフェースとして、IRemoteから派生したインターフェースを作成する。上述の例であれば、

  public static interface ServerGlobal extends IRemote {
    public String hello(String value);
  }

になる。

サーバ側の準備

上記インターフェースを実装するオブジェクトをサーバ側で用意し、名称と共にグローバルとして登録しておく。
サーバ側はソケットをオープンし、クライアントからの接続を待つ。

クライアントの接続とインターフェースの呼び出し

クライアント側はサーバに接続した後、サーバがあらかじめ登録しておいたインターフェースを名前を指定して取得する。
インターフェース(つまり、リモートオブジェクト)を取得したら、そのメソッドの呼び出しを行う。

転送可能なもの

上記のサンプルにおいて、クライアントからサーバにメソッド引数として文字列が渡され、サーバからクライアントにメソッド返り値として文字列が渡されたが、ここではJavaのシリアライゼーションを利用しているので、シリアライズできるものであれば何でも転送することが可能である。つまり、任意のオブジェクトを転送するには、Serializableインターフェースを実装すればよい。

それ以外に、IRemoteから派生したインターフェース(以下単純にIRemoteと呼ぶ)を転送することもできる。上記の例では、サーバ側であらかじめグローバルとして用意されたインターフェースを取得したが、これはサーバ側のサービスを得るための最初のとっかかりとして行われるものであり、実際には引数や返り値としてこのようなインターフェースを転送することができる。

つまり、最初にグローバルなインターフェース(リモートオブジェクト)を取得したら、そのメソッド呼び出しにおいて、IRemoteを指定したり、メソッドの返り値としてIRemoteを取得することができる。

ちなみに、「サーバ側の呼び出しメソッドの引数としてIRemoteを指定する」とは、クライアント側で用意されたリモートオブジェクトをサーバ側に受け渡すということだが、これは「サーバ側にクライアントのオブジェクトを呼び出してもらう」ことを意味する。つまりは、「サーバからのコールバック」という意味あいとなる。

仕様の概要

詳細は今後の投稿で説明していくが、大まかな仕様としては以下の通りである。

  • 一つのオブジェクトに複数のIRemoteから派生したインターフェースを実装できる。受け取った側はそのいずれのインターフェースをも利用可能。
  • ただし、インターフェースのメソッドでは、メソッドオーバロードは使用できない。元々のJavaの仕様のような、「同じ名前だが引数の異なるメソッド」を同時に定義することはできない。単純にメソッド名がユニークである必要がある。
  • 最初にリモートオブジェクトが転送される方向としては、必ず「サーバからクライアント」になるが、しかし、リモートオブジェクトの転送方向はどちらでも良い。両者は等価に扱われる。
  • リモートオブジェクトの転送は、直接的なメソッド引数あるいは直接的なメソッド返り値としてのみ可能であり、引数や返り値として現れるシリアライズオブジェクトの中の変数として転送することはできない。
  • リモートオブジェクト以外の転送可能なオブジェクトとしては、一般的なJavaのシリアライゼーションが可能な任意のオブジェクトが使用可能。
  • リモートGCをサポートしている。リモートオブジェクトが転送された先において、それに対する参照がなくなった場合、元のオブジェクトの参照計数が減らされる。これが0になった場合には、元オブジェクトも消去される。