このチュートリアルでは、Java™ Remote Method Invocation (Java RMI) によってカスタムソケットファクトリを実装して使う手順を説明します。カスタムソケットファクトリは、リモートメソッド呼び出しがネットワークレベルで通信する方法を制御するために使用できます。たとえば、このカスタムソケットファクトリを使用して、ソケットオプションの設定、アドレスのバインドの制御、認証に要求されるような接続の確立の制御、および暗号化や圧縮などのデータのエンコードの制御を行うことができます。
コンストラクタや java.rmi.server.UnicastRemoteObject または java.rmi.activation.Activatable の exportObject メソッドなどでリモートオブジェクトがエクスポートされる場合、カスタムクライアントソケットファクトリ (java.rmi.server.RMIClientSocketFactory インスタンス) およびカスタムサーバーソケットファクトリ (java.rmi.server.RMIServerSocketFactory インスタンス) を指定して、リモートオブジェクトのリモート呼び出し通信時に使用されるようにすることが可能です。
クライアントのソケットファクトリは、リモート呼び出しを開始するために使用されるソケットの作成を制御し、接続が確立される方法と使用するソケットのタイプを制御するのに使用できます。サーバーのソケットファクトリは、リモート呼び出しを受信するために使用するサーバーソケットの作成を制御するため、着信接続の待機および受け入れ方法に加え、着信接続に使用するソケットのタイプの制御にも使用できます。
リモートオブジェクトに対応するリモートスタブには、リモートオブジェクトがエクスポートされるときにあれば、クライアントのソケットファクトリが含まれます。したがって、クライアントのソケットファクトリは直列化が可能であることが必要であり、そのコードは、スタブクラスのコードやリモート呼び出しで渡されるその他の直列化できるオブジェクトと同様に、クライアントがダウンロードすることができます。
また、カスタムソケットファクトリでレジストリをエクスポートするための LocateRegistry.createRegistry メソッドと、カスタムのクライアントソケットファクトリでレジストリに対応するスタブを取得するための LocateRegistry.getRegistry メソッドもあります。
(さらに、Java RMI 用のグローバルソケットファクトリもあることに注意してください。これは、java.rmi.server.RMISocketFactory の setSocketFactory メソッドで設定できます。このグローバルソケットファクトリは、リモートスタブがカスタムのクライアントソケットファクトリを含んでいないときにソケットを作成する場合、およびリモートオブジェクトがカスタムのサーバーソケットファクトリ付きでエクスポートされないときにサーバーソケットを作成する場合に使用されます。)
このチュートリアルは次の 3 つの部分にわかれています。
このチュートリアルで使われているソースコードは、次のファイル形式から選ぶことができます。
多くのユーザーは、相互認証や暗号化などの、Java RMI クライアントとサーバー間の安全な通信に興味を持っています。カスタムソケットファクトリには、このためのフックが用意されています。詳細は、「Java RMI での SSL の使用」を参照してください。ServerSocket と Socket を実装する。RMIClientSocketFactory を実装する。RMIServerSocketFactory を実装する。ServerSocket と Socket を実装するこの例のカスタムソケットファクトリは、単純な XOR 暗号化および暗号解読を実行するソケットを生成します。この種の暗号化は、暗号解読の知識のある者であれば簡単に解読されますが、ここでは例を単純にするために使用します。
カスタム XOR ソケットの実装には、次のソースが含まれます。XOR ソケットは、特殊な入力と出力のストリーム実装を使用して、ソケットに対して読み書きされるデータの XOR 処理を行います。
RMIClientSocketFactory を実装するXorClientSocketFactory は、java.rmi.server.RMIClientSocketFactory インタフェースを実装します。クライアントソケットファクトリは、createSocket メソッドを実装して、適切なクライアントソケットインスタンス XorSocket を返す必要があります。
クライアントソケットファクトリは、java.io.Serializable インタフェースを実装して、インスタンスがリモートスタブの一部としてクライアントに直列化されるようにする必要があります。さらに、equals メソッドと hashCode メソッドを実装して、Java RMI 実装が、同等のクライアントソケットファクトリを使用するリモートスタブのインスタンス間でリソースを正しく共有できるようにすることも必須です。
package examples.rmisocfac;
import java.io.*;
import java.net.*;
import java.rmi.server.*;
public class XorClientSocketFactory
implements RMIClientSocketFactory, Serializable {
private byte pattern;
public XorClientSocketFactory(byte pattern) {
this.pattern = pattern;
}
public Socket createSocket(String host, int port)
throws IOException
{
return new XorSocket(host, port, pattern);
}
public int hashCode() {
return (int) pattern;
}
public boolean equals(Object obj) {
return (getClass() == obj.getClass() &&
pattern == ((XorClientSocketFactory) obj).pattern);
}
}
RMIServerSocketFactory を実装するXorServerSocketFactory は、java.rmi.server.RMIServerSocketFactory インタフェースを実装します。サーバーソケットファクトリは、createServerSocket メソッドを実装して、適切なサーバーソケットインスタンス XorServerSocket を返す必要があります。
サーバーソケットファクトリのインスタンスはリモートスタブ内に含まれていないので、サーバーソケットファクトリは Serializable インタフェースを実装する必要はありません。equals メソッドと hashcode メソッドを実装して、Java RMI 実装が、同等のソケットファクトリでエクスポートされたリモートオブジェクト間でリソースを正しく共有できるようにすることも、引き続き必須です。
package examples.rmisocfac;
import java.io.*;
import java.net.*;
import java.rmi.server.*;
public class XorServerSocketFactory
implements RMIServerSocketFactory {
private byte pattern;
public XorServerSocketFactory(byte pattern) {
this.pattern = pattern;
}
public ServerSocket createServerSocket(int port)
throws IOException
{
return new XorServerSocket(port, pattern);
}
public int hashCode() {
return (int) pattern;
}
public boolean equals(Object obj) {
return (getClass() == obj.getClass() &&
pattern == ((XorServerSocketFactory) obj).pattern);
}
}
RMIClientSocketFactory および RMIServerSocketFactory 実装を使用するリモートオブジェクトを作成してエクスポートする、サーバーアプリケーションを作成する。リモートオブジェクトのスタブへの参照を Java RMI レジストリに保存して、クライアントがそれを検索できるようにする。RMIServerSocketFactory を使用して、そのリモートオブジェクトへの着信呼び出しを受け入れるためのサーバーソケットを作成します。また、対応するカスタムの RMIClientSocketFactory を含むスタブも作成します。このクライアントソケットファクトリは、そのスタブを使用するリモートオブジェクトへのリモート呼び出しを起動するときに接続を作成するために使用されます。
この例は、「Java RMI 入門」チュートリアルの例に似ていますが、RMI 実装が使用するデフォルトのソケットではなく、カスタムソケットファクトリを使用します。
このアプリケーションは、次の Hello リモートインタフェースを使用します。
package examples.rmisocfac;
public interface Hello extends java.rmi.Remote {
String sayHello() throws java.rmi.RemoteException;
}
このサーバーアプリケーションは、Hello リモートインタフェースを実装するリモートオブジェクトを作成してエクスポートし、カスタムソケットファクトリを引数にとる java.rmi.server.UnicastRemoteObject.exportObject メソッドを使ってカスタムソケットファクトリを使用します。次に、ローカルレジストリを作成し、そのレジストリ内で、リモートオブジェクトのスタブへの参照を「Hello」という名前でバインドします。
package examples.rmisocfac;
import java.io.*;
import java.rmi.*;
import java.rmi.server.*;
import java.rmi.registry.*;
public class HelloImpl implements Hello {
public HelloImpl() {}
public String sayHello() {
return "Hello World!";
}
public static void main(String args[]) {
if (System.getSecurityManager() == null) {
System.setSecurityManager(new SecurityManager());
}
byte pattern = (byte) 0xAC;
try {
/*
* Create remote object and export it to use
* custom socket factories.
*/
HelloImpl obj = new HelloImpl();
RMIClientSocketFactory csf = new XorClientSocketFactory(pattern);
RMIServerSocketFactory ssf = new XorServerSocketFactory(pattern);
Hello stub =
(Hello) UnicastRemoteObject.exportObject(obj, 0, csf, ssf);
/*
* Create a registry and bind stub in registry.
*
LocateRegistry.createRegistry(2002);
Registry registry = LocateRegistry.getRegistry(2002);
registry.rebind("Hello", stub);
System.out.println("HelloImpl bound in registry");
} catch (Exception e) {
System.out.println("HelloImpl exception: " + e.getMessage());
e.printStackTrace();
}
}
}
クライアントアプリケーションは、サーバーアプリケーションが使用するレジストリへの参照を取得します。次に、リモートオブジェクトのスタブを検索して、リモートメソッド sayHello を呼び出します。
package examples.rmisocfac;
import java.rmi.*;
import java.rmi.registry.*;
public class HelloClient {
public static void main(String args[]) {
if (System.getSecurityManager() == null) {
System.setSecurityManager(new SecurityManager());
}
try {
Registry registry = LocateRegistry.getRegistry(2002);
Hello obj = (Hello) registry.lookup("Hello");
String message = obj.sayHello();
System.out.println(message);
} catch (Exception e) {
System.out.println("HelloClient exception: " +
e.getMessage());
e.printStackTrace();
}
}
}
アプリケーションのコンパイルおよび実行は、次の 4 つのステップで行います。
ステップ 1:
リモートインタフェース、クライアント、およびサーバーの各クラスをコンパイルする
javac -d . XorInputStream.java javac -d . XorOutputStream.java javac -d . XorSocket.java javac -d . XorServerSocket.java javac -d . XorServerSocketFactory.java javac -d . XorClientSocketFactory.java javac -d . Hello.java javac -d . HelloClient.java javac -d . HelloImpl.java
ステップ 2:
実装クラス上で rmic を実行する (オプション)
注: リモートオブジェクトのクラスに対応するスタブクラスを事前に生成するための rmic は、リモートオブジェクトがリリース 5.0 より前のクライアントをサポートする必要がある場合にだけ実行する必要があります。リリース 5.0 では、リモートオブジェクトのエクスポート時にそのリモートオブジェクトのクラスに対応する事前生成のスタブクラスをロードできないと、リモートオブジェクトのスタブクラスが動的に生成されます。
rmic -d . examples.rmisocfac.HelloImpl
java -Djava.security.policy=policy examples.rmisocfac.HelloImpl
サーバー側の出力は、次のようになります。
HelloImpl bound in registry
別のウィンドウでクライアントアプリケーションを起動し、アプリケーションのクラスが次のクラスパス内にあることを確認します。
java -Djava.security.policy=policy examples.rmisocfac.HelloClient
クライアント側の出力は次のようになります。
Hello World!
注:このサーバーアプリケーションとクライアントアプリケーションはどちらも、セキュリティーポリシーファイルを使用して、ローカルのクラスパス (カレントディレクトリ) 内のファイルへのアクセス権のみを付与します。サーバーアプリケーションは接続を受け入れる権限を必要とし、サーバーアプリケーションとクライアントアプリケーションの両方が、接続を作成する権限を必要とします。指定されたコードベース URL (カレントディレクトリからの相対位置を表す「file:」URL) にアクセス権 java.net.SocketPermission が付与されます。このアクセス権では、特権を持たないポート (1024 以上のポート) 上で、任意のホストからの接続を受け入れること、および任意のホストへの接続を行うことが許可されます。
grant codeBase "file:." {
permission java.net.SocketPermission "*:1024-", "connect,accept";
};
レジストリの通信を保護するなど、レジストリへの通信は、適切なカスタムソケットファクトリを LocateRegistry.createRegistry および LocateRegistry.getRegstry 呼び出しに渡すことによって、カスタマイズします。