Schildt: how to add two numbers using RMI

This very simple example is taken from Chapter 24 of "Java 2: The Complete Reference" by P.Naughton and H.Schildt.

The example is overly simplified but it still illustrates the basic steps in creating an RMI distributed program.

This example provides step-by-step directions for building a client/server application by using RMI. The server receives a request from a client, processes it, and returns a result. In this example, the request specifies two numbers, the server adds these together and returns the sum. That is it!

(Of course, this program is intended only to illustrate the basic RMI mechanism.)

The next subsections provide main steps in writing an RMI program

Define an interface that declares remote methods.

The first file AddServerIntf.java defines the remote interface: it includes one method that accepts two double arguemnts and returns their sum. All remote interfaces must extend the interface Remote, that defines no methods: its purpose is simply to indicate that an interface uses remote methods.

All remote methods should throw a RemoteException


import java.rmi.*;

public interface AddServerIntf extends Remote {
  double add(double d1, double d2) throws RemoteException;
}

Implement the remote interface and the server

The second source file AddServerImpl.java implements the remote interface:

import java.rmi.*;
import java.rmi.server.*;

public class AddServerImpl extends UnicastRemoteObject
  implements AddServerIntf {

  public AddServerImpl() throws RemoteException {
  }
  public double add(double d1, double d2) throws RemoteException {
    return d1 + d2;
  }
}

All remote objects must extend UnicastRemoteObject which provide the functionaly that is needed to make objects available from remote machines.

The third source file AddServer.java contains the main program for the server machine. Its primary function is to update the RMI registry on that machine. This is done by using the rebind() method of the Naming class (it is in java.rmi API). That method associates a name with an object reference.

import java.net.*;
import java.rmi.*;

public class AddServer {
  public static void main(String args[]) {
    try {
      AddServerImpl addServerImpl = new AddServerImpl();
      Naming.rebind("AddServer", addServerImpl);
    }
    catch(Exception e) {
      System.out.println("Exception: " + e);
    }
  }
}

Develop a client (an application or an applet) that uses the remote interface

The fourth source file AddClient.java implements the client side of this distributed application. This program requires 3 command line arguments: the IP address or name of the remote server, and two numbers that are to be summed.

The application forms an URL string using the rmi protocol, the first command line argument and the name "AddServer" that is used by naming registry of the server. The the program calls the method lookup() of the Naming class to find a reference to a remote object. All remote method invocations can then be directed to this object.

The client program illustrates the remote call by using the method add(d1,d2) that will be invoked on the remote server machine from the local client machine where the client runs.

import java.rmi.*;
public class AddClient {
  public static void main(String args[]) {
    try {
      String addServerURL = "rmi://" + args[0] + "/AddServer";
      AddServerIntf addServerIntf =
                    (AddServerIntf)Naming.lookup(addServerURL);
      System.out.println("The first number is: " + args[1]);
      double d1 = Double.valueOf(args[1]).doubleValue();
      System.out.println("The second number is: " + args[2]);

      double d2 = Double.valueOf(args[2]).doubleValue();
      System.out.println("The sum is: " + addServerIntf.add(d1, d2));
    }
    catch(Exception e) {
      System.out.println("Exception: " + e);
    }
  }
}

Generate stubs and skeletons

Compile all aforementioned Java source files and keep all generated .class files in the same directory.

Before you can use the client and the server, you must generate the necessary stub. You may also need to generate a skeleton, if a server runs on a machine that does not have Java 2, but has an earlier version of JVM.

In RMI, a stub is a java class that either resides on the client machine, or downloaded to the client machine dynamically from elsewhere (e.g., from the web server). The function of stub is to present the same interfaces as the remote server. Remote method calls initiated by the client are actually directed to the stub. The stub works with the other parts of the RMI system to formulate a request that is sent to the remote machine.

A remote method may accept arguments that are simple types or objects. In the latter case, the object may have references to other objects. All of this information must be sent to the remote machine. That is, an object passed as an argument to a remote method call must be serialized and sent to the remote machine. If a response must be returned to the client, the process works in reverse. Note that the serialization and deserialization facilities are also used if objects are returned to a client.

Skaletons are not required by Java 2, but required for the Java 1.1 RMI model. For this reason, skeletons are still necessary for compatibility between Java 1.1 and Java2. A skeleton is a Java class that resides on the server machine. It works with other parts of the 1.1 RMI system to receive requests, perform deserialization, and invoke the appropriate code on the server. It is used in this example to illustrate the compatibility.

To generate stubs and skeletons, you use a tool called the RMI compiler:

rmic AddServerImpl

This command generates two new files: AddServerImpl_Skel.class (skeleton) and AddServerImpl_Stub.class (stub). If you do not need the skeleton, run rmic -v1.2 to turn on Java 2 option. (read details in the rmic manual page).
 

