inetd から起動されるサービスの設計

Solaris オペレーティングシステム (Solaris OS) では、インターネットサービスデーモンの inetd が、システムブート時にサービスを起動する代替手段になります。インターネットの標準サービスに対するサーバープロセスであるこのデーモンを、必要に応じてサービスを起動するように構成することができます。インターネットサービスデーモンの詳細については、Solaris OS の inetd(1M) のマニュアルページを参照してください。

inetd は、JavaTM Remote Method Invocation (Java RMI) サービスを必要に応じて起動するように構成することができます。ただし、アプリケーションとその構成要素のサービスが inetd から起動されるようにするには、アプリケーションで特別な技法を使う必要があります。まず、サービスプログラムで、inetd から継承される I/O ソケットを使用できるようにエクスポートされる、ローカルレジストリを作成する必要があります。次に、この特別にエクスポートされたレジストリ内にサービスのプロキシをバインドして、クライアントがサービスを検索できるようにします。このサービスプログラムの作成が終わったら、クライアントがサービスのローカルレジストリに接続して名前でサービスを検索するときに、このプログラムが起動されるように、inetd を構成できます。

このチュートリアルでは、クライアントがサービスのローカルレジストリに接続した時点で inetd からサービスを起動できるように、特別にエクスポートされたローカルレジストリを使用してサービスプログラムを構築する方法を最初に説明します。

次に、サービスプログラムを起動するための、inetd の構成方法を説明します。inetd に使用される /etc/inetd.conf および /etc/services の 2 つの構成ファイルには、それぞれエントリを追加する必要があります。これらのファイルを編集するには、サービスが実行されるマシンへのルートアクセス権が必要です。

inetd を再構成したあとで、正しく機能するかどうかを確認するために構成をテストする必要があります。

このチュートリアルでは、次の手順を実行します。


サービスプログラムの実装

サーバー側の実装には、次のソースファイルを使います。

ServiceInterface インタフェースで、単一の引数 message をとり、かつメッセージが受信されたという確認応答を返すように指定された単一メソッド sendMessage 付きのリモートインタフェースを定義します。

InitializeRegistry クラスで、static ユーティリティーメソッドの initializeWithInheritedChannel を定義します。このメソッドは、レジストリを作成してエクスポートし (継承チャネルがある場合にはそれを使用)、クライアントが検索できるように、そのレジストリ内でリモートサービスのプロキシをバインドします。

Server サービスプログラムで、ServiceInterface インタフェースを実装し、サービスの実行用に static メソッド main を定義します。static メソッド main で、次の操作が実行されます。

実装のなかでもっとも興味深い部分は、initializeWithInheritedChannel ユーティリティーメソッド内にあります。このメソッドでは、仮想マシンを起動したプロセスから継承されたチャネル (たとえば java.nio.channels.SocketChannel または java.nio.channels.ServerSocketChannel) をアプリケーションが取得できるようにする、System.inheritedChannel メソッドを使います。この継承チャネルは、SocketChannel の場合は単一の着信接続を行うため、ServerSocketChannel の場合は複数の着信接続を受け入れるために使用できます。このようにして、inetd によって起動されたアプリケーションは、inetd から継承された SocketChannel または ServerSocketChannel を取得することができます。

initializeWithInheritedChannel ユーティリティーメソッドは、継承チャネルの取得のためにまず System.inheritedChannel メソッドを呼び出します。継承チャネルは、null または ServerSocketChannel である必要があり、それ以外の場合、このメソッドにより IOException がスローされます。

継承チャネルが null の場合、継承されたチャネルがないことを示します。つまり、プログラムはコマンド行から実行されました。この場合、initializeWithInheritedChannel メソッドは指定されたポート (ゼロ以外) のレジストリを単純にエクスポートし、そのレジストリ内の指定されたサービスプロキシをバインドします。

継承チャネルが ServerSocketChannel インスタンスの場合は、プログラムが inetd から起動されています。この場合、initializeWithInheritedChannel メソッドは RMIServerSocketFactory を使用してレジストリをエクスポートします。RMIServerSocketFactory の createServerSocket メソッドは、指定されたプロキシがレジストリ内にバインドされるまで、継承された ServerSocketChannel からの要求の受け入れを遅らせる ServerSocket を返します。

プログラムが inetd から起動された場合は、サービスのプロキシがローカルレジストリ内にバインドされるまで、レジストリは、継承された ServerSocket 上の着信接続を何も受け入れることができないということが重要な点です。サービスがレジストリ内にバインドされる前に接続が受け入れられた場合、クライアントは、サービスプロキシの検索をしようとして java.rmi.NotBoundException を受け取ることがあります。

サービスがレジストリ内にバインドされるまで要求の受け取りを遅らせる ServerSocket の実装方法の詳細については、InitializeRegistry クラスに定義された private のネストされたクラス DelayedAcceptServerSocket を参照してください。

次のようにして、サービスプログラムをコンパイルします。

% javac -d classDir ServiceInterface.java InitializeRegistry.java Server.java
classDir は、この例のクラスパスです。

J2SE 5.0 より前のリリース上で実行されるクライアントからサービスにアクセスする必要がある場合は、rmic を使用してリモートサービス用のスタブを作成する必要があります。

次の 3 つのセクションで、サービスプログラムを起動するように inetd を設定する方法を説明します。

/etc/inetd.conf の構成

/etc/inetd.conf 構成ファイルには、inetd がソケット経由で要求を受け取ったときに起動されるサービス用のエントリが含まれています。この構成ファイルの形式の詳細については、Solaris OS の inetd.conf(4) のマニュアルページを参照してください。

サービスプログラムを起動するように inetd を構成するには、次のエントリを /etc/inetd.conf 構成ファイルに追加します (マシンへのルートアクセス権が必要)。

