カスタム RMI ソケットファクトリの作成


このページでは、カスタムの RMI ソケットファクトリを作成してインストールする方法を説明します。カスタム RMI ソケットファクトリは次のような場合に役立ちます。 (1) RMI クライアントとサーバの通信をソケットを通じて行い、ソケットでデータの暗号化や圧縮を行う場合、または (2) 接続ごとに異なるソケットを使用する場合です。

自分専用の RMI ソケットファクトリをインストールすることにより、RMI トランスポート層が IP 上で TCP 以外のカスタムトランスポートプロトコルを使用できるようになります。 デフォルトでは、RMI は java.net.Socket により提供される TCP を使用します。

JavaTM 2 SDK, v1.2 より前のリリースでも、カスタムの java.rmi.RMISocketFactory サブクラスを作成して、java.net.Socket 以外のタイプのソケットを作成して RMI のトランスポート層で使用することができました。しかし、インストールされた RMI ソケットファクトリではオブジェクトごとに異なるタイプのソケットを作成することはできませんでした。たとえば JDK 1.1 では、RMI ソケットファクトリで 1 つのオブジェクトに SSL ソケットを作り、同一の Java Virtual Machine (JVM) 内の別のオブジェクトに対して TCP 上で直接 Java Remote Method Protocol (JRMP) を使用することはできませんでした。また JDK 1.2 より前は、自分のカスタムソケットのプロトコルだけを使用する rmiregistry のインスタンスを生成する必要がありました。

Java SDK, v1.2 ベータ 3 では、RMI クライアントがカスタムの RMI ソケットファクトリを使用できるようになりましたが、このソケットファクトリはダウンロードすることができなかったので、クライアントがこのソケットファクトリクラスをローカルで検索できる必要がありました。

Java 2 SDK, v1.2 のリリースから、必要な時にオブジェクトごとに異なる種類のソケット接続を生成するカスタム RMI ソケットファクトリを作成し、クライアント側のソケットファクトリをダウンロードして、デフォルトの rmiregistry をそのまま使うことができます。

このチュートリアルの構成は次のとおりです。

RMI クライアントとサーバ間の安全な通信に関心を持つ人は多いでしょう。RMI と SSL の関係については「RMI と SSL について」を参照してください。


単一タイプのソケットを生成する RMI ソケットファクトリの作成

単一タイプのソケットを生成するカスタム RMI ソケットファクトリは、次の 5 つのステップで作成します。
  1. 生成するソケットタイプを決定する
  2. クライアント側のソケットファクトリを記述して RMIClientSocketFactory を実装する
  3. RMIClientSocketFactorycreateSocket メソッドを実装する
  4. サーバ側のソケットファクトリを記述して RMIServerSocketFactory を実装する
  5. RMIServerSocketFactorycreateServerSocket メソッドを実装する

ステップ 1:
生成するソケットタイプを決定する

生成するソケットタイプは、アプリケーションの仕様により決まります。アプリケーションに適したソケットタイプを選択します。サーバが大量の機密データを扱う場合は、データを暗号化するソケットを選びます。サーバがビデオデータを扱う場合は、データを圧縮するソケットが必要です。

この例の RMI ソケットファクトリは、データを圧縮するソケットを生成します。「カスタムソケットタイプの作成」のページで examples.rmisocfac.CompressionSocket ソケットを作成します。

ステップ 2:
クライアント側のソケットファクトリを記述して RMIClientSocketFactory を実装する

クライアント側の RMI ソケットファクトリの実装は  RMIClientSocketFactory インタフェースの実装により開始します。この例のカスタムソケットファクトリは、CompressionClientSocketFactory という名前です。

次のコード例は、CompressionClientSocketFactory クラスのコードと、その次のステップ (createSocket メソッドをオーバーライドする) のコードです。次のステップについては、コード例の次に説明します。

package examples.rmisocfac;

import java.io.*;
import java.net.*;
import java.rmi.server.*;

public class CompressionClientSocketFactory
    implements RMIClientSocketFactory, Serializable {

    public Socket createSocket(String host, int port)
        throws IOException
    {
        CompressionSocket socket =
            new CompressionSocket(host, port);
        return socket;
    }

}
 

ステップ 3:
RMIClientSocketFactorycreateSocket メソッドを実装する

