18.2 Implementation

     

Most of the methods you need for working with remote objects are in three packages: java.rmi , java.rmi.server , and java.rmi.registry . The java.rmi package defines the classes, interfaces, and exceptions that will be seen on the client side. You need these when you're writing programs that access remote objects but are not themselves remote objects. The java.rmi.server package defines the classes, interfaces, and exceptions that will be visible on the server side. Use these classes when you are writing a remote object that will be called by clients . The java.rmi.registry package defines the classes, interfaces, and exceptions that are used to locate and name remote objects.

In this chapter and in Sun's documentation, the server side is always considered to be "remote" and the client is always considered "local". This can be confusing, particularly when you're writing a remote object. When writing a remote object, you're probably thinking from the viewpoint of the server, so that the client appears to be remote.


18.2.1 The Server Side

To create a new remote object, first define an interface that extends the java.rmi.Remote interface. Remote is a marker interface that does not have any methods of its own; its sole purpose is to tag remote objects so that they can be identified as such. One definition of a remote object is an instance of a class that implements the Remote interface, or any interface that extends Remote .

Your subinterface of Remote determines which methods of the remote object clients may call. A remote object may have many public methods, but only those declared in a remote interface can be invoked remotely. The other public methods may be invoked only from within the virtual machine where the object lives.

Each method in the subinterface must declare that it throws RemoteException . RemoteException is the superclass for most of the exceptions that can be thrown when RMI is used. Many of these are related to the behavior of external systems and networks and are thus beyond your control.

Example 18-2 is a simple interface for a remote object that calculates Fibonacci numbers of arbitrary size . (Fibonacci numbers are the sequence that begins 1, 1, 2, 3, 5, 8, 13 . . . in which each number is the sum of the previous two.) This remote object can run on a high-powered server to calculate results for low- powered clients. The interface declares two overloaded getFibonacci( ) methods, one of which takes an int as an argument and the other of which takes a BigInteger . Both methods return BigInteger because Fibonacci numbers grow very large very quickly. A more complex remote object could have many more methods.

Example 18-2. The Fibonacci interface
 import java.rmi.*; import java.math.BigInteger; public interface Fibonacci extends Remote {   public BigInteger getFibonacci(int n) throws RemoteException;   public BigInteger getFibonacci(BigInteger n) throws RemoteException; } 

Nothing in this interface says anything about how the calculation is implemented. For instance, it could be calculated directly, using the methods of the java.math.BigInteger class. It could be done equally easily with the more efficient methods of the com.ibm.BigInteger class from IBM's alphaWorks (http://www.alphaworks.ibm.com/tech/bigdecimal). It could be calculated with int s for small values of n and BigInteger for large values of n . Every calculation could be performed immediately, or a fixed number of threads could be used to limit the load that this remote object places on the server. Calculated values could be cached for faster retrieval on future requests , either internally or in a file or database. Any or all of these are possible. The client neither knows nor cares how the server gets the result as long as it produces the correct one.

The next step is to define a class that implements this remote interface. This class should extend java.rmi.server.UnicastRemoteObject , either directly or indirectly (i.e., by extending another class that extends UnicastRemoteObject ):

 public class UnicastRemoteObject extends RemoteServer 

Without going into too much detail, the UnicastRemoteObject provides a number of methods that make remote method invocation work. In particular, it marshals and unmarshals remote references to the object. ( Marshalling is the process by which arguments and return values are converted into a stream of bytes that can be sent over the network. Unmarshalling is the reverse: the conversion of a stream of bytes into a group of arguments or a return value.)

If extending UnicastRemoteObject isn't convenientfor instance, because you'd like to extend some other classyou can instead export your object as a remote object by passing it to one of the static UnicastRemoteObject.exportObject() methods:

 public static RemoteStub exportObject(Remote obj)   throws RemoteException public static Remote exportObject(Remote obj, int port)  // Java 1.2  throws RemoteException public static Remote exportObject(Remote obj, int port,  // Java 1.2  RMIClientSocketFactory csf, RMIServerSocketFactory ssf)   throws RemoteException 

