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


#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;`とあるのは必須。無いと実行時にエラーが出る。

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

public interface RmiInterface extends Remote {
	void sendData(String i) throws RemoteException;
}
```

```java:存在しないメソッドをコールした時の例外
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()`とか定義すればいいけど切断は気がつかない？
コード見れば分かるけど、接続されたクライアントの情報を取るメソッドは無いからサーバーからクライアントへの直接の情報送信は出来ない。

```java:サーバー-受信側-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);
	}
}
```

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

```java:クライアント-送信側-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();
		}
	}
}
```

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

```markdown:java.policy
grant {
	permission java.security.AllPermission;
};
```

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

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

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

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

```markdown
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に固めてるから起動するクラスの指定が違うだけ。
サーバー側もクライアント側も、必要なパラメーターは同じ。

```markdown
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出来るのかは確認するべし。