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.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 は、この例のクラスパスです。
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.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
にハングアップ信号を送信して変更済みの構成が読み込まれるようにしてください。また、その前に起動されたすべてのプロセスを終了させることも忘れないでください。