RMI ソケットファクトリの機能は RMI ランタイムにソケットを提供することなので、CompressionClientSocketFactory には、正しいタイプのソケット (CompressionSocket) を作成して返す RMIClientSocketFactory createSocket メソッドを実装する必要があります。上のコード例で、CompressionSocket が作成されて返されていることに注目してください。

ステップ 4:
サーバ側のソケットファクトリを記述して RMIServerSocketFactory を実装する

サーバ側の RMI ソケットファクトリの実装は  RMIServerSocketFactory  インタフェースの実装により開始します。この例のカスタムソケットファクトリは、CompressionServerSocketFactory という名前です。

次のコード例は、CompressionServerSocketFactory クラスのコードと、その次のステップ (createServerSocket の実装) のコードです。次のステップについては、コード例の次に説明します。

package examples.rmisocfac;

import java.io.*;
import java.net.*;
import java.rmi.server.*;
 
public class CompressionServerSocketFactory
    implements RMIServerSocketFactory, Serializable {

    public ServerSocket createServerSocket(int port)
        throws IOException
    {
        CompressionServerSocket server = new CompressionServerSocket(port);
        return server;
    }
}
 

ステップ 5:
RMIServerSocketFactorycreateServerSocket メソッドを実装する

RMI ソケットファクトリに createServerSocket を実装する方法は createSocket を実装する方法とほとんど同じですが、createServerSocket ではソケットタイプ CompressionServerSocket を作成して返す必要がある点が異なります。

これで、RMI ソケットファクトリの作成例を 1 つ学びました。次に、複数タイプのソケットを生成できるソケットファクトリの作成の例を学びます。


複数タイプのソケットを生成する RMI ソケットファクトリの作成

複数タイプのソケットを生成できる RMI ソケットファクトリを作成する手順は、単一タイプのソケットを生成するファクトリの場合とほぼ同じです。ただし、異なるソケットタイプ用の「ラッパー」を作成する必要があるので、その方法を説明します。

この例では、カスタムの RMIClientSocketFactory クラスは複数のソケットタイプを扱うので、MultiClientSocketFactory という名前にします。 同様に、カスタムの RMIServerSocketFactory クラスの名前は MultiServerSocketFactory です。各ソケットファクトリには、このオブジェクトインスタンスにどのプロトコルをサポートするかを指定するコンストラクタがあります。

すでに説明した例と同じ手順でカスタムソケットファクトリの作成を開始します。 まず、生成するソケットタイプを決定します。

ステップ 1:
生成するソケットタイプを決定する

このカスタム RMI ソケットファクトリは、XorSocketCompressionSocket、およびデフォルトの java.net.Socket の 3 つのタイプのソケットを生成します。

XorSocket タイプのソケットの実装のソースコードを見るには、ここをクリックしてください。

ステップ 1 はこれで終わりです。次のコード例は、ステップ 2 からステップ 5 のコードです。コード例のあとで、それぞれのステップについて説明します。

            
package examples.rmisocfac;

import java.io.*;
import java.net.*;
import java.rmi.server.*;
public class MultiClientSocketFactory
    implements RMIClientSocketFactory, Serializable
{
    /* 
     * Get the default RMISocketFactory 
     */
    private static RMISocketFactory defaultFactory =
        RMISocketFactory.getDefaultSocketFactory();

    private String protocol;
    private byte[] data;

    public MultiClientSocketFactory(String protocol, byte[] data) {
        this.protocol = protocol;
        this.data = data;
    }  
    
    /*  
     * Override createSocket to call the default 
     * RMIClientSocketFactory's createSocket method.This 
     * way, you'll get a TCP connection if you don't 
     * specify compression or xor 
     */ 
    public Socket createSocket(String host, int port)
        throws IOException
    {
        if (protocol.equals("compression")) {
            return new CompressionSocket(host, port);

        } else if (protocol.equals("xor")) {
            if (data == null || data.length != 1)
                throw new IOException("invalid argument for XOR protocol");
            return new XorSocket(host, port, data[0]);
 
        }
 
        return defaultFactory.createSocket(host, port);
    }
}

ステップ 2:
RMIClientSocketFactory を実装する

上のコード例で、MultiClientSocketFactory クラスは RMIClientSocketFactory を実装します。
 

