Sun calls the object parameter-passing mechanism object serialization. An RMI request is a request to invoke the method of a remote object. The request has the same yntax as a request to invoke an object method in the same (local) computer. In general, RMI is designed to preserve the object model and its advantages across a network.
RMI provides the mechanism by which the server and the client communicate and pass information back and forth. This mechanism includes:
Locating remote objects: An application can register its remote objects with RMI's simple naming facility, the rmiregistry, or the application can pass and return remote object references as part of its normal operation.RMI distributed applications use the registry to obtain a reference to a remote object. The server calls the registry to associate (or bind) a name with a remote object. The client looks up the remote object by its name in the server's registry and then invokes a remote method on the server. This procedure is demonstrated in Figure 1.
Communicating with remote objects: Details of communication between remote objects are handled by RMI. Programmers don’t need to worry about these details.
Loading class bytecodes for remote objects passed around: Because RMI allows a caller to pass objects to remote objects, RMI provides the necessary mechanisms for loading an object's code, as well as for transmitting its data.
Figure 1: Illustration of RMI communication
An object becomes remote by implementing a remote interface, which has the following characteristics.
A remote interface extends the interface java.rmi.Remote. Each method of the interface declares java.rmi.RemoteException in its throws clause, in addition to any application-specific exceptions.
The methods that are defined in remote interfaces are called remote
methods.
A stub for a remote object implements the same set of remote interfaces
that the remote object implements. This allows a stub to be cast to any
of the interfaces that the remote object implements. However, this also
means that only those methods defined in a remote interface are available
to be called from the receiving virtual machine.
An RMI example is introduced below. It takes the input Fahrenheit degree then converts into Celsius degree and vice versa. The server is dedicated to converting calculations and then transferring the result to the client. The responsibility of the client side is to accept the choice input by users and then to call remote objects defined on the server side.
When RMI is used to develop a distributed application, programmers can
follow the these general steps.
1.Design and implement
the components of the RMI application.
2.Compile sources
and generate stubs.
3.Make classes network
accessible.
4.Start the application.
(A) Define a Remote Interface
Remote method invocations can fail in very different ways from local
method invocations, due to network related communication problems and server
problems. To indicate that it is a remote object, an object implements
a remote interface, which has the following characteristics:
The remote interface must be public. Otherwise, a client will get an error when attempting to load a remote object that implements the remote interface. The remote interface extends the interface java.rmi.Remote. Each method must declare java.rmi.RemoteException in its throws clause. A remote object passed as an argument or return value (either directly or embedded within a local object) must be declared as the remote interface, not the implementation class.
Here is the interface definition for Temperature Converter. The
interface contains two methods, f2c() and c2f(), which return a float number
to the caller respectively. f2c() converts the input Fahrenheit degree
number into Celsius degree number while c2f() converts the input Celsius
degree number into Fahrenheit degree number.
Sample Code of ConverterInterface.java
import java.rmi.*;
public interface ConverterInterface extends java.rmi.Remote
{
// Convert Fahrenheit degree to Celsius degree
public float f2c( float f ) throws RemoteException;
// Convert Celsius degree to Fahrenheit degree
public float c2f( float c ) throws RemoteException;
}
(B) Write an Implementation Class
To write a remote object, programmers need to write a class that implements
one or more remote interfaces. The implementation class needs to:
1.Implement the remote interface(s),
2.Define the constructor for the remote object, and
3.Provide implementations for the methods that can be invoked remotely.
a.Implement Remote Interfaces
The implementation class for the Temperature Converter example is ConverterImpl
, which is saved in ConverterImpl.java. An implementation class specifies
the remote
interface(s) it is implementing. The implementation class for the Temperature
Converter is declared as public class ConverterImpl extends UnicastRemoteObject
implements ConverterInterface
This declaration states that the class implements the ConverterInterface remote interface (and therefore defines a remote object) and extends the class java.rmi.server.UnicastRemoteObject.
Extending UnicastRemoteObject indicates that the ConverterImpl class
is used to create a single (non-replicated) remote object that uses RMI's
default socket- based
transport for communication. If users choose to extend a remote object
from a non-remote class, they need to explicitly export the remote object
by calling the method
UnicastRemoteObject.exportObject.
b.Define the Constructor for the Remote Class
The constructor for a remote class is not different from the constructor for a non-remote class: it initializes the variables of each newly created instance of the class.
Here is the constructor for the ConverterImpl class, which does nothing
except call the constructor of its parent class, UnicastRemoteObject. Although
the call to the super
no-argument constructor occurs by default if omitted, it is included
in this example to make clear the fact that Java constructs the superclass
before the class.
public ConverterImpl() throws RemoteException
{
super();
}
Note the following:
The super method call invokes the no-argument constructor of java.rmi.server.UnicastRemoteObject, which "exports" the remote object by listening for incoming calls to the remote object on an anonymous port. The constructor must throw java.rmi.RemoteException, because RMI's attempt to export a remote object during construction might fail if communication resources are not available.
c.Provide an Implementation for Each Remote Method
The implementation class for a remote object contains the code that implements each of the remote methods specified in the remote interface. For example, here is the implementation for the two methods, f2c() and c2f(), which each return a float number to the caller. f2c() converts the input Fahrenheit degree number into Celsius degree number while c2f() converts the input Celsius degree number into Fahrenheit degree number.
// Converter Fahrenheit degree to Celsius degree
public float f2c( float f ) throws RemoteException
{
return (float)( (f-32.0)/180.0*100.0 );
}
// Convert Celsius degree to Fahrenheit degree
public float c2f( float c ) throws RemoteException
{
return (float)( (180.0/100.0)*c+32.0 );
}
Arguments to, or return values from, remote methods can be of any Java type, including objects, as long as those objects implement the interface java.io.Serializable. Most of the core Java classes in java.lang and java.util implement the Serializable interface.
Remote objects have different characteristics than do local objects. A local object is created from a class that is able to define methods not specified in the remote interface. However, those methods can only be invoked within the virtual machine running the service and cannot be invoked remotely.
The ways to pass local objects and remote objects are different.
Local objects are passed by copy, and only the non-static and non-transient fields are copied by default. Remote objects are passed by reference. A reference to a remote object is actually a reference to a stub, which is a client-side proxy for the remote object. Stubs will be fully described later.
Sample Code of ConverterImpl.java
import java.rmi.*;
import java.rmi.server.*;
public class ConverterImpl extends UnicastRemoteObject implements
ConverterInterface
{
public ConverterImpl() throws RemoteException
{
super();
}
// Convertor Fahrenheit degree to Celsius degree
public float f2c( float f ) throws RemoteException
{
return (float)( (f-32.0)/180.0*100.0 );
}
// Convert Celsius degree to Fahrenheit degree
public float c2f( float c ) throws RemoteException
{
return (float)( (180.0/100.0)*c+32.0 );
}
}
(C) Implement the Server's Main Method
The main function of a server here is to provide the initialization and housekeeping to prepare the server for accepting calls from clients. An RMI server should include the following three features.
1.Create and install a security manager.a.Create and Install a Security Manager
2.Create one or more instances of a remote object.
3.Register at least one of the remote objects with the RMI remote object registry, for bootstrapping purposes.
The main method of the service first creates and installs a security manager: either the RMISecurityManager or one that users have defined themselves. For example:
if( System.getSecurityManager() == null )
System.setSecurityManager( new RMISecurityManager() );
The statement uses System.getSecurityManager() to check whether a security manager is set or not. If the security manager is not set, then setSecurityManager() is applied to set one. A security manager needs to be run so that it can guarantee that the classes loaded do not perform "sensitive" operations. If no security manager is specified, no class loading for RMI classes, local or otherwise, is allowed.
b.Create One or More Instances of a Remote Object
The main method of the service creates one or more instances of the remote object that provides the service. For example:
ConverterImpl svr = new ConverterImpl();
The constructor exports the remote object, which means that, once created, the remote object is ready to begin listening for incoming calls.
c.Register a Remote Object
For a client to be able to invoke a method on a remote object, that client must first obtain a reference to the remote object. Most of the time, the reference will be obtained as a parameter to, or a return value from, another remote method call.
For bootstrapping, the RMI system also provides a URL-based registry that allows programmers to bind a URL of the form //localhost/objectname to the remote object, here objectname is a simple string name. Once a remote object is registered on the server, clients can look up the object by name, obtain a remote object reference, and then remotely invoke methods on the object.
For example, the following code binds the URL of the remote object named ConverterInterface to a reference for the remote object:
Naming.rebind( "//localhost/ConverterInterface", svr );
Note the following about the arguments to the call:
The host defaults to the current host if omitted from the URL, and no
protocol needs to be specified in the URL.
The RMI runtime substitutes a reference to the remote object's stub
for the actual remote object reference specified by the svr argument. Remote
implementation objects like instances of ConverterImpl never leave the
virtual machine where they are created, so when a client performs a lookup
in a server's remote object registry, a reference to the stub is returned.
Optionally, a port number can be supplied in the URL: for example
//localhost:1234/ConverterInterface. The port defaults to 1099. It
is necessary to specify the port number only if a server creates a registry
on a port other than the default 1099.
For security reasons, an application can bind or unbind only in the
registry running on the same host. This prevents a client from removing
or overwriting any of the entries in a server's remote registry. A lookup,
however, can be done from any host.
Sample Code of ConverterServer.java
import java.rmi.*;
import java.rmi.server.*;
public class ConverterServer
{
public static void main( String args[] ) throws Exception
{
// Assign a security manager, in the event that
//dynamic classes are loaded
if( System.getSecurityManager() == null )
System.setSecurityManager
( new RMISecurityManager() );
try{
// Create an instance of the ConverterServer
ConverterImpl svr = new ConverterImpl();
// then bind it with the rmi registry
Naming.rebind ( "ConverterInterface", svr );
System.out.println ("Service bound....");
}
catch( Exception e )
{ System.out.println( "Server Failed: " + e ); }
}
}
(D) Write a Client Application that Uses the Remote Service
Writing clients is the easy part - all a client has to do is to call the registry to obtain a reference to the remote object and call its methods. All the underlying network communication is hidden from view to make RMI clients simple. An RMI client must first assign a security manager, and then obtain a reference to the service. Note that the client receives an instance of the interface defined earlier, and not the actual implementation. Some behind-the-scenes work is going on, but this is completely transparent to the client.
// Assign secuirty manager
if( System.getSecurityManager() == null )
System.setSecurityManager( new RMISecurityManager() );
// Call registry for ConverterInterface
ConverterInterface service =
(ConverterInterface) Naming.lookup
("rmi://" + args[0] + "/ConverterInterface");
To identify a service, programmers specify an RMI URL. The URL contains the hostname on which the service is located, and the logical name of the service. This returns a ConverterInterface instance, which can then be used just like a local object reference. The methods can be called just as before. The client of the distributed Temperature Converter example remotely invokes the ConverterServer's f2c or c2f methods in order to get the corresponding converted values, which are displayed when a client runs.
// Call remote method
System.out.println( "The equivalent Celsius degree: " +
service.f2c( tempValue ) );
// Call remote method
System.out.println( "The equivalent Celsius degree: " +
service.f2c( tempValue ) );
Writing RMI clients is the easiest part of building distributed services. In fact, there's more code for the user interface menu in the client than there is for the RMI components! To keep things simple, there's no data validation, so users must be careful when entering numbers.
Sample Code for ConverterClient.java
import
java.rmi.*;
import
java.rmi.Naming; // for Naming.lookup()
import
java.io.*;
public
class ConverterClient
{
public static void main( String args[] ) throws Exception
{
// Check for hostname argument
if( args.length != 1 )
{
System.out.println
( "Syntax: ConverterClient <hostname>" );
System.exit( 1 );
}
// Assign secuirty manager
if( System.getSecurityManager() == null )
System.setSecurityManager( new RMISecurityManager() );
// Call registry for ConverterInterface
// To identify a service, we specify an RMI URL. The URL
// contains the hostname
// on which the service is located, and the logical name of
// the service. This
// returns a Converterinterface instance, which can then be
// used just like a
// local object reference.
ConverterInterface service =
(ConverterInterface) Naming.lookup
( "rmi://" + args[0] + "/ConverterInterface" );
DataInputStream din = new DataInputStream( System.in );
for( ;; ){
System.out.println();
System.out.println
( "1 - Convert Fahrenheit degree to celsius degree" );
System.out.println
( "2 - Convert celsius degree to Fahrenheit degree" );
System.out.println
( "3 - Exit" ); System.out.println();
System.out.print( "Choice: " );
String line = din.readLine();
Integer choice = new Integer( line );
int value = choice.intValue();
float tempValue;
switch( value )
{
case 1:
System.out.print( "Farenheit degree: " );
line = din.readLine(); System.out.println();
tempValue = (Float.valueOf(line)).floatValue();
// Call remote method
System.out.println
( "The equivalent Celsius degree: "
+ service.f2c( tempValue ) );
break;
case 2:
System.out.print( "Celsius degree: " );
line = din.readLine();
tempValue = (Float.valueOf(line)).floatValue();
// Call remote method
System.out.println
( "The equivalent Fahrenheit degree: "
+ service.c2f( tempValue ) );
break;
case 3:
System.exit(0);
default :
System.out.println ("Invalid option");
break;
}
}
}
}
ConverterInterface.java, which contains the source code for the remote interface. ConverterImpl.java, which is the source code for the ConverterImpl remote object implementation. ConverterServer.java, which is the server for the Temperature Converter example. ConverterClient.java, which is the source code for the client.
In this section, the .java source files are compiled to create .class files, then the rmic compiler is run to create stubs and skeletons. A stub is a client- side proxy for a remote object that forwards RMI calls to the server-side skeleton, which in turn forwards the call to the actual remote object implementation.
When the javac and rmic compilers are used, programmers must specify where the resulting class files should reside. ConverterInterface.class, ConverterImpl.class and ConverterServer.class have to stay on the server side and ConverterClient.class has to stay on the client side.
(A) Compile the Java Source Files
To compile the Java source files, users can execute the following command at the DOS prompt. javac *.java
(B) Generate Stubs and Skeletons
To create stub and skeleton files, users must run the rmic compiler on the names of compiled class files that contain remote object implementations. rmic takes one or more class names as input and produces output class files of the form <Implement filename>_Skel.class and <Implement filename>_Stub.class.
For example, to create the stub and skeleton for the ConverterImpl remote object implementation, run rmic like this:
rmic ConverterImpl
The preceding command creates two files, ConverterImpl_Stub.class and ConverterImpl_Skel.class.
(C) Make classes network accessible
To make classes network accessible, users have to start the remote object registry. The RMI registry is a simple server-side bootstrap name server that allows remote clients to get a reference to a remote object. It is typically used only to locate the first remote object an application needs to talk to. That object in turn will provide application specific support for finding other objects.
To start the registry on the server, users need to execute the rmiregistry command. This command produces no output and is typically run in the background. In Windows 95, 98 or NT environments, rmiregistry should be run in the following way.
start rmiregistry
Users can use javaw if start is not available.
The registry by default runs on port 1099. To start the registry on a different port, users must specify the port number in the command. For example, to start the registry on port 2001 on Windows NT, rmiregistry should be executed as
start rmiregistry 2001
If the registry is running on a port other than the default, users need to specify the port number in the URL-based methods of the java.rmi.Naming class when making calls to the registry. For example, if the registry is running on port 2001 in the Temperature Converter example, the call required to bind the URL of the ConverterServer to the remote object reference is:
Naming.rebind("//localhost:2001/ConverterInterface", svr);
Users must stop and restart the registry when they modify a remote interface
or use modified/additional remote interfaces in a remote object implementation.
Otherwise, the class bound in the registry will not match the modified
class.
(A) Start the Server
The following command shows how to start the Temperature Converter server, by executing the following command.
start java ConverterServer
(B) Run the Client
Once the registry and server are running, the client can be run. Users must execute the following command at the DOS prompt.
Start java ConverterClient localhost
Figure 10: Illustration of the result of executing the Temperature Converter
example