The easiest way to try a distributed architecture is to copy all required files manually to correct machines/directories:

Of course, RMI has techniques for dynamic class loading, they will be illustrated below in a more elaborated version of this example. For now, we would like to test the client/server without going into other details.

Start the RMI registry

Go to the directory on the server machine where you keep files mentioned above. Check that the CLASSPATH environment variable includes the directory in which your files are located (for example, CLASSPATH has . entry - the current directory). Then:

Start the server

In the same directory:
java AddServer

Run the client

Now go to your local machine.

Suppose that you started the server on jupiter.scs.ryerson.ca Then, you can start the client on your local machine as follows:


java AddClient jupiter.scs.ryerson.ca  567  999
The first number is: 567.0
The second number is: 999.0
The sum is: 1566.0

If you do not have an Internet connection, you can run all programs of this example on your local machine (both server and client). In this case, you can run
java AddClient   127.0.0.1   567   999
to use the "loop back" address (127.0.0.1) for the local machine. This will allow you to test the entire RMI mechanism without actually having to install the server on a remote computer.
 



 

A revised version of the example

All the client really needs to know about the remote object is its remote interface. Everything else it needs - for instance, the stub classes - can be loaded from a web server (though not an RMI server) at runtime. This may be necessary if, for example, several companies developed different remote servers, and you are devoloping a client application that needs to call remote methods of those servers. Your application will need to load the stub files from the servers dynamically.

This ability to load classes from the network is one of the unique features of Java. Of course, as with any time that classes are to be loaded from a potentially untrusted host, they must be checked by a   SecurityManager.

In addition, if you're running the client or server with Java 2, then you'll need to specify a security policy file, to prevent SecurityExceptions being thrown. This policy file will allow your application to bind to a local port (if a service), and to connect to remote hosts (if a client). The following changes should be made when running the client/server :


    java -Djava.security.policy=rmi.policy  yourserver

You'll also need to create a policy file "rmi.policy" (if one does not already exist). Here's a sample policy file that will allow you to accept conections from ports higher than 1024, but connect to all ports as a client (the real file is provided later).
grant { 
   permission java.net.SocketPermission "*:1024-65535",
          "connect,accept,resolve";
   permission java.net.SocketPermission "*:1-1023", 
           "connect,resolve";
};

In the previous version of the example, the client did not attempt to load stub file from the server; both the server and client also lack any security managers that prevent from loading malicious code. The next modification of the previous example provides both security managers and the ability to load files at runtime from the remote server.

Define an interface that declares remote methods.

The first file AddServerIntf.java that defines the remote interface remains the same.

import java.rmi.*;

public interface AddServerIntf extends Remote {
  double add(double d1, double d2) throws RemoteException;
}

Implement the remote interface and the server

The second source file AddServerImpl.java (it implements the remote interface) also remains the same, with one minor variation: it calls the super-class constructor explicitly.
import java.rmi.*;
import java.rmi.server.*;

public class AddServerImpl extends UnicastRemoteObject
  implements AddServerIntf {

  public AddServerImpl() throws RemoteException {
  super();
  }

  public double add(double d1, double d2) throws RemoteException {
    return d1 + d2;
  }
}

The revised version of the third source file AddServer.java includes the security manager, assumes that you will run the server on jupiter using the port 56789, and uses a slightly modified name "MyAddServer" for registration purposes.

import java.net.*;
import java.rmi.*;
public class AddServer {
  public static void main(String args[]) {

    // Create and install a security manager 

    if (System.getSecurityManager() == null) { 
	   System.setSecurityManager(new RMISecurityManager()); 
    } 
    try {
      AddServerImpl addServerImpl = new AddServerImpl();

	// You want to run your AddServer on jupiter using the port 56789
	// and you want to use rmi to connect to jupiter from your local machine
	// Note that to accomplish this you have to start on jupiter
	// rmiregistry 56789 &
	// in the directory that contains NO classes related to this server !!!

      Naming.rebind("rmi://jupiter.scs.ryerson.ca:56789/MyAddServer", addServerImpl);
    }
    catch (Exception e) { 
	    System.out.println("Exception: " + e.getMessage()); 
	    e.printStackTrace(); 
    }
  }
}

Copy files AddServerIntf.java, AddServerImpl.java, and AddServer.java to your directory on jupiter and compile them as usual using javac.


 

Develop a client (an application or an applet) that uses the remote interface

The revised version of the fourth source file AddClient.java has a few new features.

import java.rmi.*;

   // USAGE: java AddClient   firstNum secondNum  

