このチュートリアルでは、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
を実装するこの例のカスタム・ソケット・ファクトリは、単純な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
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
呼出しに渡すことによって、カスタマイズします。