Java RMIによるカスタム・ソケット・ファクトリの使用

このチュートリアルでは、Java Remote Method Invocation (Java RMI)によってカスタム・ソケット・ファクトリを実装して使う手順を説明します。カスタム・ソケット・ファクトリは、リモート・メソッド呼出しがネットワーク・レベルで通信する方法を制御するために使用できます。たとえば、このカスタム・ソケット・ファクトリを使用して、ソケット・オプションの設定、アドレスのバインドの制御、認証に要求されるような接続の確立の制御、および暗号化や圧縮などのデータのエンコードの制御を行うことができます。

コンストラクタやjava.rmi.server.UnicastRemoteObjectまたはjava.rmi.activation.ActivatableexportObjectメソッドなどでリモート・オブジェクトがエクスポートされる場合、カスタム・クライアント・ソケット・ファクトリ(java.rmi.server.RMIClientSocketFactoryインスタンス)およびカスタム・サーバー・ソケット・ファクトリ(java.rmi.server.RMIServerSocketFactoryインスタンス)を指定して、リモート・オブジェクトのリモート呼出し通信時に使用されるようにすることが可能です。

クライアントのソケット・ファクトリは、リモート呼出しを開始するために使用されるソケットの作成を制御し、接続が確立される方法と使用するソケットのタイプを制御するのに使用できます。サーバーのソケット・ファクトリは、リモート呼出しを受信するために使用するサーバー・ソケットの作成を制御するため、着信接続の待機および受け入れ方法に加え、着信接続に使用するソケットのタイプの制御にも使用できます。

リモート・オブジェクトに対応するリモート・スタブには、リモート・オブジェクトがエクスポートされるときにあれば、クライアントのソケット・ファクトリが含まれます。したがって、クライアントのソケット・ファクトリは直列化が可能であることが必要であり、そのコードは、スタブ・クラスのコードやリモート呼出しで渡されるその他の直列化できるオブジェクトと同様に、クライアントがダウンロードすることができます。

また、カスタム・ソケット・ファクトリでレジストリをエクスポートするためのLocateRegistry.createRegistryメソッドと、カスタムのクライアント・ソケット・ファクトリでレジストリに対応するスタブを取得するためのLocateRegistry.getRegistryメソッドもあります。

(さらに、Java RMI用のグローバル・ソケット・ファクトリもあることに注意してください。これは、java.rmi.server.RMISocketFactorysetSocketFactoryメソッドで設定できます。このグローバル・ソケット・ファクトリは、リモート・スタブがカスタムのクライアント・ソケット・ファクトリを含んでいないときにソケットを作成する場合、およびリモート・オブジェクトがカスタムのサーバー・ソケット・ファクトリ付きでエクスポートされないときにサーバー・ソケットを作成する場合に使用されます。)

このチュートリアルは次の3つの部分にわかれています。

このチュートリアルで使われているソース・コードは、次のファイル形式から選ぶことができます。

多くのユーザーは、相互認証や暗号化などの、Java RMIクライアントとサーバー間の安全な通信に興味を持っています。カスタム・ソケット・ファクトリには、このためのフックが用意されています。詳細は、「Java RMIでのSSLの使用」を参照してください。

カスタム・ソケット・ファクトリの実装

次の3つのステップで、一対のカスタム・ソケット・ファクトリ・クラスを実装します。
  1. カスタムServerSocketSocketを実装する。
  2. カスタムRMIClientSocketFactoryを実装する。
  3. カスタムRMIServerSocketFactoryを実装する。

ステップ1:
カスタムServerSocketSocketを実装する

使用するソケットのタイプは、アプリケーションの仕様によって決まります。サーバーが機密データを送信または受信する場合は、データを暗号化するソケットを選びます。

この例のカスタム・ソケット・ファクトリは、単純なXOR暗号化および暗号解読を実行するソケットを生成します。この種の暗号化は、暗号解読の知識のある者であれば簡単に解読されますが、ここでは例を単純にするために使用します。

カスタムXORソケットの実装には、次のソースが含まれます。XORソケットは、特殊な入力と出力のストリーム実装を使用して、ソケットに対して読み書きされるデータのXOR処理を行います。

ステップ2:
カスタム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);
    }
}

ステップ3:
カスタム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);
    }

}

アプリケーションでのカスタム・ソケット・ファクトリの使用

リモート・オブジェクト用のカスタム・ソケット・ファクトリを使用するのに必要な追加のステップは、2つだけです。
  1. カスタムのRMIClientSocketFactoryおよびRMIServerSocketFactory実装を使用するリモート・オブジェクトを作成してエクスポートする、サーバー・アプリケーションを作成する。リモート・オブジェクトのスタブへの参照をJava RMIレジストリに保存して、クライアントがそれを検索できるようにする。
  2. リモート・オブジェクトのスタブを検索してリモート・メソッドを呼び出すクライアント・アプリケーションを作成する。カスタム・ソケット・ファクトリは、クライアント・アプリケーション内で参照される必要はない。クライアントがリモート・オブジェクトのスタブを検索すると、クライアント側ソケット・ファクトリがクライアントにダウンロードされる。

ステップ1:
サーバー・アプリケーションを作成する

リモート・オブジェクトとの通信でカスタム・ソケットを使用する必要がある場合は、リモート・オブジェクトをエクスポートするときに使用するカスタム・ソケット・ファクトリを指定する必要があります。カスタム・ソケット・ファクトリを指定するリモート・オブジェクトがアプリケーションからエクスポートされると、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();
        }
    }
} 

ステップ2:
クライアント・アプリケーションを作成する

クライアント・アプリケーションは、サーバー・アプリケーションが使用するレジストリへの参照を取得します。次に、リモート・オブジェクトのスタブを検索して、リモート・メソッド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. リモート・インタフェース、クライアント、およびサーバーの各クラスをコンパイルする
  2. サーバーを起動する
  3. クライアントの実行

ステップ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:
サーバーを起動する

java -Djava.security.policy=policy examples.rmisocfac.HelloImpl

サーバー側の出力は、次のようになります。

      HelloImpl bound in registry

ステップ3:
クライアントの実行

別のウィンドウでクライアント・アプリケーションを起動し、アプリケーションのクラスが次のクラス・パス内にあることを確認します。

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呼出しに渡すことによって、カスタマイズします。


Copyright © 1993, 2020, Oracle and/or its affiliates. All rights reserved.