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.
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.
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:
See "Extending Other Interfaces," Chapter 12
NOTE: When parameters are required by an RMI method, the objects are passed using object serialization, as discussed in Chapter 39.
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.
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.
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.
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.
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.
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.
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.
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
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
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.
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.
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
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 &
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
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.
/* * * 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); } } }