ステップ 3:
RMIClientSocketFactorycreateSocket メソッドを実装する

この例で作成するソケットファクトリはデフォルトタイプに加えて 2 つの異なるタイプのソケットを生成するので、createSocket メソッドをオーバーライドする必要があります。MultiClientSocketFactory コンストラクタの protocol フィールドが「xor」の場合は、XorSocket が作成されて返されます。protocol フィールドが「compression」の場合は、CompressionSocket が作成されて返されます。
 

ステップ 4:
サーバ側ソケットファクトリを記述して RMIServerSocketFactory を実装する

package examples.rmisocfac;

import java.io.*;
import java.net.*;
import java.rmi.server.*;

public class MultiServerSocketFactory
    implements RMIServerSocketFactory, Serializable
{
    /*
     * Get the default RMISocketFactory
     */
    private static RMISocketFactory defaultFactory =
        RMISocketFactory.getDefaultSocketFactory();

    private String protocol;
    private byte[] data;

    public MultiServerSocketFactory(String protocol, byte[] data) {
        this.protocol = protocol;
        this.data = data;
    }

    /*
     * Override createServerSocket to call the default
     * RMIServerSocketFactory's createServerSocket method, if
     * an invalid protocol is specified.
     */
    public ServerSocket createServerSocket(int port)
        throws IOException
    {
        if (protocol.equals("compression")) {
            return new CompressionServerSocket(port);

        } else if (protocol.equals("xor")) {
            if (data == null || data.length != 1)
                throw new IOException("invalid argument for XOR protocol");
            return new XorServerSocket(port, data[0]);

        }

        return defaultFactory.createServerSocket(port);
    }
}

ステップ 5:
RMIServerSocketFactorycreateServerSocket メソッドを実装する

createServerSocket をオーバーライドする方法は、createSocket のオーバーライドとほとんど同じです。ステップ 3 と同様に、RMIServerSocketFactorycreateServerSocket メソッドで作成されて返されるソケットのタイプは、MultiClientSocketFactory コンストラクタの protocol フィールドによって決まります。

アプリケーションでカスタム RMI ソケットファクトリを使用する

リモートオブジェクトでカスタム RMI ソケットファクトリを使用するのに必要な追加のステップは、2 つだけです。
  1. リモートオブジェクトの実装で、RMIClientSocketFactory パラメータと RMIServerSocketFactory パラメータを受け取る UnicastRemoteObject (または Activatable) コンストラクタを呼び出すコンストラクタを記述する
  2. プログラムに対してソケットの作成を許可する java.security.policy ファイルを記述する
: この例で使用するポリシーファイルでは、簡潔にする目的で、任意の場所の任意のユーザに対してグローバルなアクセス権を与えます。このポリシーファイルは、実稼働環境では使用しないでください。java.security.policy ファイルを使ってアクセス権を適切に指定する方法については、次のドキュメントを参照してください。
「デフォルトの Policy の実装とポリシーファイルの構文」
「Java 2 SDK におけるアクセス権」
 

ステップ 1:
RMIClientSocketFactoryRMIServerSocketFactory をパラメータにとる UnicastRemoteObjectコンストラクタを呼び出す、リモートオブジェクトコンストラクタを記述する

カスタム RMI ソケットファクトリを作成する場合は、使用するソケットタイプを RMI ランタイムに通知する手段が必要です。サーバが UnicastRemoteObject を継承すると仮定すると、この通知は、次のような UnicastRemoteObject コンストラクタを呼び出すリモートオブジェクトコンストラクタを作成することにより実現できます。

protected UnicastRemoteObject(int port, RMIClientSocketFactory csf,
    RMIServerSocketFactory ssf)

次のコード例は、オリジナルの RMI 版「Hello World」XorSocket タイプのソケットに適合するように修正した HelloImpl コンストラクタです。

    public HelloImpl(String protocol, byte [] pattern)
       throws RemoteException
    {
       super(0, new MultiClientSocketFactory(protocol, pattern),
           new MultiServerSocketFactory(protocol, pattern));
    }
 

ここで、UnicastRemoteObject コンストラクタに注目してください。

protected UnicastRemoteObject(int port, RMIClientSocketFactory csf,
    RMIServerSocketFactory ssf)

