servicestechnologyarticlesnewsemploymentcontacthome
 

 

  Web-based Java Application Implementation: Applets vs. Servlets  
  JDBC Intricacies: PreparedStatements and CallableStatements  

 

 


AN INTRODUCTION TO RMI

 

The Remote Method Invocation API is a very powerful tool when it comes to implementing client/server communication in Java. It eliminates the need for creating protocols by the passing of Java objects and primitives between Virtual Machines. This ability allows the developer to create methods that facilitate communication between the client and server transparently. All of this functionality does require some initial setup, and this article will present how to create a simple client/server using RMI.

Before introducing the code, some of the underlying RMI architecture must be discussed. The most important layer that the novice developer must be concerned with is the Stub/Skeleton layer. This layer provides the blueprints of the client and server methods that the lower layers will use to actually communicate. Stubs are located on the client-side, and handle the initialization of methods called by the server, along with sending parameters. Skeletons reside on the server-side, and they deal with the receipt of parameters and returning values to the client. Both skeletons and stubs are created with the RMI compiler, rmic, which is used on both the client and server classes after they have been compiled. Examples will be given later on.

The client/server example presented in this article is a very simple library system, where the clients connect to the server and can either check in or out the available books. In order to implement this system, we first need one interface for the client and one for the server, where both extend the empty interface java.rmi.Remote. They each define the methods that the actual client and server must implement. In addition, each method must declare that it throws java.rmi.RemoteException, the superclass of all RMI exceptions. Here is LibraryClient.java:


import java.rmi.*;
import java.util.*;

public interface LibraryClient extends Remote {

  void setBooks(Vector books) throws RemoteException;

}

The method setBooks() is called by the server to update the book lists of all active clients. And here is LibraryServer.java:

import java.rmi.*;
import java.util.*;

public interface LibraryServer extends Remote {

 void processRequests(LibraryClient client) throws RemoteException;

 void stopClient(LibraryClient client) throws RemoteException;

 Vector getBooks() throws RemoteException;

 void checkin(String title) throws RemoteException;

 void checkout(String title) throws RemoteException;

}

The method processRequests() tells the server that a particular client wants its requests processed. stopClient() kills a client's connection to the server and removes it from the server's list of attached clients. Clients call getBooks() to retrieve the list of books from the server. The methods checkin() and checkout() actually change the book status on the server-side. Now the concrete client and server classes must be implemented, and like any class that implements an interface, it must implement all of that interface's methods. Explanations will be given for the methods dealing directly with RMI calls, and the complete source code can be retrieved from the link at the end of this article. First, here is LibraryClientImpl.java:

import java.awt.*;
import java.awt.event.*;
import java.rmi.*;
import java.rmi.server.*;
import java.util.*;


public class LibraryClientImpl extends Frame
  implements LibraryClient, ActionListener, ItemListener {

  public static final int DEF_PORT = 1099;

  protected LibraryServer server = null;

  protected static final int WIDTH = 400;

  protected static final int HEIGHT = 200;

  protected Vector vBooks = null;

protected Hashtable books = new Hashtable();
 
  private List bookList = null;

  private TextField title = null, author = null, publisher = null,
  date = null, status = null;

  public static void main(String args[]) {
   try {
    new LibraryClientImpl();
   }
   catch (Exception e) {
    e.printStackTrace();
    System.exit(-1);
   }
  }

  public LibraryClientImpl() {
  super("Library Client");

   connect("localhost");

  try {
     vBooks = server.getBooks();
   } catch (RemoteException e) {
    e.printStackTrace();
   }

   // setup remaining panels
   constructLayout();

   // handle window close requests
   this.addWindowListener(new WindowAdapter() {
   public void windowClosing(WindowEvent e) { close(0); }
   });

   this.pack();
   this.show();
  }

  protected void connect(String sName) {
   try {
    UnicastRemoteObject.exportObject(this);
    String serverName = "//" + sName + "/LibraryServerImpl";
    server = (LibraryServer)Naming.lookup(serverName);
    server.processRequests(this);
   } catch (java.rmi.ConnectException ce) {
    System.err.println("Error: server not started");
   close(-1);
   } catch (java.rmi.ConnectIOException cioe) {
   System.err.println("Error: Cannot connect to server at" + sName);
   close(-1);
  } catch (Exception e) {
   e.printStackTrace();
    close(-1);
   }
  }

  ...

}

The most important method here is connect(), which actually sets up the connection to the Library server. The first line (after the try) exports the client (the remote object) to make it available to receive incoming calls. The second line initializes the address of the server in the URL format:

//host:port/servername

