Distributed objects
Remote Method Invocation (RMI) permits messaging objects in other processes and even on other hosts. This sections demonstrates the principle with a system to transmit the current time
and then a system like syslog
is implemented to show that one can distribute classes that are specialized later. RMI actually can load classes (methods) across the network. The application framework
uses RMI to manage a single instance of an application.
This section is based on a paper by Paul Tymann. On Linux, the examples require JDK 1.1.5. To simplify compilation on Windows class names begin with lower case letters. Still, some symbolic links are required as aliases.
Principle
java.reflect can be used to describe arbitrary method calls. If they are serialized they can be sent across a network connection. Certain aspects of this can be completely automated:
![]()
rmic
generates classes that represent the server object at the client and that wait at the server for calls and forward them to the actual server object.
rmiregistry
is a simple name server that registers server objects locally.
File hierarchy
To demonstrate that clients and server use different classes the following file hierarchy
is used:
| code/programs/rmi/ | root | |
timed is a client-server system: timed.server makes the current time available, timed.client obtains it locally or on an explicitly specified server and displays it.
timed illustrates the principal architecture of a client-server system based on RMI and shows how to use the tools rmic
and rmiregistry
.
Interface
{programs/rmi/src/timed/timed.java}
package timed;
/** An interface describing a distributed time service */
public interface timed extends java.rmi.Remote {
/** name under which the service is registered */
final String id = "Time";
/** returns the current time */
String getTime () throws java.rmi.RemoteException;
}
{}
An interface declares the methods that the client can call at the server. It must be derived from Remote
-- this signifies that RMI is used.
id is defined here so that client and server use the same name for connecting. The name should be made much more specific.
/** A class with a trivial main program to obtain the current time */
public class client {
/** obtains and displays the current time from the server */
public static void main (String args []) {
String host = args != null && args.length > 0 ? args[0] : "localhost";
String id = "//"+ host +"/"+ timed.id;
try {
System.setSecurityManager(new RMISecurityManager());
System.out.println(((timed)Naming.lookup(id)).getTime());
} catch (Exception e) {
System.out.println(e); System.exit(1);
}
}
}
{}
Using the appearance of a URL, the client (optionally) prefixes the id with a DNS or IP address. If necessary the rmiregistry
port (1099) can be specified following a colon.
Naming.lookup()
contacts rmiregistry and hopefully returns a proxy for a registered server object. The client can use the proxy as if it were the real object.
The RMISecurityManager
mostly deals with loading classes across the net. It prevents for example that the client receives file access privileges from the server.
import java.rmi.*;
import java.rmi.server.UnicastRemoteObject;
/** A class to serve the current time */
public class server extends UnicastRemoteObject implements timed {
/** required because of the exception */
public server () throws RemoteException {
}
/** returns the current time */
public String getTime () throws RemoteException {
return new java.util.Date().toString();
}
/** starts a server to return the current time */
public static void main (String args []) {
try {
System.setSecurityManager(new RMISecurityManager());
Naming.rebind(timed.id, new server());
} catch (Exception e) {
System.out.println(e); System.exit(1);
}
}
}
{}
Naming.rebind()
contacts the local rmiregistry
and registers or replaces a reference to an object that implements the interface that the client expects. The server can only register the id with the local rmiregistry -- host and port can usually be omitted.
Because the server is derived from UnicastRemoteObject
,the object is automatically exported for peer to peer connections.
> cd code\programs\rmi\src\timed
> set CLASSPATH=..
> javac -g Client.java Server.java
> rmic -g -keepgenerated timed.Server
First, client and server and (implicitly) the interface are compiled. Then rmic
is used to generate and compile the auxiliary classes server_Skel and server_Stub. The server needs both(!), the client only needs server_Stub.
Next, several processes are run in parallel, where, however, rmiregistry
should only be started once:
> rmiregistry
> cd code\programs\rmi\server\timed
> java timed.server
> cd code\programs\rmi\client\timed
> java timed.client
Mon Dec 08 19:34:34 GMT+00:00 1997
$ cd code/programs/rmi/client/timed
$ java timed.client perky
Mon Dec 08 19:34:44 GMT+00:00 1997
If JDK 1.1.5 is used, compilation and execution can be on both, Windows or Linux.
logd is a client-server system: logd.server accepts objects as messages and displays them to standard output. logd.client sends text from the command line to a system specified as the first argument; by default it sends the time to the local host.
logd shows that complete objects can be shipped.
Interface
{programs/rmi/src/logd/logd.java}
package logd;
import java.io.Serializable;
/** An interface describing a distributed log service */
public interface logd extends java.rmi.Remote {
/** name under which the service is registered */
final String id = "Log";
/** logs a message */
void log (Serializable msg) throws java.rmi.RemoteException;
}
{}
logd is derived from Remote
-- this signifies that RMI is used.
The information msg is not Remote but Serializable
, i.e., the object will be shipped.
/** A class with a trivial main program to log a message */
public class client {
/** logs to the host named as first argument the rest of the command line */
public static void main (String args []) {
String host = args != null && args.length > 0 ? args[0] : "localhost";
String id = "//"+ host +"/"+ logd.id;
String msg = new java.util.Date().toString();
if (args != null)
for (int n = 1; n < args.length; ++ n)
msg = msg +" "+ args[n];
try {
System.setSecurityManager(new RMISecurityManager());
((logd)Naming.lookup(id)).log(msg);
} catch (Exception e) {
System.out.println(e); System.exit(1);
}
}
}
{}
String
implements Serializable
, i.e., it can be shipped (the class is final).
/** A class to log messages */
public class server extends UnicastRemoteObject implements logd {
/** initializes the log stream */
public server () throws RemoteException, IOException {
System.out.println("log started at "+ new java.util.Date());
Naming.rebind(logd.id, this);
}
/** logs a message */
public synchronized void log (Serializable msg) throws RemoteException {
System.out.println(msg); System.out.flush();
}
/** starts a server to log messages */
public static void main (String args []) {
try {
System.setSecurityManager(new RMISecurityManager());
new server();
} catch (Exception e) {
System.out.println(e); System.exit(1);
}
}
}
{}
Because an output should not be split, the output method is synchronized.
Again, several processes are run in parallel, where, of course, rmiregistry
is started only once:
> set CLASSPATH=..
> rmiregistry
> cd code\programs\rmi\server\logd
> java logd.server
log started at Mon Dec 08 23:53:13 GMT+00:00 1997
Mon Dec 08 23:54:50 GMT+00:00 1997
Mon Dec 08 21:02:07 GMT+03:30 1997 hello, world
> cd code\programs\rmi\client\logd
> java logd.client
$ cd code/programs/rmi/client/logd
$ java logd.client perky hello, world
logd2 implements a system compatible to logd
, which will identify the originator of a message.
logd2 illustrates that classes can be specialized later.
Interface
{programs/rmi/src/logd2/logd2.java}
package logd2;
import java.io.Serializable;
/** An interface describing an extended distributed log service */
public interface logd2 extends logd.logd {
/** retrieves an identification object */
ident get () throws java.rmi.RemoteException;
/** logs a message with the originator */
void log (Serializable msg, ident me) throws java.rmi.RemoteException;
}
{programs/rmi/src/logd2/ident.java}
package logd2;
import java.io.Serializable;
/** An interface describing an identification collector */
public interface ident extends Serializable {
/** sets client identification into the collector */
ident init ();
}
{}
ident cannot be an inner class -- apparently, rmic does not support that at present (1.1.5).
get() retrieves an ident object from the server. It must be initialized at the client using init() and sent back using log().
/** A class with a trivial main program to log a message */
public class client {
/** logs to the host named as first argument the rest of the command line */
public static void main (String args []) {
String host = args != null && args.length > 0 ? args[0] : "localhost";
String id = "//"+ host +"/"+ logd.logd.id;
String msg = new java.util.Date().toString();
if (args != null)
for (int n = 1; n < args.length; ++ n)
msg = msg +" "+ args[n];
try {
System.setSecurityManager(new RMISecurityManager());
logd2 server = (logd2)Naming.lookup(id);
server.log(msg, server.get().init());
} catch (Exception e) {
System.out.println(e); System.exit(1);
}
}
}
{}
/** A class to log messages, with or without originator */
public class server extends logd.server implements logd2 {
/** required because of the exceptions */
public server () throws RemoteException, IOException {
}
/** logs a message with originator */
public synchronized void log (Serializable msg, ident me)
throws RemoteException {
log(me +" "+ msg);
}
/** send collector */
public ident get () throws RemoteException {
return new who();
}
/** starts a server to log messages */
public static void main (String args []) {
try {
System.setSecurityManager(new RMISecurityManager());
new server();
} catch (Exception e) {
System.out.println(e); System.exit(1);
}
}
}
{programs/rmi/src/logd2/who.java}
package logd2;
import java.net.*;
/** A class to collect information about a message originator */
public class who implements ident {
/** information: the host's name */
protected String me = "unknown host";
/** executed on the client, determines client's hostname */
public ident init () {
try {
me = InetAddress.getLocalHost().getHostName();
} catch (Exception e) { }
return this;
}
/** executed on the server, returns client's hostname */
public String toString () {
return me;
}
}
{}
The client needs a copy of who.class, otherwise it cannot receive a who object. The class could probably be obtained with an RMIClassLoader
.
Once again, several processes are run in parallel. rmiregistry
should be restarted to avoid a dubious message relating to incompatible stub classes:
> set CLASSPATH=..
> rmiregistry
> cd code\programs\rmi\server\logd2
> java logd2.server
log started at Tue Dec 09 01:44:02 GMT+00:00 1997
perky Tue Dec 09 01:44:09 GMT+00:00 1997 hello, world
Tue Dec 09 01:44:22 GMT+00:00 1997 old style
cindy Mon Dec 08 23:03:59 GMT+03:30 1997 hi, there
> cd code\programs\rmi\client\logd2
> java logd2.client perky hello, world
> java logd.client perky old style
$ cd code/programs/rmi/client/logd2
$ java logd2.client perky hi, there
The new server can receive the old messages.
The last entry in the log shows the other hostname cindy. This demonstrates that init() is really executed at the client for the object transferred to the client.