example-server stream tcp wait nobody jreHome/bin/java  \
     java -classpath classpath example.inetd.Server
jreHome はインストール済みの JRE へのパス、classpath はこの例へのクラスパスです。

nobody 以外のユーザーとしてプログラムを実行する必要がある場合は、nobody を、プログラムを実行する必要のあるユーザー ID に置き換えてください。

/etc/services の構成

次に、サービスを参照する名前の example-server を、/etc/services 構成ファイル内にサービスとして指定する必要があります。この構成ファイルの形式の詳細については、Solaris OS の services(4) のマニュアルページを参照してください。

サービスとして example-server を指定するには、次のエントリを /etc/services 構成ファイルに追加します (マシンへのルートアクセス権が必要)。

example-server        port/tcp
port は、サービスのローカルレジストリ用のポート番号です。

inetd による新しい構成の読み込み

ここまでで構成ファイルが変更されたので、inetd は新しい構成を読み取る必要があります。その結果、構成されたサービスに対応する適切なポートで待機できるようになります。

ただし最初に、サービスプログラムがまだ実行されていないことを確認する必要があります。これを行うには、次のコマンドを実行します。

% ps -ef | grep example.inetd.Server
上記コマンドによってサービスプログラムのために実行されている java プロセスに関する情報が表示されない場合は、プログラムが実行されていないことになります。情報が表示された場合は、作業を続行する前に、まずそのプログラムを終了させる必要があります。

次に、inetd が新しい構成を読み取る必要があります。inetd に構成を再度読み取らせるには、実行中の inetd プロセスにハングアップの信号を送信する必要があります。まず、次のコマンドを実行して、実行中の inetd プロセスのプロセス ID を調べます。

% ps -ef | grep inetd
このコマンドによって、次のように表示されます。
root   171     1  0   Sep 30 ?        0:02 /usr/sbin/inetd -s
この例の inetd のプロセス ID は、171 です。これで、次のコマンドにプロセス ID を指定して実行すると (ルートアクセス権が必要)、inetd プロセスにハングアップ信号を送信することができます。
% kill -HUP 171
これで、クライアントが上記のように構成されたポートに接続しようとしたときにサービスプログラムを起動するための設定が、inetd に対してすべて行われました。

単純なクライアントを使用した構成のテスト

inetd が正しく構成されたことをテストするために、上記のように構成されたポート上のレジストリ内でサービスを検索し、そのサービス上でメソッドを呼び出す単純なクライアントプログラムを実行することができます。構成が正しい場合は、検出したサービスのローカルレジストリに接続しようとすることで、inetd がサービスプログラムを起動します。

次に、サービスを検索してそのサービス上でメッセージを送信するメソッドを呼び出す、単純なプログラムを示します。このプログラムは、3 つのコマンド行引数、つまりサービスのローカルレジストリのホストおよびポート番号、サービスに送信するメッセージを取ります。

package example.inetd;

import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class Client {

    public static void main(String[] args) throws Exception {

        int port = 0;
        String host = "";
        String message = "";
        
        if (args.length > 2) {
            host = args[0];
            try {
                port = Integer.parseInt(args[1]);
                if (port == 0) {
                    goodbye("nonzero port argument required", null);
                }
            } catch (NumberFormatException e) {
                goodbye("malformed port argument", e);
            }
            message = args[2];
        } else {
            usage();
        }

        Registry registry = LocateRegistry.getRegistry(host, port);
        ServiceInterface proxy =
            (ServiceInterface) registry.lookup("ServiceInterface");
        System.out.println("sending message: " + message);
        System.out.println("received message from proxy: " +
                           proxy.sendMessage(message));
        
        System.out.println("done.");
    }

    private static void goodbye(String message, Exception e) {
        System.err.println("Client: " + message + (e != null ? ": " : ""));
        if (e != null) {
            e.printStackTrace();
        }
        System.exit(1);
    }

    private static void usage() {
        System.err.println("Client <host> <port> <message>");
        System.exit(1);
    }
}

クライアント用ソースのすべて (コメントも含めて) は、次のとおりです。

次のようにして、このプログラムをコンパイルし、実行します。

% javac -d classDir ServiceInterface.java Client.java
% java -classpath classDir example.inetd.Client host port "message"
classDir はこの例のクラスパス、host はサービスがそこで実行されるように構成されたホスト、port/etc/services ファイル内でサービス用に構成されたポート、message はサービスに送信される文字列です。

クライアントプログラムがメッセージを送信し、サービスプログラムからメッセージを受信したことが表示されれば、サービスプログラムが inetd から正しく起動されたことになります。

クライアントプログラムがハングアップするか例外のトレースを出力した場合は、サービスプログラムによって作成された出力ファイルをチェックしてください。サービスプログラムは、System.err に書き出されたすべての出力を java.io.tmpdir プロパティーで指定されたディレクトリ内のファイルにリダイレクトします。通常このディレクトリは、Solaris OS の /var/tmp です。この出力ファイルの接頭辞は「example-server-err」、接尾辞は「.tmp」です。このファイル名には、ファイル名を一意にするための文字 (通常は数字) が接頭辞と接尾辞の間に含まれています。

サービスプログラムが inetd から正しく起動された場合、出力ファイルには「ready」というテキストが含まれています。警告もエラーメッセージも含まれていません。

ファイルが存在しない場合は、「ready」メッセージがファイル内にないか、その他のエラー出力がファイル内に存在するので、構成を再チェックしてください。inetd 構成の変更を終えたときには、inetd にハングアップ信号を送信して変更済みの構成が読み込まれるようにしてください。また、その前に起動されたすべてのプロセスを終了させることも忘れないでください。


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