In this example, the arbitrary port used is 1099, the host is the local machine, and the server name is LibraryServerImpl. The third line retrieves an instance of the server from the RMI registry using the address above. Line four tells the server that this client is ready to have its requests processed. The remaining lines take care of the errors that could occur. A java.rmi.ConnectException is thrown when the RMI server has not been started, and java.rmi.ConnectIOException when there is a problem connecting to an instance of the server. This is all there is to initializing the client connection to the server.

The server setup is a little more complex, as the source code for LibraryServerImpl.java shows:

import java.rmi.*;
import java.rmi.registry.*;
import java.rmi.server.*;
import java.util.*;

public class LibraryServerImpl extends UnicastRemoteObject
  implements LibraryServer, Runnable {

  public static final int DEF_PORT = 1099;

  protected Hashtable clientTable = new Hashtable();

  protected Thread notifier = null;

  protected Vector vBooks = new Vector();

  protected Hashtable books = new Hashtable();

  public static void main(String[] args) {
   int port = DEF_PORT;
   try {
    String url = null;
    // Create and install the security manager
    System.setSecurityManager(new RMISecurityManager());

    if (args.length > 0) {
     port = Integer.parseInt(args[0]);
    }
    LocateRegistry.createRegistry(port);
    LibraryServer server = new LibraryServerImpl();
    url = "//:" + port + "/LibraryServerImpl";
    Naming.rebind(url, server);
   } catch (ExportException ee) {
    System.err.println("Error: server already started on port " + port);
   System.exit(-1);
   } catch (Exception e) {
   e.printStackTrace();
   System.exit(-1);
   }
  } // end main

  public LibraryServerImpl() throws RemoteException {
   super();

   loadBooks();
   System.out.println("Books loaded");
  }

  public synchronized void processRequests(LibraryClient client) throws RemoteException {
  LibraryClient prevEntry = (LibraryClient)clientTable.get(client);
  if (prevEntry == null) {
    clientTable.put(client, client);
  }
 if (notifier == null) {
  notifier = new Thread(this);
 v notifier.start();
  }
}

protected synchronized void removeClient(LibraryClient client) {
 clientTable.remove(client);
 if (clientTable.isEmpty()) {
  Thread thread = notifier;
  notifier = null;
  thread.stop();
 }
}

public void stopClient(LibraryClient client) throws RemoteException {
  removeClient(client);
}

...

}

The first thing to notice is that the server extends java.rmi.server.UnicastRemoteObject. This class defines a remote object that provides point-to-point communication using TCP streams, and therefore all RMI servers must extend it. Two important member variables are the clientTable, which contains all active clients, and the notifier thread, which tells the server the are no more clients. The server is initialized is in the main method which can take an optional argument containing the port number that the server should start on. First, the default security manager is set to a java.rmi.RMISecurityManager. Note that this manager cannot be used in applets since it loosens the security model as to allow remote classes to be downloaded from remote systems. Next, a new RMI registry is created on the specified port. Then, a new instance of the server is constructed, and then the registry binds that instance to the specified port using the same URL as in the client, minus the host machine. The only major exception thrown during this process is java.rmi.server.ExportException, which tells the user that the server has already been started on the port specified.

The server's constructor is used to initialize static objects to be referenced by the client. The method processRequests() adds a particular client to the clientTable so all clients can be notified of certain events. stopClient() is called by a client before it exits so the server can clean up after that particular client.

Those are all the steps to setup both the RMI server and clients. The remaining methods are application specific. There is a pattern to these methods, and that is all server methods that are called by the client must be defined in the server's interface and also must throw java.rmi.RemoteException. The same is true for all client methods called by the server.

One last topic must be covered, and that is the compilation procedure for the classes. First, the interfaces for the client and server must be compiled with javac. Next, compile the implementations of the client and server, also with javac. The final step is to compile the stubs and skeletons for the generated implementation classes using rmic. The procedure for the example code is given below:

javac LibraryClient.java LibraryServer.java
javac LibraryClientImpl.java LibraryServerImpl.java
rmic LibraryClientImpl LibraryServerImpl

Remember not to append .java to the class names in the rmic step! Also, in order to run the client, make sure the server is started before the client.

The Remote Method Invocation API is a very useful addition to the core functionality of Java. It allows developers to implement distributed client/server systems much faster and more reliably without the overhead of implementing connection and parameter-passing protocols. Another benefit of using RMI is that it is 100% Java, so any architecture that implements Java can use it. In Java 1.2, RMI will be enhanced even more with the addition of secure connections using Secure Socket Layer (SSL) and seamless integration with CORBA objects using Internet InterORB Protocol (IIOP). All of these features will make Java a viable choice when implementing enterprise-wide solutions in the future.

Download the source code for this article.

 

Please send feedback to articles@thirdeyeconsulting.com

 

servicestechnologyarticlesnewsemploymentcontacthome