Home Papers Reports Projects Code Fragments Dissertations Presentations Posters Proposals Lectures given Course notes
<< 3. TCP IP Sockets in Java5. Borg; message passing, strong mobility, synchronization >>

4. Java Remote Method Invocation (RMI)

Werner Van Belle1* - werner@yellowcouch.org, werner.van.belle@gmail.com

1- Programming Technology Lab (PROG) Department of Computer Science (DINF) Vrije Universiteit Brussel (VUB); Pleinlaan 2; 1050 Brussel; Belgium

Abstract :  These are course notes I made to teach distributed systems for the VUB. I used them in 1998-1999, 1999-2000, 2000-2001, 2001-2002, 2002-20003

Reference:  Werner Van Belle; Java Remote Method Invocation (RMI);


1 Java

The exercises take place in Java. Some hints to ease the pain:

  1. set CLASSPATH=.
    Put this in your .bash_rc or .profile
  2. public classes
    must be written in files with exactly the same name
  3. javac is the compiler
    javac blah.java compiled blah.java to blah.class
  4. java is the interpreter.
    java blah executes blah.class
    Put no .java or .class behind blah !
  5. try {...} catch (Exception e) {System.out.println(e)}
    helps when prototyping.
  6. If you are a java-guru:
    Do NOT use packages.
  7. If a strange error occurs when compiling a class (a non matching method), make sure you wrote a constructor for that class.

2 Introduction

JAVA WAS ORIGINALLY CONCEIVED BY SUN to be used on embedded systems, but it turned out to be a better language for networks because it offered a virtual machine that could run Java applications on all kinds of different architectures. Java is an object oriented programming language that supports automated memory management (= garbage collection), threads, and interfaces. Libraries like Java RMI (remote method invocation) are available to support semi transparent remote accessibility. This model, a typical thread based, transparent distribution model, is the focus of this exercise.

3 Logical Operation

Figure 1: Schematic overview of the different actors passing your RMI request along.
Image javarmi-schematic

Figure 2: Logical Operation of a Java RMI call. The client waits until the server returns.
Image javarmi-logical

JAVA PROCESSES can communicate by using Java RMI. Communication happens solely between Java virtual machines, thereby using an interpreted byte code which can run on different machines. Java RMI communication goes from a client process toward a server object. The client requests the name of a certain object at a central registry. Normally it receives a stub object in response. Every call invoked upon this stub object (on the client) will be translated by the RMI architecture to a call on the matching server object (on the server). As long as the server doesn't send a response the client keeps on waiting. This is illustrated in Figure 2.

