LoginSignup
16

More than 5 years have passed since last update.

RMI Java Remote Method Invocationの方法をjava8で再確認

Last updated at Posted at 2015-12-13

RMI Java Remote Method Invocation

二つの異なるJVM同士でプロセス間通信を行う。サーバー=受信側は任意の文字列キーをBindして、接続を待つ。クライアント=送信側はそのキーでサーバーに接続する。
そして共通インターフェイスにあるメソッドをクライアント側からコールすると、サーバー側のスコープで実行される。
クライアントからサーバーはメソッドの引数、その逆はメソッドの戻り値で値をやり取りする。ここで指しているクライアント側からのコールはクライアントの好きなタイミングで出来るが、サーバーからは出来ない。http1でサーバー側からのプッシュは基本できないのと似てる。
セキュリティなにそれなローカル専用。CentOS6とwin7環境で確認。環境は偶然どっちも以下のとおり。ビルド番号まで同じだった。

>java -version
java version "1.8.0_45"
Java(TM) SE Runtime Environment (build 1.8.0_45-b15)
Java HotSpot(TM) 64-Bit Server VM (build 25.45-b02, mixed mode)

送信と受信二つのJVMインスタンスの他にrmiregistryというプロセスも必要。
rmiregistry -J-Djava.rmi.server.useCodebaseOnly=falseというコマンドを立ち上げる。&を付けてバックグラウンドプロセス化するのがベター。起動したら放置で多分OK。
パラメーターは無くても起動出来るけど、代わりに後述のインターフェイスとなるクラス名を書かないといけないので管理が面倒
さらに後述するけどセキュリティポリシーも多分必須。

シンプルにサーバー・クライアント形式で接続

まずは共通となるインターフェイスを作る。サーバー側とクライアント側でやり取りするメソッドを定義する。
送信側と受信側と接続するステップにおいてはパッケージ名とインターフェイス名が一致する必要があって、メソッドについてはコールする時に一致判定が行われる。ので余計なメソッドがある分には問題ない。クラスにthrows RemoteException;とあるのは必須。無いと実行時にエラーが出る。

サーバーとクライアントで共通して使うべきインターフェイス-RmiInterface.java
import java.rmi.Remote;
import java.rmi.RemoteException;

public interface RmiInterface extends Remote {
    void sendData(String i) throws RemoteException;
}
存在しないメソッドをコールした時の例外
java.rmi.ServerException: RemoteException occurred in server thread; nested exception is: 
    java.rmi.UnmarshalException: unrecognized method hash: method not supported by remote object
    at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:354)

サーバー 受信側のコードは以下のとおり。クライアント 送信側からの接続はいくらでも受け付ける。接続時、切断時のメソッドは無いっぽい。接続時はインターフェイスにonConnect()とか定義すればいいけど切断は気がつかない?
コード見れば分かるけど、接続されたクライアントの情報を取るメソッドは無いからサーバーからクライアントへの直接の情報送信は出来ない。

サーバー-受信側-Server.java
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.server.UnicastRemoteObject;

public class Server implements RmiInterface {

    public static void main(String[] args) throws Exception {
        Server obj = new Server();
        RmiInterface stub = (RmiInterface) UnicastRemoteObject.exportObject(obj, 0);
        LocateRegistry.getRegistry().bind("RmiTest", stub);//ここでRmiTestの名前で接続を待つ
        Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
            public void run() {
                //unbindせずにプロセスが死ぬと、再度bindする時に怒られてrmiregistryプロセスの再起動が必要になる
                try {
                    LocateRegistry.getRegistry().unbind("RmiTest");
                } catch (RemoteException | NotBoundException e) {
                    e.printStackTrace();
                }
            }
        }));
        Thread.sleep(999 * 1000);//終了されるとマズいから手抜き
    }

    @Override
    public void sendData(String i) {//クライアントのインスタンスからコールされる
        System.err.println(i);
    }
}

クライアント 送信側 いくらでも起動すればよい。

クライアント-送信側-Client.java
import java.rmi.Naming;

public class Client {
    public static void main(String[] args) {
        try {
            System.setSecurityManager(new SecurityManager());//セキュリティポリシーをセット
            RmiInterface i = (RmiInterface) Naming.lookup("rmi://localhost/RmiTest");//localhost部分は固定。RmiTestをサーバー側と一致させる
            int count = 1;
            while (true) {
                Thread.sleep(1 * 1000);
                i.sendData("ok-" + count);//ここでok-1 とかの引数がサーバー側のメソッドで実行される
                count++;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

セキュリティポリシーファイル ファイルとしてはどうしても必要っぽい。「セキュリティ無し。全部許可」くらいパラメーターで指定出来たらいいのに。

java.policy
grant {
    permission java.security.AllPermission;
};

実行するコマンドライン
前述のRmiInterface.javaServer.javaClient.javaを1つのjarファイルに固めた前提とする

サーバー・クライアント共通に必要なコマンドは以下のとおり。コマンドの最後に&を付けてバックグランドにするのが普通かな。

rmiregistry -J-Djava.rmi.server.useCodebaseOnly=false

サーバー 受信側に必要なコマンドは以下のとおり。パラメーターは全て必要。codebaseはフルパスで指定する必要があるっぽい。もちろん改行消して1行にするべし。
rmiregistryのuseCodebaseOnlyを指定しないと、rmiregistryにいちいちcodebaseを指定しないといけない。めんどい。

java -Djava.rmi.server.useCodebaseOnly=false
     -Djava.rmi.server.codebase=file:C:/test/hoge.jar 
     -Djava.security.policy=java.policy 
     -cp hoge.jar Server

クライアント 送信側に必要なコマンドは以下のとおり。いくらでも起動して良い。同一のjarに固めてるから起動するクラスの指定が違うだけ。
サーバー側もクライアント側も、必要なパラメーターは同じ。

java -Djava.rmi.server.useCodebaseOnly=false
     -Djava.rmi.server.codebase=file:C:/test/hoge.jar
     -Djava.security.policy=java.policy
     -cp hoge.jar Client

これでシンプルなサーバー・クライアント形式の通信が出来る。
bindするキーを動的に指定出来るし、コマンドライン的にはサーバー側クライアント側の区別は無いので、適当に乱数を挟むとクライアント側でもサーバーを起動して双方向の通信も可能になる。
自分の環境で、どうすればちゃんとunbind出来るのかは確認するべし。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16