public class AddClient {
  public static void main(String args[]) {

    // The client will try to download code, in particular, it will
    // be downloading stub class from the server.
    // Any time code is downloaded by RMI, a security manager must be present.
 
    if (System.getSecurityManager() == null) { 
	   System.setSecurityManager(new RMISecurityManager()); 
    } 

    try {
      String addServerURL = "rmi://" + args[0] + ":" + args[1] + "/MyAddServer";

     System.out.println("I will try to invoke the remote method from  " + addServerURL);

      AddServerIntf remoteObj =
                    (AddServerIntf) Naming.lookup(addServerURL);

      System.out.println("The first number is: " + args[2]);

      double d1 = Double.valueOf(args[2]).doubleValue();

      System.out.println("The second number is: " + args[3]);

      double d2 = Double.valueOf(args[3]).doubleValue();

      // Now we invoke from a local machine the remote method "add"

      System.out.println("The sum is: " + remoteObj.add(d1, d2));
    }
    catch(Exception e) {
      System.out.println("Exception: " + e);
    }
  }
}

Keep this file on your local machine together with the remote interface and the rmi.policy file that controls access to your local machine:

grant {
  // The simplest (but uncarefull) policy is allow everything:
  //	permission java.security.AllPermission;
  // More secure policy is the following.
  //
  // jupiter.scs.ryerson.ca   This is the rmihost - RMI registry and the server
  // www.scs.ryerson.ca       This is webhost - HTTP server for stub classes
  permission java.net.SocketPermission 
    "jupiter.scs.ryerson.ca:1024-65535", "connect,accept";
  permission java.net.SocketPermission 
    "www.scs.ryerson.ca:80", "connect";

};
You also have to copy this policy file to your directory on jupiter that contains all server-related files.

Generate stubs and skeletons

Next, go to the server (jupiter), and change into the directory that contains AddServerIntf.class (interface), AddServerImpl.class (its implementation), and AddServer.class (server itself) and rmi.policy file. In that directory, run rmic compiler:

rmic AddServerImpl

This command generates two new files: AddServerImpl_Skel.class (skeleton) and AddServerImpl_Stub.class (stub).

Start the RMI registry

Before you proceed, copy stub and interface classes to a directory, where the web server can access them, e.g. to the world-accessible sub-directory JavaClasses of your public_html directory:

ls -t -l ~mes/public_html/JavaClasses/
total 24
-rw-r--r--   1 mes      mes          205     AddServerIntf.class
-rw-r--r--   1 mes      mes         1612     AddServerImpl_Skel.class
-rw-r--r--   1 mes      mes         3171     AddServerImpl_Stub.class

Check that all these files are accessible, e.g., try to download them using Netscape or IE: if you succeeded, then they are accessible.

On jupiter in the directory that does NOT contain any server related classes and assuming that those classes are NOT on you CLASSPATH, start rmiregistry:

rmiregistry 56789 &

For example, create the temporary directory tmpTEST, go to that directory and start there rmiregistry naming system. By default, rmiregistry naming system loads stub and skeleton files from directories mentioned in your CLASSPATH. But because you want to load them dynamically from your web directory JavaClasses, you want to hide these files from rmiregistry: this way you force to load required files from the codebase given below as a command-line argument.

Start the server

In the directory that contains all server related classes:
-rw-r--r--   1 mes      mes         1075 	 AddServer.class
-rw-r--r--   1 mes      mes          363 	 AddServerImpl.class
-rw-r--r--   1 mes      mes         1612 	 AddServerImpl_Skel.class
-rw-r--r--   1 mes      mes         3171 	 AddServerImpl_Stub.class
-rw-r--r--   1 mes      mes          205 	 AddServerIntf.class
-rw-r--r--   1 mes      mes          511 	 rmi.policy

we can run the server:

java -Djava.security.policy=rmi.policy 
  -Djava.rmi.server.codebase=http://www.scs.ryerson.ca/~mes/JavaClasses/ AddServer &

Note that the URL given to "codebase" ends with / and use your own login name, of course.

Run the client

Now, go to the directory of your local computer that contains only 3 files: For example, you can create a new directory and copy there 3 files mentioned in this section.

This time, you would like to load the stub class dynamically from the server. Assume that the server side was developed by a different company, you are responsible only for the client application and you do not have access to the server-related files when you start your client. All you know is that the RMI server will be running on jupiter and you can connect to the registry at the port 56789 if you need to invoke remote methods on the server.

Finally, you can run the client application on your local machine:


java -Djava.security.policy=rmi.policy AddClient jupiter.scs.ryerson.ca 56789 456 544
I will try to invoke the remote method from  rmi://jupiter.scs.ryerson.ca:56789/MyAddServer
The first number is: 456
The second number is: 544
The sum is: 1000.0


Other RMI Examples