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.java
: サービス用リモート・インタフェースInitializeRegistry.java
: 継承チャネルを使用してレジストリを作成/エクスポートするユーティリティServer.java
: サービス・プログラムServiceInterface
インタフェースで、単一の引数message
をとり、かつメッセージが受信されたという確認応答を返すように指定された単一メソッドsendMessage
付きのリモート・インタフェースを定義します。
InitializeRegistry
クラスで、staticユーティリティ・メソッドのinitializeWithInheritedChannel
を定義します。このメソッドは、レジストリを作成してエクスポートし(継承チャネルがある場合にはそれを使用)、クライアントが検索できるように、そのレジストリ内でリモート・サービスのプロキシをバインドします。
Server
サービス・プログラムで、ServiceInterface
インタフェースを実装し、サービスの実行用にstaticメソッドmain
を定義します。staticメソッドmain
で、次の操作が実行されます。
inetd
から起動された場合にエラー出力が失われることを防ぐために、System.err
の出力をファイルにリダイレクトする。inetd
からではなくコマンド行からプログラムを実行する場合に使用するオプションのポート番号で、InitializeRegistry.initializeWithInheritedChannel
ユーティリティ・メソッドを呼び出す。ready
メッセージを出力する。実装のなかでもっとも興味深い部分は、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.javaclassDirは、この例のクラス・パスです。
次の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.ServerjreHomeはインストール済みのJREへのパス、classpathはこの例へのクラス・パスです。
nobody
以外のユーザーとしてプログラムを実行する必要がある場合は、nobody
を、プログラムを実行する必要のあるユーザーIDに置き換えてください。
/etc/services
の構成次に、サービスを参照する名前のexample-server
を、/etc/services
構成ファイル内にサービスとして指定する必要があります。この構成ファイルの形式の詳細については、Solaris OSのservices(4)
のマニュアル・ページを参照してください。
サービスとしてexample-server
を指定するには、次のエントリを/etc/services
構成ファイルに追加します(マシンへのルート・アクセス権が必要)。
example-server port/tcpportは、サービスのローカル・レジストリ用のポート番号です。
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); } }
クライアント用ソースのすべて(コメントも含めて)は、次のとおりです。
ServiceInterface.java
: サービス用リモート・インタフェースClient.java
: クライアント・プログラム次のようにして、このプログラムをコンパイルし、実行します。
% 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
にハング・アップ信号を送信して変更済みの構成が読み込まれるようにしてください。また、その前に起動されたすべてのプロセスを終了させることも忘れないでください。