Chapter 40
Remote Method Invocation

by Joe Weber

One of the features that really pushes the Client-Server model in many systems is known as Remote Procedure Calls (RPC). With JDK 1.1, Java has a similar feature called Remote Method Invocation which allows you to create Objects which actually exist and process on machines other than the client computer. This chapter covers this exciting and extremely powerful advance.

Remote Method Invocation

First, it's necessary to define what Remote Method Invocation is. With object serialization, you learned that you could take an entire object and pass it along a stream. Remote Method Invocation is a sister to object serialization that allows you to not only pass an Object along a stream, but to actually allow that Object to exist on a separate computer and invoke methods on those other systems as well.

In other words, RMI allows you to create Java objects whose methods can be invoked by the virtual machine on a different computer. Although these objects live and process on a different computer, they are used on the remote machine just like any local object.

Creating a Remote Object

Any object that can be called remotely must implement the Remote interface. However, the Remote interface itself does not have any methods, so obviously just implementing the Remote interface isn't going to buy you a whole lot. In fact, the first step to creating a Remote Object is to create an interface for the object. The interface will contain all of the methods that can be called remotely, and that interface must extend the Remote interface. The new Remote object must then implement your new interface. Since the new interface extends the Remote interface, implementing the new interface fulfills the requirement for the remote object to implement the Remote interface. Each of these implementing objects are then referred to as remote objects. So, to create and implement Remote Object there are five simple steps:

  1. Define an interface that extends the Remote interface. Each method of this new interface must declare that it will throw a RemoteExecption.

See "Extending Other Interfaces," Chapter 12

  1. Define a class that implements the interface. Because the new interface extends Remote, this fulfills the requirements for making the new class a remote object. The class must provide a means to marshal references to the instances of the class. Currently, the only class available to do this is the UnicastRemoteObject.
  2. Generate the stubs and skeletons that are needed for the remote implementations by using the rmic program.
  3. Create a client program that will make RMI calls to the server.
  4. Start the registry and run your remote server and client.


NOTE: When parameters are required by an RMI method, the objects are passed using object serialization, as discussed in Chapter 39.


A Sample RMI Application

To understand RMI, take a look at a complete example. As is so frequently the case, the example used is a fairly simple one, which simply creates a string and returns it.

Creating a Remote Interface

The first step to creating an RMI application is to create an interface, which extends the Remote interface. Each of the methods in this interface will be able to be called remotely. If you're already thinking ahead, you may have realized that the use of an interface in this system is an amazingly elegant use of object-oriented programming. With an interface, the system that calls the Remote Object works with the interface just like any other class, but the compiler doesn't need to know anything about the code within the body of the methods. Just as when you create any interface, you want to make sure that the prototype for each of the methods matches exactly with the method headers you will use in the final class. Listing 40.1 shows a simple remote interface for this example.

Listing 40.1RemoteInterface--A Sample Interface that Extends Remote

public interface RemoteInterface extends java.rmi.Remote {
String message (String message) throws java.rmi.RemoteException;
}

Here, you have defined an interface with a single method. Remember that the Remote interface does not actually have any methods of its own, so message is the only method that needs to be defined by any class that implements the RemoteInterface interface.


NOTE: An interface that will be utilized remotely can use any class as a parameter or a return type, so long as that type implements the Serializable.


Creating an Implementing Class

The second step is to define a class that implements your new RemoteInterface interface. This class is defined in Listing 40.2. In this example you will have message simply return a String which will contain information from both the passed in string and one which is local to the Object (name). Doing this should help to prove that you are, in fact, doing the processing on the remote computer. To further emphasize the point, you will do a println, which you will see later is displayed on the remote computer, not the client one.

Listing 40.2RemoteObject--A Sample Remote Object that Receives and Sends a String

import java.rmi.Naming;
import java.rmi.server.UnicastRemoteObject;
import java.rmi.RemoteException;
import java.rmi.RMISecurityManager;

public class RemoteObject extends UnicastRemoteObject implements RemoteInterface{
      String name;
      public RemoteObject(String name) throws RemoteException{
            super();
            this.name = name;
      }

      public String message(String message) throws RemoteException{
           String returnString = "My Name is:"+name+",thanks for your
      message:"+message;
            System.out.println("Returning:"+returnString);
            return "My Name is:"+name+",thanks for your message:"+message;
      }

      public static void main (String args[]){
            System.setSecurityManager (new RMISecurityManager());
            try{
                  String myName = "ServerTest";
                  RemoteObject theServer = new RemoteObject (myName);
                  Naming.rebind(myName,theServer);
                  System.out.println("Ready to continue");
            } catch (Exception e){
                  System.out.println("An Exception occured while creating  server");
            }
      }
}