The protocol between client and server is defined as an interface extending the Remote type (this is comparable to IDL's in CORBA, in this instance however it is a language feature and does not require a special compiler). This interface (the protocol description) is the only class-file that ought to be shared between client and server. The client uses this information to create a stub, the server uses this information to create a skeleton, listing.

3.1 A Protocol Description

import java.rmi.Remote; 
import java.rmi.RemoteException; 

public interface Protocol extends Remote 
{
   String giveHello() throws RemoteException; 
}

3.2 A Server Object

The code below is the implementation of a server object implementing the above protocol. Note that the newly created object announces itself at the RMI registry using the 'Naming' class to export its own name. From now on everybody can ask the registry a reference for that name.
import java.rmi.Naming; 
import java.rmi.server.UnicastRemoteObject; 

public class ServerImpl extends UnicastRemoteObject implements Protocol
{
   public ServerImpl() throws RemoteException 
     {
	super();
     }
   
   // implementation of the protocol
   public String giveHello() 
     {
	return *blort*;
     }
   // instantiate a server object and bind it
   public static void main(String args[]) 
     {
	try 
	  {
	     ServerImpl obj = new ServerImpl(); 
	     Naming.rebind("//7.0.0.1/HelloServer", obj);
	     System.out.println("HelloServer bound in registry");
	  } 
	catch (Exception e) 
	  { 
	     System.out.println("ServerImpl err: " + e.getMessage());
	     e.printStackTrace(); 
	  } 
	// the program does not end as long as there is a 
	// background thread running 
     }
}
 

3.3 A Client Calling the Server

import java.rmi.Naming; 

public class ClientApplication
{
   static Protocol obj = null; 
   public static void main(String args[]) 
     {
	try 
	  {
	     /* zoek het object op */
	     obj = (Protocol)Naming.lookup("//7.0.0.1/HelloServer");
	     /* en gewoon gebruiken */
	     System.out.println(obj.giveHello());
	  }
	catch (Exception e)
	  {
	     System.out.println("ClientApplication exc: " + e.getMessage());
	     e.printStackTrace();
	  }
     }
}
   
   

4 Internal Operation

Figure 3: Remote object calling in Java RMI.
Image javarmi-internal

Sun's serialization and deserialization interface to Java helps exporting an object (and all the objects it knows and depends on) to a byte stream. The standard behavior for serialization is to serialize the object and all the objects it contains. If we want to modify this behavior we can use an externalizable interface which describes how the object is to be exported.

To be able to contact a remote object as if it were a local object, one should create stubs for all the remote objects which will be contacted (one can do this automatically at compile time using rmic). Such a stub will provide all the methods the remote objects supports and fill in the bodies with code to contact the remote object. The logic within one such a stub body is quite simple:

  1. Contact the remote object using sockets
  2. Serialize all the arguments passed to the stub by the caller.
  3. Send out all arguments
  4. Wait for an answer, which will be the result
  5. Receive/read the result
  6. Deserialize the result
  7. Return the result to the original caller
The object that is being exported at the server side, should offer a way to be contacted by remote clients. This is done by a skeleton, which contains some listen code and logic to contact the actual exported object. The logic in a skeleton is also very simple:
  1. Listen for connections
  2. Accept an incoming connection in a separate thread
  3. Receive the serialized arguments
  4. Deserialize the arguments
  5. Invoke the method call upon the correct object
  6. Serialize the result of the invocation
  7. Send back the result over the socket
Figure 3 illustrates how we can transparently contact a remote object. We see how the setup is inherently client server. When we want to contact a client from within the server the client need to export an interface and become a listening server itself.

5 Warm Up: Running the Demo Code

To run the demo code do the following:

  1. Compile the .java files using javac: Protocol.java, ClientApplication.java and ServerImpl.java.
  2. After compiling the ServerImpl class, we generate a stub and skeleton class for the given protocol. We do this with rmic. (RMI stub Compiler)
  3. The first thing every distributed application has to do before connecting to some other process, is looking up who it should connect to. Java RMI uses for this purpose a special server which should be started on the machine where remote objects will be exported. This server is called the RMI registry. Before starting the code, make sure a rmiregistry is started.
  4. Now, start the server application (ServerImpl).
  5. Now start the client application (ClientApplication).

6 Exercise: White-board

A white-board is an application that allows multiple clients to work on the same shared resource. Clients can Join on the white-board. After Joining they can post a string to the white-board which will be repeated to all connected clients.

7 Advanced Exercise: RMI in detail

Some more advanced questions:

8 Java Threads

\begin{algorithm}
% latex2html id marker 270\par\begin{list}{}{\setlength{\r...
...par
}\end{list}\caption{
A distributed recursive factorial}
\par
\end{algorithm}IN ALL ITS ASPECTS JAVA is an innovating language. For example, it introduces garbage collection in the industry. On another note we see how Java introduces a standard threading library (compare this with C which has all kinds of dirty libraries and tricks like setjmp and longjmp to solve multiple sessions.) A thread is an execution context which can run together with other threads in the same environment. The difference with processes is that threads do share memory, while processes don't share memory.

This is important because Java RMI, as already seen, waits before returning an answer. With Java RMI a client can ask the server to execute a method and return the answer. In the meantime the client simply waits. Now, let's have a look at the Java program in algorithm 1.

The FacApp class exports a FacCalcer interface. A FacCalcer is an object that is able to calculate the factorial of a certain number by multiplying that number by the factorial of that number minus one. To do this last step the FacCalcer needs to be supplied another FacCalcer. This can be seen in the definition of the fac method. When we start this application it will first try to lookup an existing FacCalcer. If there is one it will contact that FacCalcer to calculate some factorial, if there is none, it will become the faccalcer itself and announce it in the registry.


To start this program, first an rmiregistry should be started and afterward two times the FacApp. The last facapp (faccalcer2 from now on) requests faccalcer1 to calculate the factorial of 3. In return faccalcer1 will request faccalcer2 to calculate the factorial of 1... But how is this possible ? How can the original requester be interrupted while he is still waiting. The answer lies in the Java threading mechanism. Every time an RMI call comes in, the server-thread will spawn a new thread which handles the request. This can be seen in figure 4.

Figure 4: In the above figure, full lines indicate a running process, dotted lines indicate a waiting (listening) process and horizontal dashed lines indicate a 'wait for return'. In this figure we see how a new thread is spawned every time a call comes in.
Image javarmi-factorial

http://werner.yellowcouch.org/
werner@yellowcouch.org