Powered by SmartDoc

RMI

RMI の概要

RMI とは

RMIという言葉は、Remote Method Invocation (遠隔メソッド呼び出し)の略です。RMIは、あるJava Virtual Machine上のインスタンスが、別のJava Virtual Machine上のメソッドを呼び出す仕組みです。このとき、2つのJava Virtual Machineは、それぞれ別のホスト上にあっても構いません。2つのJava Virtual Machineは、クライアント・サーバモデルによって通信を行います。

RMIを使わない通常のJavaプログラムについて考えてみましょう。例えばHelloクラスのsayHelloメソッドを実行しようとすると、Helloのインスタンスを作成して、このインスタンスのsayHelloメソッドを実行します。

RMIでは、Helloクラスの実装はサーバ側にあります。クライアントでHelloクラスのインスタンスを作成してsayHelloメソッドを呼び出すと、サーバ上のsayHelloメソッドが実行され、返値がクライアントに返されます。

Helloクラスの実装はサーバ側にあるので、クライアントはサーバ側を呼び出すだけでよいのです。このときクライアントは、メソッドを実行するだけでサーバを呼び出せます。

RMI のしくみ

RMIの構成図を図6.1.2.1[RMIの構成図]に示します。

RMIの構成図

サーバとクライアント

サーバでは、クライアントから呼び出されるメソッドを実装します。クライアントでは、サーバを見つけだして、メソッドを実行します。

スタブとスケルトン

RMIでは、サーバとクライアントには通信に必要な具体的な処理は記述されていません。そうした処理は「スタブ」と「スケルトン」というクラスに記述されています。スタブはクライアント側で、スケルトンはサーバ側で処理を行います。RMIでは、直接にはスタブとスケルトンが通信を行うことになります。スタブとスケルトンは、サーバのプログラムからツールで生成されます。

スタブとスケルトン

rmiregistryは、サーバの情報を登録しておくための「ネームサーバ」です。クライアントはrmiregistryにアクセスしてサーバのプログラムを探します。

RPC

RMIの原型となった技術がRPC (Remote Procedure Call)です。RPCは、別のマシン上にある手続きを実行するためのしくみを提供します。

もともとはSun Microsystemsによって開発され、現在ではUnixやWindowsなどのOSで常用されています。

RMI を構成するプログラム

1つのRMIプログラムは、基本的には、3つの部分から構成されます。サーバ側のプログラムと、クライアント側のプログラム。そして、両者に共通するインタフェースです。

Remote インタフェース

Remoteインタフェースでは、クライアントから呼び出せるメソッドを定義します。このインタフェースは、サーバとクライアントで共有されます。サーバは、このインタフェースを実装することになります。

Hello.java
import java.rmi.Remote;
import java.rmi.RemoteException;

public interface Hello extends Remote {
    public String sayHello() throws RemoteException;
}

このインタフェースは、java.rmi.Remoteを継承しています。また、それぞれのメソッドは、RemoteExceptionを発行できるようにしなければいけません。

サーバ側のプログラム

次に、サーバ側のプログラムについて見てみましょう。

HelloImpl.java
import java.rmi.Remote;
import java.rmi.Naming;
import java.rmi.RMISecurityManager;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.net.MalformedURLException;

public class HelloImpl extends UnicastRemoteObject implements Hello {
    
    public HelloImpl() throws RemoteException {
        super();
    }

    public String sayHello() throws RemoteException {
        return "Hello world!";
    }

    public static void main(String args[]) {
        if (System.getSecurityManager() == null) {
            System.setSecurityManager(new RMISecurityManager());
        }
        try {
            HelloImpl obj = new HelloImpl();
            StringBuffer url = new StringBuffer();
            url.append("rmi://");
            url.append(args[0]);
            url.append("/HelloServer");
            Naming.rebind(new String(url), obj);
            System.out.println("HelloServer bound in registry");
        } catch (RemoteException e) {
            e.printStackTrace();
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
    }
}

まずは、クラスの定義部分から解説しましょう。Helloインタフェースを実装します。そして、UnicastRemoteObjectを継承します。

public class HelloImpl
        extends UnicastRemoteObject
        implements Hello {
            ......
}

次に、コンストラクタとメソッドです。

    public HelloImpl() throws RemoteException {
        super();
    }

    public String sayHello() throws RemoteException {
        return "Hello world!";
    }

コンストラクタとメソッドは、RemoteExceptionを発行するようにします。そのため、必ずコンストラクタを用意しなければいけません。そして、インタフェースで用意されたメソッドを実装する必要があります。

では最後に、mainメソッドを見てみましょう。

この部分は、クラスが許可されていない操作を行わないために必要になります。