Several key things need to be noticed about the RemoteObject class. First, the RemoteObject extends the UnicastRemoteObject. For the scope of this chapter, you can think of the UnicastRemoteObject as the java.applet.Applet for RMI servers. You can create your own RemoteObject classes, but that's beyond the scope of this chapter. Next, the server implements the RemoteInterface that you defined in Listing 40.1.


CAUTION:
Under JDK 1.02, you will need to import and extend java.rmi.UnicastRemoteObject, not UnicastRemoteObject. So the header for the class under JDK 1.02 is:

public class RemoteObject extends UnicastRemoteObject implements RemoteInterface{




Unfortunately, this change can cause a number of incompatibilities.


Each method in the RemoteObject that can be called via RMI must declare that it will throw a RemoteException. Notice that even the constructor method must be defined to throw a RemoteException. The reason for this isn't immediately obvious. After all, which of the commands in the constructor method could possibly throw an exception? It's certainly not the assignment of name, so that leaves: the super() constructor call, of course. Sure enough, what UnicastRemoteObject's constructor does is export the remote object (the one just created) by listening for incoming requests for the object on the anonymous port (1099). Unfortunately this export may fail if the resources to do communication are unavailable, causing an Exception, which your class must throw.


NOTE: As with all classes which extend other classes, the super() call occurs by default (assuming one is available) but, to help you see where the exception is called from, it's included here explicitly.


Of course the RemoteObject must define the message method of RemoteInterface, because it implemented RemoteInterface. You are most concerned with this method because this is the method you try to call using RMI. To make things simple, the message method simply returns a String, which includes the message that is received. If our client program receives the String back, you can be sure that the server received your original String.

The first thing the main method does is establish a new SecurityManager. This security manager does not necessarily have to be RMISecurityManager, but the new security manager does have to allow RMI objects to be loaded. This is important to make sure that RMI objects do not perform operations that might be considered to be sensitive. The default security manager does not allow any RMI objects to be exported.

The next thing the main method does is create an instance of RemoteObject which will actu- ally be the instance that is "attached" to by the client program. This object must then be bound into the registry. Now, there are some important things to notice about how this is done. The rebind() method has two parameters. The first is the name by which the object will be known, the second is the object itself. In this case you are binding the object to the local machine and it's not really necessary to fully qualify the name. To use a fully qualified URL, the syntax would be:

//host.name.com/bindname

However, as in the previous example, only the bind name is really required.


NOTE: Using 1.02, you can have a space in the name of the object; however, 1.1 no longer supports this feature.


CAUTION:
Under JDK 1.02 you will need to set the security manager to java.rmi.server.StubSecurityManager, not java.rmi.RMISecurityManager. So you will need to change the first line of the main() method to read:

System.setSecurityManager (new StubSecurityManager());




Also note that you must import this class, and not the RMISecurityManager one, as well.


Compiling the RemoteSever

As with Object Serializaiton, it is once again necessary to include additional classes when compiling RemoteObject.


NOTE: For users of JDK 1.02, before compiling RemoteObject, you will need to download the Remote Method patch as detailed in the previous chapter with Object Serialization, and add the rmi.zip file to your classpath as indicated here:

set classpath=c:\java\lib\classes.zip;c:\java\lib\rmi.zip;c:\objio.zip;.




It's not technically necessary to include the OBJIO.ZIP file at this point, but it's not a bad idea to keep it there for good measure.


You can now compile the RemoteObject by typing the following:

javac RemoteObject.java

Creating the Stubs

The next step to creating an RMI server is to create the stubs and skeletons for the RemoteObject. You can do this using the rmic compiler by typing the following:

rmic RemoteObject

As you can see, the syntax for the rmic compiler is nearly identical to that for the java command. In fact, many of the same command line options that you have available to you when running the java command are available to you when running rmic.

Unfortunately, a small quirk in the Windows version of the JDK does not automatically include the current directory (.) in the classpath as it does in java or javac, so you will need to use the classpath option as shown below (which assumes you have the JDK1.1 installed in the c:\java directory).

rmic -classpath c:\java\lib\classes.zip;. RemoteObject

The rmic compiler produces two files for you:

RemoteObject_Skel.class 
RemoteObject_Stub.class

Creating a Client

The next step to creating an RMI program is to create the client that will actually invoke the remote methods. Listing 40.3 shows an example class.

Listing 40.3RemoteClient.java--An Example Client that Interfaces to the RemoteObject Class

import java.rmi.RMISecurityManager;
import java.rmi.Naming;
public class RemoteClient {
      public static void main(String args[]){
           System.setSecurityManager(new RMISecurityManager());
           try{
                 RemoteInterface server = (RemoteInterface)
           Naming.lookup("ServerTest");
                 String serverString = server.message("Hello There");
                 System.out.println("The server says :\n"+serverString);
           } catch (Exception e){
                 System.out.println("Error while performing RMI");
           }
      }
}

The most important portions of the RemoteClient class are the two lines in the middle of the try-catch block:

remoteInterface server = (RemoteInterface) Naming.lookup("Server Test");     
String serverString = server.message("Hello There");

The first line of code looks to the registry to locate the stub called "Server Test" (if you look back to the RemoteObject program in Listing 40.2, line 21, you will see that you bound it using this name). Once the program has created an instance of the RemoteInterface, it then calls the message method with the string "Hello There." Notice that this is actually a method call. You are invoking a method on a completely different system. The method then returns a string that is stored in serverString and later printed out.

You can now compile the client program just as you did for RemoteObject:

javac RemoteClient.java

This, of course, assumes that you have already set your classpath for the RemoteObject class.

Starting the Registry and Running the Code

Before you can actually run the RemoteObject and RemoteClient classes, you must first start the RMI Registry program on the computer that will be hosting the RemoteObject. This step is required even if the computer that you will be running the RemoteObject on is the same as the RemoteClient (as you will do in this case for demonstration purposes). In order for the registry to work, the directory with the stub and skeleton files must be in the classpath for the rmiregistry program.

Unfortunately, just as with rmic, the registry program does not even include the current directory in the path, so to start the Registry program, type:

set classpath=c:\java\lib\classes.zip;.
     start rmiregistry

This command will cause the RMI Register program to start. On UNIX machines, you can start the registry and push it into the background by typing:

set classpath=/usr/java/lib/classes.zip;.
rmiregistry &

If you want to start the registry out of a different directory than the skeleton/stub directory, you should substitute the period (.) with the directory containing these files. Also, you should make sure that the location of classes.zip matches your installation.


NOTE: For users of JDK 1.02, you need to start the registry in a slightly different fashion. Under 1.02, the following command will start the registry up.

java java.rmi.registry.RegistryImpl



Binding RemoteObject into the Registry

Once the registry has been started, you need to start the RemoteObject program. You can start the RemoteObject program by typing:

javaw RemoteObject

As with the Registry program, you can pass this into the background on a UNIX machine by running instead as:

java RemoteObject &

Running the Client Program

The last task is to start the RemoteClient. However, before you do, make sure that the RemoteObject program has printed:

Ready to continue

This is your clue that the RemoteObject has been exported and bound into the registry. Be patient, especially if you don't have an active internet connection, because this can take a while.

Once the RemoteObject has let you know it's okay to continue, you want to start the RemoteClient. To do this, if you're running under Windows you will need to open another DOS prompt window. If you're using a UNIX machine, even if you have put the RemoteObject in the background, you will probably want another session (or x-terminal) so that you can tell the difference between the outputs from the server and the client.

Finally, to run the RemoteClient type:

java RemoteClient

The following output should appear on the screen.

The Server Says:
My Name is:ServerTest, thanks for your message:Hello There

Notice that the string was produced on the server and returned to you. If you look at the RemoteObject window, what you will see is output that says:

Ready to continue
Returning: My Name is:ServerTest, thanks for your message:Hello There

Creating an Applet Client

Now that you have created an application that utilizes the RemoteObject, try doing this with an Applet as shown in Listing 40.4. As you will soon see, there really isn't much difference.


NOTE: Due to the changes in the virtual machine that are required for RMI and Object Serialization, at the time of this writing you can only run these applets using Appletviewer. Neither Netscape Navigator nor Microsoft Internet Explorer have support for RMI or Object Serialization.


Listing 40.4RemoteAppletClient.java: An Applet that Uses a Remote Object

/*
 *
 * RemoteAppletClient
 *
 */
import java.applet.Applet;
import java.rmi.RMISecurityManager;
import java.rmi.Naming;
public class RemoteAppletClient extends Applet {
  public void init(){
    System.setSecurityManager(new RMISecurityManager());
    try{
      RemoteInterface server = (RemoteInterface) Naming.lookup("ServerTest");     
      String serverString = server.message("Hello There");
      System.out.println("The server says :\n"+serverString);
      } catch (Exception e){
        System.out.println("Error while performing RMI:"+e);
        }
   }
 }