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

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

inetdは、Java 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は、この例のクラス・パスです。

次の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, 2020, Oracle and/or its affiliates. All rights reserved.