        if (System.getSecurityManager() == null) {
            System.setSecurityManager(new RMISecurityManager());
        }

次の部分で、"rmi://localhost/HelloServer"といったスタイルのURIを生成します。

            StringBuffer url = new StringBuffer();
            url.append("rmi://");
            url.append(args[0]);
            url.append("/HelloServer");

そして最後に、HelloImplのインスタンスをrmi://localhost/HelloServerという名前で、rmiregistryに登録しています。

            Naming.rebind(new String(url), obj);

クライアント側のプログラム

クライアント側のプログラムです。

HelloClient.java
import java.rmi.Naming;
import java.rmi.RMISecurityManager;
import java.rmi.RemoteException;
import java.rmi.NotBoundException;
import java.net.MalformedURLException;

public class HelloClient {
    public static void main(String args[]) {
        if (System.getSecurityManager() == null) {
            System.setSecurityManager(new RMISecurityManager());
        }
        try {
            StringBuffer url = new StringBuffer();
            url.append("rmi://");
            url.append(args[0]);
            url.append("/HelloServer");
            Hello obj = (Hello)Naming.lookup(new String(url));
            System.out.println(obj.sayHello());
        } catch (RemoteException e) {
            e.printStackTrace();
        } catch (NotBoundException e) {
            e.printStackTrace();
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
    }
}

まずは、"rmi://localhost/HelloServer"といったようなURIを生成します。

            StringBuffer url = new StringBuffer();
            url.append("rmi://");
            url.append(args[0]);
            url.append("/HelloServer");

次に、rmiregistryから"rmi://localhost/HelloServer"という名前のオブジェクトを探し、Helloにキャストします。

            Hello obj = (Hello)Naming.lookup(new String(url));

最後に、HelloオブジェクトのsayHelloメソッドを実行しています。

            System.out.println(obj.sayHello());

RMI を動かす

プログラムのコンパイル

まずは、いま紹介した3つのプログラムをコンパイルします。

javac Hello.java
javac HelloImpl.java
javac HelloClient.java

スタブとスケルトンの生成

次のコマンドによって、スタブとスケルトンを生成します。

rmic HelloImpl

HelloImplは、コンパイルされたHelloImpl.classのことです。このコマンドによって、次の2つのクラスが生成されます。

また、rmicコマンドにkeepオプションをつけることによって、スタブとスケルトンのソースファイルを残すことができます。

rmic -keep HelloImpl

サーバ側とクライアント側で用意するプログラム

サーバ側とクライアント側で用意するプログラムは、それぞれ次のようになります。

サーバ側

クライアント側

rmiregistryの起動

rmiregistryを起動します。このとき、起動するフォルダにクラスファイルを一切置かないようにしてください。

start rmiregistry

サーバの起動

次のコマンドでサーバを起動します。

java -Djava.rmi.server.codebase=file:///C:\Home\rmi\
     -Djava.security.policy=policy.txt
     HelloImpl localhost
(1行で入力する)

起動時にプロパティが指定されているので、見てみましょう。

codebase

まずは、この部分です。

java.rmi.server.codebase=file:///C:\Home\rmi\

codebaseプロパティでスタブの位置を指定しています。この例では、C:\Home\rmi\フォルダにスタブのクラスファイルを置いておきます。このとき、URLの最後に"\"をつけるのを忘れないようにしましょう。

codebaseには、Web上のURLを指定することもできます。例えばhttp://www.wakhok.ac.jp/~tomoharu/にcodebaseを指定した場合、このURL上にスタブを置いておきます。

クライアントが起動されると、codebaseで指定されたURLからスタブがダウンロードされて、クライアントプログラムからスタブを利用できるようになります。

ポリシーファイルの設定

次に、このプロパティです。

java.security.policy=policy.txt

policy.txtというファイルでアクセス権を指定できます。policy.txtの内容は次のようになっています。

policy.txt
grant {
	// Allow everything for now
	permission java.security.AllPermission;
};

ここでは、すべてのユーザにすべてのアクセス権を与えています。

クライアントの起動

次のようにしてクライアントを起動します。サーバと同じように、ポリシーファイルを指定するようにしましょう。

java  -Djava.security.policy=policy.txt HelloClient localhost

うまくいけば、サーバからのメッセージが出力されるはずです。

注意点

Cygwinを使うとうまく動きません(原因は不明)。そしてプログラムを起動するときには、クラスパスを設定してはいけません。クラスパスを設定していると、もしクラスパス中にスタブがあった場合、クライアントプログラムはそのスタブをロードするからです。

参考文献・URL

Java(TM) Remote Method Invocation (RMI)