These create a remote object that uses your object to do the work. It's similar to how a Runnable object can be used to give a thread something to do when it's inconvenient to subclass Thread . However, this approach has the downside of preventing the use of dynamic proxies in Java 1.5, so you need to manually deploy stubs. (In Java 1.4 and earlier, you always have to use stubs.)

There's one other kind of RemoteServer in the standard Java class library, the java.rmi.activation.Activatable class:

 public abstract class Activatable extends RemoteServer // Java 1.2 

A UnicastRemoteObject exists only as long as the server that created it still runs. When the server dies, the object is gone forever. Activatable objects allow clients to reconnect to servers at different times across server shutdowns and restarts and still access the same remote objects. It also has static Activatable.exportObject( ) methods to invoke if you don't want to subclass Activatable .

Example 18-3, the FibonacciImpl class, implements the remote interface Fibonacci . This class has a constructor and two getFibonacci( ) methods. Only the getFibonacci( ) methods will be available to the client, because they're the only ones defined by the Fibonacci interface. The constructor is used on the server side but is not available to the client.

Example 18-3. The FibonacciImpl class
 import java.rmi.*; import java.rmi.server.UnicastRemoteObject; import java.math.BigInteger; public class FibonacciImpl extends UnicastRemoteObject implements Fibonacci {   public FibonacciImpl( ) throws RemoteException {     super( );   }        public BigInteger getFibonacci(int n) throws RemoteException {     return this.getFibonacci(new BigInteger(Long.toString(n)));     }      public BigInteger getFibonacci(BigInteger n) throws RemoteException {            System.out.println("Calculating the " + n + "th Fibonacci number");     BigInteger zero = new BigInteger("0");     BigInteger one  = new BigInteger("1");          if (n.equals(zero)) return one;     if (n.equals(one)) return one;      BigInteger i  = one;     BigInteger low  = one;     BigInteger high  = one;     while (i.compareTo(n) == -1) {       BigInteger temp = high;       high = high.add(low);       low = temp;        i = i.add(one);     }     return high;   } } 

The FibonacciImpl( ) constructor just calls the superclass constructor that exports the object; that is, it creates a UnicastRemoteObject on some port and starts it listening for connections. The constructor is declared to throw RemoteException because the UnicastRemoteObject constructor can throw that exception.

The getFibonacci(int n) method is trivial. It simply returns the result of converting its argument to a BigInteger and calling the second getFibonacci( ) method. The second method actually performs the calculation. It uses BigInteger throughout the calculation to allow for arbitrarily large Fibonacci numbers of an arbitrarily large index to be calculated. This can use a lot of CPU power and huge amounts of memory. That's why you might want to move it to a special-purpose calculation server rather than performing the calculation locally.

Although getFibonacci( ) is a remote method, there's nothing different about the method itself. This is a simple case, but even vastly more complex remote methods are not algorithmically different than their local counterparts. The only differencethat a remote method is declared in a remote interface and a local method is notis completely external to the method itself.

Next, we need to write a server that makes the Fibonacci remote object available to the world. Example 18-4 is such a server. All it has is a main( ) method. It begins by entering a try block that catches RemoteException . Then it constructs a new FibonacciImpl object and binds that object to the name "fibonacci" using the Naming class to talk to the local registry. A registry keeps track of the available objects on an RMI server and the names by which they can be requested . When a new remote object is created, the object adds itself and its name to the registry with the Naming.bind( ) or Naming.rebind( ) method. Clients can then ask for that object by name or get a list of all the remote objects that are available. Note that there's no rule that says the name the object has in the registry has to have any necessary relation to the class name. For instance, we could have called this object "Fred". Indeed, there might be multiple instances of the same class all bound in a registry, each with a different name. After registering itself, the server prints a message on System.out signaling that it is ready to begin accepting remote invocations. If something goes wrong, the catch block prints a simple error message.