HelloImpl コンストラクタから呼び出されています。カスタムソケットファクトリが設定されたあとは、RMI クライアント/サーバアプリケーションで、目的のタイプのソケットが使用されます。

ステップ 2:
プログラムに対してソケットの作成を許可する java.security.policy ファイルを記述する

この例で使用するポリシーファイルは安全ではないので、製作環境では使用しないでください。

この例で使用するポリシーファイルは次のようになります。

grant {
 // Allow everything for now
 permission java.security.AllPermission;
};

次は、MultiClientSocketFactoryMultiServerSocketFactory を使い、XorSocket タイプのソケットを使って通信する「Hello World」プログラムです。


この例は、オリジナルの RMI チュートリアルの「Hello World」の例を修正したものです。もっとも大きな違いは、この例のクライアントはアプレットではないということです。またこの例では、クライアントクラスの名前は HelloClient です。さらに、すべてのクラスが examples.rmisocfac パッケージ内にある点が異なります。

この例では、クライアント、サーバ、レジストリはすべて同一のマシンで実行されることにします。 この点は重要です。

以下は、ファイル Hello.javaHello インタフェースです。パッケージ名以外は、このインタフェースはオリジナルの Hello.java から変更されていません。

        package examples.rmisocfac;
 
        public interface Hello extends java.rmi.Remote {
            String sayHello() throws java.rmi.RemoteException;
        }
クライアントクラス HelloClient.java の修正されたバージョンを見てみます。main の最初に RMISecurityManager が組み込まれていることに注目してください。それ以外は、HelloClient クラスはオリジナルと同じです。
        package examples.rmisocfac;
 
        import java.rmi.*;
 
        public class HelloClient {
 
            private static String message = "";
        
            public static void main(String args[]) {

               //Create and install a security manager
               if (System.getSecurityManager() == null)
                   System.setSecurityManager(new RMISecurityManager());

               try {
                   Hello obj = (Hello) Naming.lookup("/HelloServer");
                   message = obj.sayHello();
                   System.out.println(message);

               } catch (Exception e) {
                   System.out.println("HelloClient exception: " +
                                       e.getMessage());
                   e.printStackTrace();
               }

            }
 
        }
最後のコード例は、HelloImpl.java ファイルの HelloImpl のソースコードを修正したものです。パッケージ名の変更のほか、クライアントとサーバの間の RMI 呼び出しにソケットタイプ「xor」を使用できるように、2 つの修正が加えられています。コンストラクタが、クライアントとサーバのソケットファクトリをパラメータにとる UnicastRemoteObject コンストラクタを呼び出すように変更されていることに注目してください。
        package examples.rmisocfac;

        import java.io.*;
        import java.rmi.*;
        import java.rmi.server.*;

        public class HelloImpl extends UnicastRemoteObject implements Hello {
 
            /*
             * Constructor calls constructor of superclass with
             * client and server socket factory  parameters.
             */
            public HelloImpl(String protocol, byte [] pattern) throws RemoteException {
                super(0, new MultiClientSocketFactory(protocol, pattern),
                         new MultiServerSocketFactory(protocol, pattern));
            }

            /*
             * Remote method returns String "Hello World!"
             * when invoked.
             */
            public String sayHello() throws RemoteException {
                return  "Hello World!";
            }
 
            public static void main(String args[]) {

                //Create and install a security manager
                if (System.getSecurityManager() == null)
                    System.setSecurityManager(new RMISecurityManager());

                byte [] aPattern = { (byte)1011 };
                try {
                    HelloImpl obj = new HelloImpl("xor", aPattern);
                    Naming.rebind("/HelloServer", obj);
                    System.out.println("HelloServer bound in registry");
                } catch (Exception e) {
                    System.out.println("HelloImpl err:" + e.getMessage());
                    e.printStackTrace();
                }
            }
        }

この例と関連ファイルをダウンロードするには、ここをクリックしてください。

上記の「Hello World」プログラムのコンパイル方法および実行方法については、ここをクリックしてください。

* この Web サイトで使用されている用語「Java Virtual Machine」または「JVM」は、Java プラットフォーム用の仮想マシンを表します。


Copyright © 1999 Sun Microsystems, Inc. All Rights Reserved.
コメントの送付先: rmi-comments@java.sun.com 
Sun