Designing Services to be Launched from |
Documentation Contents |
On the Solaris Operating System (Solaris OS), the Internet
services daemon inetd
provides an
alternative to starting up services at system boot time. This
daemon, a server process for Internet standard services,
can be configured to start services on demand. For details on
the Internet services daemon, see the Solaris OS
inetd(1M)
man page.
As of the J2SE 5.0 release, inetd
can be
configured to start Java RMI services on demand. An application,
however, must use a specific technique to enable that application and
its constituent services to be started from inetd
.
First, the service program should create a local registry, exported to
use the I/O socket inherited from inetd
. The service
program should then bind the service's proxy in this
specially exported registry so that clients can look up the service.
Once the service program is complete, inetd
can be
configured to start this program when a client attempts to connect to
the service's local registry in order to look up the service by name.
This tutorial first describes how to structure a
service program (employing a specially exported local registry)
so that the service can be started from inetd
when
clients connect to the service's local registry.
Next, the tutorial describes how to configure
inetd
to launch the service program. You will need to
add an entry to each of two configuration files that
inetd
uses, /etc/inetd.conf
and
/etc/services
. Editing these files requires root access
to the machine that the service will run on.
After inetd
has been reconfigured, you should
test your configuration to make sure that it works properly.
This tutorial has the following steps:
- Implement the service program
- Configure
/etc/inetd.conf
- Configure
/etc/services
- Force
inetd
to read its new configuration- Test your configuration using a simple client
The server-side implementation uses the following source files:
The interface
ServiceInterface.java
: remote interface for the serviceInitializeRegistry.java
: utility to create/export a registry using an inherited channelServer.java
: service programServiceInterface
defines a remote interface with a single methodsendMessage
specified to take a single argument,message
, and return an acknowledgment that the message was received.The class
InitializeRegistry
defines a static utility methodinitializeWithInheritedChannel
that creates and exports a registry (using an inherited channel, if any), and binds a remote service's proxy in that registry for clients to look up.The service program
Server
implements the interfaceServiceInterface
and defines a staticmain
method for running the service. The staticmain
method does the following:
- Redirects
System.err
output to a file, in order to prevent the error output from being lost in the case that the service program is started frominetd
.- Parses the optional argument, which is the port number for the local registry.
- Creates and exports the server on an anonymous port.
- Invokes the
InitializeRegistry.initializeWithInheritedChannel
utility method with the service's proxy, the name for the service in the registry, and an optional port number to use if the program is run from the command line instead of being started frominetd
.- Prints out the message
ready
.- Waits forever.
The most interesting part of the implementation is in the
initializeWithInheritedChannel
utility method. This method uses theSystem.inheritedChannel
method which allows an application to obtain a channel (java.nio.channels.SocketChannel
orjava.nio.channels.ServerSocketChannel
, for example) inherited from a process that launched the virtual machine. Such an inherited channel can be used to either service a single incoming connection (in the case of aSocketChannel
), or accept multiple incoming connections (in the case ofServerSocketChannel
). In this way, applications launched byinetd
can obtain theSocketChannel
orServerSocketChannel
inherited frominetd
.The
initializeWithInheritedChannel
utility method first invokes theSystem.inheritedChannel
method to obtain the inherited channel. The inherited channel must either benull
or aServerSocketChannel
or the method will throw anIOException
.If the inherited channel is
null
, there is no inherited channel; that is, the program was run from the command line. In this case, theinitializeWithInheritedChannel
method simply exports a registry on the specified port (which must be nonzero) and binds the specified service proxy in that registry.If the inherited channel is a
ServerSocketChannel
instance, then the program was started frominetd
. In this case, theinitializeWithInheritedChannel
method exports a registry with anRMIServerSocketFactory
whosecreateServerSocket
method returns aServerSocket
that delays accepting requests from the inheritedServerSocketChannel
until the specified proxy is bound in the registry.In the case that the program is started from
inetd
, it is important that the registry not accept any incoming connections on the inheritedServerSocket
until the service's proxy is bound in the local registry. If a connection is accepted before the service is bound in the registry, a client might receive ajava.rmi.NotBoundException
from attempting to look up the service proxy.For details on how to implement a
ServerSocket
that delays accepting requests until a service is bound in the registry, see the private nested classDelayedAcceptServerSocket
defined in theInitializeRegistry
class.Compile the service program as follows:
where classDir is the class path for this example.% javac -d classDir ServiceInterface.java InitializeRegistry.java Server.javaNote that if the service needs to be accessible to clients running on releases earlier than J2SE 5.0, you will need to use
rmic
to generate a stub for the remote service.The next three sections describe how to set up
inetd
to start the service program.
/etc/inetd.conf
The
/etc/inetd.conf
configuration file contains entries for the services to be launched wheninetd
receives a request over a socket. For details on the format of this configuration file see the Solaris OSinetd.conf(4)
man page.To configure
inetd
to launch the service program, add the following entry to the/etc/inetd.conf
configuration file (requires root access to the machine):where jreHome is a path to the installed JRE, and classpath is the class path for the example.example-server stream tcp wait nobody jreHome/bin/java \ java -classpath classpath example.inetd.ServerIf the program needs to be run as a user other than
nobody
, replacenobody
above with the user ID under which the program should run.
/etc/services
Next, the name chosen to refer to the service,
example-server
, needs to be listed as a service in the/etc/services
configuration file. For details on the format of this configuration file see the Solaris OSservices(4)
man page.To list
example-server
as a service, add the following entry to the/etc/services
configuration file (requires root access to the machine):where port is the port number for the service's local registry.example-server port/tcp
inetd
to read its new configurationNow that the configuration has been modified,
inetd
needs to read the new configuration so that it can listen on the appropriate ports for the services that are configured.But first, it is important to make sure that the service program is not already running. To do this, run the following command:
If the above command does not print out the information for a running% ps -ef | grep example.inetd.Serverjava
process for the service program, then the program is not running. If it does, then you will need to terminate the program first before continuing.Next,
inetd
needs to read its new configuration. To forceinetd
to reread its configuration, the runninginetd
process must be sent a hangup signal. First, find the process ID for the runninginetd
process by running the following command:which will print out something like this:% ps -ef | grep inetdIn this example, the process ID forroot 171 1 0 Sep 30 ? 0:02 /usr/sbin/inetd -sinetd
is171
. Now, theinetd
process can be sent a hangup signal by running the following command and supplying the process ID (requires root access):Now,% kill -HUP 171inetd
is all set to launch the service program when a client attempts to connect to the port configured above.
To test that
inetd
is configured properly, you can run a simple client program that looks up the service in the registry on the port configured above, and then invokes a method on the service. Attempting to connect to the service's local registry will, in turn, causeinetd
to launch the service program if the configuration is correct.The following is a simple program to look up the service and invoke a method on the service to send it a message. The program takes three command-line arguments, the host and port of the service's local registry, and the message to send to the service:
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); } }The full source (including comments) for the client is:
ServiceInterface.java
: remote interface for the serviceClient.java
: client programCompile and run this program as follows:
where classDir is the class path for this example, host is the host that the service is configured to run on, port is the port configured for the service in the% javac -d classDir ServiceInterface.java Client.java % java -classpath classDir example.inetd.Client host port "message"/etc/services
file, and message is a string to send to the service.If the client program prints out that it sent a message to and received a message from the service, then the service program was successfully launched by
inetd
.If the client program either hangs or prints an exception trace, check the output produced by the service program. The service program redirects any output written to
System.err
to a file located in the directory specified by the propertyjava.io.tmpdir
, typically/var/tmp
on the Solaris OS. The output file is prefixed with "example-server-err" and has the suffix of ".tmp". The file name also contains other characters (typically numbers) in between to keep the file name unique.If the service program starts up successfully from
inetd
, the output file should contain the text "ready
" with no warning or error messages.If the file does not exist, the "
ready
" message is not in the file, or additional error output is in the file, then recheck the configuration. Upon changing theinetd
configuration, remember to sendinetd
a hangup signal so that it rereads its altered configuration, and remember to terminate any process started from the previous attempt before continuing.
Copyright ©
2004 Sun Microsystems, Inc. All Rights
Reserved.
Please send comments to: rmi-comments@java.sun.com |