Example 18-4. The FibonacciServer class
 import java.net.*; import java.rmi.*; public class FibonacciServer {   public static void main(String[] args) {     try {       FibonacciImpl f = new FibonacciImpl( );       Naming.rebind("fibonacci", f);       System.out.println("Fibonacci Server ready.");     }      catch (RemoteException rex) {       System.out.println("Exception in FibonacciImpl.main: " + rex);     }     catch (MalformedURLException ex) {       System.out.println("MalformedURLException " + ex);     }        } } 

Although the main( ) method finishes fairly quickly here, the server will continue to run because a nondaemon thread is spawned when the FibonacciImpl object is bound to the registry. This completes the server code you need to write.

18.2.2 Compiling the Stubs

RMI uses stub classes to mediate between local objects and the remote objects running on the server. Each remote object on the server is represented by a stub class on the client. The stub contains the information in the Remote interface (in this example, that a Fibonacci object has two getFibonacci( ) methods). Java 1.5 can sometimes generate these stubs automatically as they're needed, but in Java 1.4 and earlier, you must manually compile the stubs for each remote class. Even in Java 1.5, you still have to manually compile stubs for remote objects that are not subclasses of UnicastRemoteObject and are instead exported by calling UnicastRemoteObject.exportObject( ) .

Fortunately, you don't have to write stub classes yourself: they can be generated automatically from the remote class's byte code using the rmic utility included with the JDK. To generate the stubs for the FibonacciImpl remote object, run rmic on the remote classes you want to generate stubs for. For example:

 %  rmic FibonacciImpl  %  ls Fibonacci*  Fibonacci.class      FibonacciImpl_Stub.class  FibonacciServer.java FibonacciImpl.class  Fibonacci.java FibonacciImpl.java   FibonacciServer.class 

rmic reads the .class file of a class that implements Remote and produces .class files for the stubs needed for the remote object. The command-line argument to rmic is the fully package-qualified class name (e.g., com.macfaq.rmi.examples.Chat , not just Chat ) of the remote object class.

rmic supports the same command-line options as the javac compiler: for example, - classpath and -d . For instance, if the class doesn't fall in the class path , you can specify the location with the -classpath command-line argument. The following command searches for FibonacciImpl.class in the directory test/classes :

 % rmic -classpath test/classes FibonacciImpl 

18.2.3 Starting the Server

Now you're ready to start the server. There are actually two servers you need to run, the remote object itself ( FibonacciServer in this example) and the registry that allows local clients to download a reference to the remote object. Since the server expects to talk to the registry, you must start the registry first. Make sure all the stub and server classes are in the server's class path and type:

 %  rmiregistry &  

On Windows, you start it from a DOS prompt like this:

 C:>  start rmiregistry  

In both examples, the registry runs in the background. The registry tries to listen to port 1,099 by default. If it fails, especially with a message like "java.net. SocketException: Address already in use", then some other program is using port 1099, possibly (though not necessarily ) another registry service. You can run the registry on a different port by appending a port number like this:

 %  rmiregistry 2048 &  

If you use a different port, you'll need to include that port in URLs that refer to this registry service.

Finally, you're ready to start the server. Run the server program just as you'd run any Java class with a main( ) method:

 %  java FibonacciServer  Fibonacci Server ready. 

Now the server and registry are ready to accept remote method calls. Next we'll write a client that connects to these servers to make such remote method calls.

18.2.4 The Client Side

Before a regular Java object can call a method, it needs a reference to the object whose method it's going to call. Before a client object can call a remote method, it needs a remote reference to the object whose method it's going to call. A program retrieves this remote reference from a registry on the server where the remote object runs. It queries the registry by calling the registry's lookup( ) method. The exact naming scheme depends on the registry; the java.rmi.Naming class provides a URL-based scheme for locating objects. As you can see in the following code, these URLs have been designed so that they are similar to http URLs. The protocol is rmi . The URL's file field specifies the remote object's name. The fields for the hostname and the port number are unchanged:

 Object o1 = Naming.lookup("rmi://login.ibiblio.org/fibonacci"); Object o2 = Naming.lookup("rmi://login.ibiblio.org:2048/fibonacci"); 

Like objects stored in Hashtable s, Vector s, and other data structures that store objects of different classes, the object that is retrieved from a registry loses its type information. Therefore, before using the object, you must cast it to the remote interface that the remote object implements (not to the actual class, which is hidden from clients):

 Fibonacci calculator = (Fibonacci) Naming.lookup("fibonacci"); 

Once a reference to the object has been retrieved and its type restored, the client can use that reference to invoke the object's remote methods pretty much as it would use a normal reference variable to invoke methods in a local object. The only difference is that you'll need to catch RemoteException for each remote invocation. For example:

 try {   BigInteger f56 = calculator.getFibonacci(56);   System.out.println("The 56th Fibonacci number is " + f56);   BigInteger f156 = calculator.getFibonacci(new BigInteger(156));   System.out.println("The 156th Fibonacci number is " + f156); } catch (RemoteException ex) {   System.err.println(ex) } 

Example 18-5 is a simple client for the Fibonacci interface of the last section.

Example 18-5. The FibonacciClient
 import java.rmi.*; import java.net.*; import java.math.BigInteger; public class FibonacciClient {   public static void main(String args[]) {              if (args.length == 0  !args[0].startsWith("rmi:")) {       System.err.println(         "Usage: java FibonacciClient rmi://host.domain:port/fibonacci number");       return;        }                  try {             Object o = Naming.lookup(args[0]);       Fibonacci calculator = (Fibonacci) o;       for (int i = 1; i < args.length; i++) {         try {           BigInteger index = new BigInteger(args[i]);           BigInteger f = calculator.getFibonacci(index);           System.out.println("The " + args[i] + "th Fibonacci number is "             + f);         }         catch (NumberFormatException e) {           System.err.println(args[i] + "is not an integer.");         }       }      }     catch (MalformedURLException ex) {       System.err.println(args[0] + " is not a valid RMI URL");     }     catch (RemoteException ex) {       System.err.println("Remote object threw exception " + ex);     }     catch (NotBoundException ex) {       System.err.println(        "Could not find the requested remote object on the server");     }    } } 

Compile the class as usual. Notice that because the object that Naming.lookup( ) returns is cast to a Fibonacci , either the Fibonacci.java or Fibonacci.class file needs to be available on the local host. A general requirement for compiling a client is to have either the byte or source code for the remote interface you're connecting to. To some extent, you can relax this a little bit by using the reflection API, but you'll still need to know at least something about the remote interface's API. Most of the time, this isn't an issue, since the server and client are written by the same programmer or team. The point of RMI is to allow a VM to invoke methods on remote objects, not to compile against remote objects.

18.2.5 Running the Client

Go back to the client system. Make sure that the client system has FibonacciClient.class , Fibonacci.class , and FibonacciImpl_Stub.class in its class path. (If both the client and the server are running Java 1.5, you don't need the stub class.) On the client system, type:

 C:\>  java FibonacciClient rmi://host.com/fibonacci 0 1 2 3 4 5 55 155  

You should see:

 The 0th Fibonacci number is 1 The 1th Fibonacci number is 1 The 2th Fibonacci number is 2 The 3th Fibonacci number is 3 The 4th Fibonacci number is 5 The 5th Fibonacci number is 8 The 55th Fibonacci number is 225851433717 The 155th Fibonacci number is 178890334785183168257455287891792 

The client converts the command-line arguments to BigInteger objects. It sends those objects over the wire to the remote server. The server receives each of those objects, calculates the Fibonacci number for that index, and sends a BigInteger object back over the Internet to the client. Here, I'm using a PC for the client and a remote Unix box for the server. You can actually run both server and client on the same machine, although that's not as interesting.



Java Network Programming
Java Network Programming, Third Edition
ISBN: 0596007213
EAN: 2147483647
Year: 2003
Pages: 164

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net