Java RMI and Class Download

Readme/index.html file


Copyright (C) 2007-2009 Fernando G. Tinetti (my web page at UNLP)

1.- Introduction
What follows is a short review of class download in the context of Java RMI. Conceptually, there are not "client" and "server" processes, but in this description it will be considered that the process or thread that makes the invocation to a remote method will be the "client" and the process or thread that implements and runs the invoked method will be the "server". Furthermore, this approach matches almost every RMI explanation in the literature. Different alternatives will be described from "no download at all" to "download on client and server side". Every alternative will be directly related to RMI, not necessarilly all the application possibilities are going to be examined. Every example has been tested under Linux Kernel 2.6, more specifically

$ uname -sr
Linux 2.6.15-1.2054_FC5


and with java jdk-1_5_0_06-linux-i586. More specifically,

$ javac -version
javac 1.5.0_06
...
$ java -version
java version "1.5.0_06"
...

Different configurations/designs for class download (will be explained below):

1) No download, "basic" Java RMI. Every class needed is on the server and client side before startup.

2) Server side download, class of method parameter downloaded. This is the minimum server side meaningful download, since the method parameter could be unknown on startup time provided that the parameter has a known interface at the server side.

3) Client side download, class of method return downloaded. This is the minimum client side meaningful download, since the method return could be unknown on startup time provided that the method return class has a known interface at the client side.

2.- The "basic" Java RMI: no download of classes defined for/related to the application
This alternative/configuration is included here just as an introduction to Java RMI as well as to introduce class download in the context of Java RMI. Also, the example used on every class download explanation/alternative configuration will be given here. Some technical concepts are not completely justified and/or with non complete reasons to exist, but it is not worse than the Java RMI explanations given by the Java developers/owners.
In fact, since Java version 1.5.xxx the server stub (in terms of Java RMI) is downloaded in the client side. This can be considered the minimum class download, since it's the server stub on the client side. Server stub download should be fully transparent (but it's not), because it's nothing to do with server or client functionality, it seems to be just an implementation requirement (for Java RMI). However, there are some lines of Java code that should appear to accomplish this task at the server side. On Java versions previous to 1.5.xxx the server stub had to be explicitly generated with rmic [7] and, also, it had to be explicitly downloaded if required/decided such a functionality for the application.
From a high level of abstraction point of view, Java RMI provides a facility to program a distributed Java based application, as explained in the Java RMI website [1]. More specifically, Java RMI provides a facility to make a method invocation on an object which is not on the local JVM. From this point of view, a "remote invocation" made by a client is, in fact, a reference to a remote object. As such, on the client side it has to be known "something" on the remote object. This is why the server interface has to be defined. As the first and most simple example, consider the following interface, in the file RemoteIface.java

 
/*  RemoteIface.java  */  
import java.rmi.*;  /* Remote class and RemoteException exception */
public interface RemoteIface extends Remote
{  /* The method to be invoked from other JVMs */   
   public byte[] sendThisBack(byte[] data) throws RemoteException; 
} 
Note that it's not enough to have a server interface, the server interface has to be "Remote Method Invocation enabled". This implies:
  a) The interface should extend Remote.
  b) The method/s should throw the exception RemoteException.
And both decisions/definitions seems to be naturally justified, since this interface has been defined in order to have a way on the client side to invoke/use a remote object. There may be as many "remote methods" as needed, and this is why it's important to remember that the object, not the method is remote (even when Java named RMI: Remote Method Invocation instead of ROR: Remote Object Reference, but this has other implications beyond the scope of this discussion...).

Given an interface for a server, the implementation should be no more than implementing the methods defined as "RMI enabled". In this document, the interface implementation will be given independently of the application which creates and makes this object available to other application/s. This decision makes easier to define/use/explain classes "RMI enabled" and, also, allows to explain different concepts on different places. The class implementing the interface RemoteIface given above would be in the file RemoteClass.java
 
/*  RemoteClass.java  */  
import java.rmi.*;  /* RemoteException exception */  
public class RemoteClass implements RemoteIface 
{ /* Constructor */   
  public RemoteClass() {}    

  /* The method to be invoked from other JVMs */
  public byte[] sendThisBack(byte[] data) throws RemoteException
  {
    System.out.println("Data back to client");
    return data;
  }
}
Note that except for the line
 importjava.rmi.*;
and the exception RemoteException, the implementation class of the interface does not difer from any implementation class given for an interface. The task for a distributed application programmer is, thus, simplified. As already noted, it's necessary an application which creates and makes available an object of this class to other applications (applications running on other JVMs). An object of this class is created as any other object, e.g.
   RemoteClass robject = new RemoteClass();
but making this object available to other applications is specifically related to Java RMI and, also to the Java RMI "evolution" (just to name it in some way). In terms of Java RMI, it is necessary to [2]
    a) Export the remote object to the Java RMI runtime.
    b) Register the remote object, making an association between a name     (string) and an object's stub.
And both tasks are directly related to Java RMI behavior. More specifically, an object can be created and exported to the Java RMI runtime with
   RemoteClass robject = new RemoteClass();
   RemoteIface riface  = (RemoteIface) UnicastRemoteObject.exportObject(robject, 0); 
where UnicastRemoteObject.exportObject makes, in fact, two important tasks [2]:
    a) Makes the object ready to receive incoming invocations.
    b) Returns an object's stub which is necessary on the client side.
It is necessary now to provide some way to the clients for obtaining the stub, which is known as "registering" the object under a name. Registration will be made in this example by using the method rebind provided by the class java.rmi.Naming. Now, an object can be created, exported, and registered with
   RemoteClass robject = new RemoteClass();
   RemoteIface riface  = (RemoteIface) UnicastRemoteObject.exportObject(robject, 0);
   Naming.rebind("remote", riface); 
i.e. the object is running and listening to incoming invocations and registered under the name "remote". Method rebind of class java.rmi.Naming requires a registry (in terms of Java RMI). The registry is provided by the rmiregistry command (external to the JVM). The rmiregistry process has to be running on the host where the server is running. This process is, in fact, the one that efectively registers the object, i.e. associates a name (string) to an object and provides the corresponding stub to the clients. A file StartRemoteObject.java can define a class with only a main method with the tasks explained above
/*  StartRemoteObject.java  */  
import java.rmi.server.UnicastRemoteObject;  /* exportObject */
import java.rmi.Naming;                      /* rebind */

public class StartRemoteObject
{
  public static void main (String args[])
  {
    try{
      /* Create ("start") the object which has the remote method */
      RemoteClass robject = new RemoteClass();

      /* Obtain something to export to te client side... the "stub"... */
      RemoteIface riface  = (RemoteIface) UnicastRemoteObject.exportObject(robject, 0);

      /* Register the object on the rmiregistry */
      Naming.rebind("remote", riface);
    } catch (Exception e){
      System.out.println("Hey, something is missing");
      e.printStackTrace();
      System.out.println(e.getMessage());
    }
  }
}
There is nothing else to program on the server side. Furthermore, the server is able to be running at this point, with a few steps/commands:
   a) Compile:                               $> javac *.java
   b) Run rmiregistry:                       $> rmiregistry &
   c) Run Java with StartRemoteObject:       $> java StartRemoteObject
And, after these commands, the server is running and waiting to be invoked from other JVMs. The server process is running until something like ^C, and the rmiregistry could be killed with "killall rmiregistry". Both tasks are recommended in order to restart everything again, once the client process is ready to run.
The server is complete, but some important concepts/statements deserve some explanations:
a) A little bit of history: The mechanism of registering using another process (independent from client and server, rmiregistry in Java RMI) could be rather strange for all those programmers that did not use/know RPC (Remote Procedure Call). For example: why not "publish" the object/method directly on a socket? Well, despite the possible choices, RPC (also defined by Sun, but some years earlier than Java) works just in this way: those procedures that can be remotely called have to be registered on the computer where they are implemented, by another process: the portmapper. It is not necessarilly a bad idea, it seems to be an idea that solved the problem for RPC and is just reused for RMI. In fact, Sun recognizes in [3] that Java RMI is just the way in which RPC is defined/used for Java.

b) The server stub download on the client side is not completely hidden on the server side. More specifically, the line

   RemoteIface riface = (RemoteIface) UnicastRemoteObject.exportObject(robject, 0); 
is strictly necessary, since it's where the server stub is generated. The original Java RMI (and now non existent, since Java 1.5.xxx) stub-skeleton [6] discussion will be avoided here, just to avoid some inconsistencies and, also, some wasting of time. However, it's clear that it is "natural" to think that having defined the class as extending Remote, exporting as well as handling the stub download should be handled automatically (i.e. hidden to the programmer). As an advance in Java 1.5.xxx, the skeleton is not necessary anymore as well as the static stub generation, which had to be made with rmic (rmi compiler) instead of being generated at run time. From the point of view of hiding details on the server side, RPC could be considered better: at least using rpcgen on Linux only the server code has to be programmed, i.e. the code for services/procedures to be remotely called. This behavior corresponds to the interface implementation, i.e. the RemoteClass class given in this document. The code of StartRemoteObject class in the example given in this document is automatically generated with rpcgen for RPC on Linux.

c) In [2], the object is registered with the method bind of class Registry. In the example given in this page, the Naming class is used and this class solves everything related to the rmiregistry. Using the method bind of class Registry requires to get a reference to the rmiregistry on the application, which is made in [2] by using the method getRegistry of LocateRegistry.

d) As a very specific detail, the rmiregistry should have access to the compiled interface, RemoteIface.class in this example. A priori, this requirement seems to be very restrictive for very large distributed applications, but it's not a problem for the little example given in this document, just start the rmiregistry on the directory contaning the server and, specifically, the compiled interface.

The server side is complete and running, the client should be programmed and the complete client/server example could be used. There should be some way to make an invocation on the remote object (i.e. the object not in the local JVM) on the client side. Furthermore, RMI has been defined to make invocations on remote objects as similar as possible to an invocation on a local object (i.e. an object on the JVM where the process/thread makes the invocation). More specifically, on the client, there should be code like

   ...
   RemoteIface remoteobj;
   ...
   buffer = new   byte[bufferlength];
   remoteobj.sendThisBack(buffer);   ... 
i.e. an object which is an implementation of the remoteIface interface (remote in this example) and an invocation on that object. Note that at this point the invocation is exactly the same as if the object remote would be local. As it might be expected, the remote object remoteobj is not obtained with a simple "new" on some class:
it's necessary to obtain a reference to an object in another JVM, the server stub. This task will be made in this document by using an URL and the rmiregistry. Basically, the server object (the one used "remotely") has been registered/accounted on a process running on the same computer as the JVM that contains it, i.e. has been registered with the rmiregistry. The rmiregistry has to be running on the same computer where the JVM containing the remote object is running (the server side). Thus, the client has to "lookup" (in Java RMI terms) for the object and this may be made by using an URL. Treated as an URL, the client needs to know the "Location" of the rmiregistry and, thus, the remote object itself. The rmiregistry uses a well known port (a TCP or UDP port) to register and lookup remote objects. Usually, it's the 1099, but it may be changed on rmiregistry startup. Assuming that the object has been successfully registered (with rmiregistry) on the server under the name "remote", the URL to use for remote object lookup could be
   //<hostname>:1099/remote 
where <hostname> should be replaced by the DNS domain name that could be solved to the server host on the client host. The way in which the client could obtain a reference to the object can be added to the previous example
   ...
   import java.rmi.Naming;  /* lookup(...) */
   ...
   RemoteIface remoteobj;
   remoteobj = (remoteIface) Naming.lookup("//localhost:1099/remote");
   ...
   buffer = new byte[bufferlength];
   remoteobj.sendThisBack(buffer);
   ... 
assuming that the server is running on the same computer, localhost. Class java.rmi.Naming solves (through the method lookup) the problem of binding a URL to a remote object already created and registered on the rmiregistry process under that URL. Basically, the method lookup asks to the rmiregistry running on the host and listening on the port indicated by the URL and returns a reference (the stub, in fact) to the object if the rmiregistry successfully returns such a reference to the object. The client can be completely defined right now on a file AskRemote.java
/*  AskRemote.java  */
import java.rmi.Naming;                 /* lookup */ 
import java.rmi.registry.Registry;      /* REGISTRY_PORT */ 
import java.net.MalformedURLException;  /* Exceptions... */ 
import java.rmi.NotBoundException; 
import java.rmi.RemoteException;  

public class AskRemote
{
  public static void main(String[] args)
  {
    /* Look for hostname and msg length in the command line */
    if (args.length != 1)
    {
      System.out.println("1 argument needed: (remote) hostname");
      System.exit(1);
    }
    try{
      String rname = "//" + args[0] + ":" + Registry.REGISTRY_PORT + "/remote";
      RemoteIface remoteobj; 
      remoteobj = (RemoteIface) Naming.lookup(rname);
      byte buffer[];
      int bufferlength = 100;
      buffer = new byte[bufferlength];
      remoteobj.sendThisBack(buffer);
      System.out.println("Done");
    } catch (MalformedURLException e) {
      e.printStackTrace();
    } catch (RemoteException e) {
      e.printStackTrace();
    } catch (NotBoundException e) {
      e.printStackTrace();
    }
    ...
  }
}
Where this client version adds two implementation details to the previous example:
    a) The hostname where the remote object is contained has to be given as a command line parameter to the application, args[0].
    b) Instead of using a hardcoded port (1099 in the previous example), it's used the value given by java.rmi.registry.Registry.REGISTRY_PORT to construct the URL for lookup.

This client code does not hide some details compared to RPC implemented on C under Linux, for example. The Java RMI programmer necessarilly has to make the URL, the lookup invocation, etc., while the C programmer (at least using rpcgen on Linux, for example) does not necessarilly have to engage on these details. Most of the times (if not always) all of these details are hiden by a "simple" call to a function like clnt_create(...).

So far, the client side can be considered complete. What about classes and class download? all that is needed on the client side seems to be
    a) RemoteIface.class (the compiled remoteIface.java)
    b) AskRemote.class (the compiled askRemote.java)
Well, yes and no. From the programmer point of view: yes. There is nothing else to program. However, there is one more necessary class (a .class file, in particular): the server stub, but it comes to the client as the object returned by the method lookup(). On the server side, the classes needed are
    a) RemoteIface.class (the compiled RemoteIface.java)
    b) RemoteClass.class (the compiled RemoteClass.java, which is an implementation for the interface RemoteIface.java)
    c) StartRemoteObject.class (the compiled StartRemoteObject.java)
Now, the complete client/server application is ready to run. Client and server Java RMI applications will be on different computers/hosts most (if not all) of the times. However, different JVMs are, in fact, different computers for Java applications even when running on the same computer/host. Also, having everything on a single computer simplifies debugging and, in this simple example, simplifies the running environment: just a few consoles/terminals on a computer. Binary/Java classes distribution becomes important because resembles the distribution on different computers. Remember to compile every .java file with

 
   $> javac *.java 
In this example, the server side, i.e. the binaries needed on the server side, will be in a directory named server
   $> mkdir server 
and should contain the files RemoteClass.class, RemoteIface.class, and StartRemoteObject.class, e.g.
   $> cp RemoteClass.class RemoteIface.class StartRemoteObject.class server/ 
The client side, i.e. the binaries needed on the client side, will be in a directory named client
   $> mkdir client 
and should contain the files RemoteIface.class, and AskRemote.class, e.g.
   $> cp RemoteIface.class AskRemote.class client/ 
The server should be started on it's own console with the sequence
   $> cd server
   $> rmiregistry &
   $> java StartRemoteObject 
and this console will not be available for anything else. Remember that rmiregistry as well as the JVM running StartRemoteObject should have access to the "server side" classes, those copied in the server directory. The client should be started on another console with the sequence
   $> cd client
   $> java AskRemote localhost 
In this case, on the client console will appear
   $> About to lookup for //localhost:1099/remote
   $> Done 
And in the server console will appear
   $> Data back to client 
The only difference if running the client on a different computer should be the command line argument to AskRemote, the DNS name of the computer where the server should be given. Furthermore, the server side should be reachable from the client side, including firewall configuration and/or rules to/from port 1099.

The basic Java RMI example is complete now, and the server stub is downloaded at runtime by the "Java RMI environment". The complete code can be found in nodonload.tar, including the client and server side directories with the corresponding .class files and some scripts to run the applications.

3.- Download at the server side: compute server and mobile agent examples
The facility for class download on the server side is very well exemplified in [3]. In fact, the idea is general enough for allowing some kind of processing on a set of "servers" reachable from some computer. This last example is given in [4] as mobile agents, and code example can be obtained at [5].

3.1.- The first example
In the first (and simple) example, it will be assumed a Java interface defined on a file ObjIface.java as

/*  ObjIface.java  */
import java.io.Serializable;

public interface ObjIface extends Serializable 
{
  int justToSee();
} 
i.e. a method returning an int value. Note that this interface (and the class which implements it) has only one requirement: the method justToSee(). Note, also, that the interface should extend class Serializable, since the objects should be able to be trasferred on the network. The server interface remains extending Remote and could be defined in the file RemoteIface2.java as
 
/*  RemoteIface2.java  */
import java.rmi.*;  /* Remote class and RemoteException exception */  

public interface RemoteIface2 extends Remote 
{ 
  /* The method to be invoked from other JVMs */
  public int recvAndExec(ObjIface obj) throws RemoteException; 
} 
It is expected that the server receive an object of some implementation of ObjIface, invokes the method justToSee() and returns the int value returned by the method justToSee(). An implementation for the server interface could be on the file RemoteClass2.java as
 
/*  RemoteClass2.java  */  
import java.rmi.*;  /* RemoteException exception */  

public class RemoteClass2 implements RemoteIface2 
{ 
  /* Constructor */   
  public RemoteClass2() {}    

  /* The method to be invoked from other JVMs */ 
  public int recvAndExec(ObjIface obj) throws RemoteException   
  { 
    System.out.println("Invoking the received object method");
    return obj.justToSee();
  }
}
The server interface RemoteIface2 is needed on the client side in order to make a remote method invocation. The interface ObjIface is needed on the server side in order to receive an object without an available local class and, also, make an invocation on the received object. The implementation of the interface will be downloaded at runtime in the server side. The server side should be complete with a class which creates, exports, and registers an object of class RemoteClass2. In fact, this is exactly done in the previous StartRemoteObject class, and it will be used "directly" as the StartRemoteObject2.java, replacing RemoteClass by RemoteClass2 and RemoteIface by RemoteIface2
/*  StartRemoteObject2.java  */  
import java.rmi.server.UnicastRemoteObject;  /* exportObject */ 
import java.rmi.Naming;                      /* rebind */  

public class StartRemoteObject2 
{ 
  public static void main (String args[]) 
  { 
    try{
      /* Create ("start") the object which has the remote method */
      RemoteClass2 robject = new RemoteClass2();

      /* Obtain something to export to te client side... the "stub"... */
      RemoteIface2 riface  = (RemoteIface2) UnicastRemoteObject.exportObject(robject, 0);

      /* Register the object on the rmiregistry */
      Naming.rebind("remote", riface);
    } catch (Exception e){
      System.out.println("Hey, something is missing");
      e.printStackTrace();
      System.out.println(e.getMessage());
    }
  }
}
However, it's clear that something has to be done for the class downloading at the server side. The first technical detail to be solved at the server side is to create a security manager (in terms of Java and Java RMI documents as in [3]) with some security policy. This can be made in the StartRemoteObject2 class [3] or, directly, on the JVM startup. The security policy can be defined in a java.policy file like
 
grant 
{ 
  permission java.net.SocketPermission "*:1099", "connect, accept, resolve";
  permission java.net.SocketPermission "*:1024-65535", "connect, accept";
  permission java.net.SocketPermission "*:80", "connect"; 
}; 
which could be considered too permissive, but good enough for this example. This security policy could be given to the JVM at startup with the option
   -Djava.security.policy=java.policy 
and the security manager could be created at startup with the option
   -Djava.security.manager 
The security manager as well as the security policy "only" allow dynamic class download to the JVM. The other technical detail at the server side is to provide the necessary information to look for classes to be downloaded. More specifically, an URL has to be given to the Java RMI environment for class download with the option -Djava.rmi.server.codebase as in
    -Djava.rmi.server.codebase=http://localhost/distrclasses/ 
where it's assumed that there is a local http server which has classes binaries under the directory distrclasses. Now, the server side is complete and ready to run assuming the rmiregistry is running and some implementation of interface objIface is reachable through a local http server.

The client side still remains to be programmed. However, the client side is similar to the previous one (without dynamic class download). On the client side, it's expected to find an implementation for the interface ObjIface, and it could be on the file ObjClass.java as

/*  ObjClass.java  */  
public class ObjClass implements ObjIface 
{   
  private int priv_id; 

  public ObjClass(int id)
  {
    priv_id = id;
  } 

  public int justToSee() 
  {
    return priv_id;
  }
}
where the constructor is used to give an int value which is later returned on each justToSee() invocation. The client could be very similar to AskRemote, but creates and sends as the remote method invocation parameter an object of class ObjClass. An example of client (a rather hardcoded one) could be on the file AskRemote2.java as
/*  AskRemote2.java  */  
import java.rmi.Naming;                 /* lookup */ 
import java.rmi.registry.Registry;      /* REGISTRY_PORT */ 
import java.net.MalformedURLException;  /* Exceptions... */ 
import java.rmi.NotBoundException; 
import java.rmi.RemoteException;  

public class AskRemote2 
{ 
  public static void main(String[] args)
  {
    /* Look for hostname and msg length in the command line */
    if (args.length != 1)
    {
      System.out.println("1 argument needed: (remote) hostname");
      System.exit(1);
    }
    try { 
      String rname = "//" + args[0] + ":" + Registry.REGISTRY_PORT + "/remote";
      RemoteIface2 remoteobj;
      remoteobj = (RemoteIface2) Naming.lookup(rname); 

      objClass oneobj = new objClass(1); 
      int retval; 
      retval = remoteobj.recvAndExec(oneobj); 
      System.out.println("Done with return value = " + retval);
    } catch (MalformedURLException e) {
      e.printStackTrace();
    } catch (RemoteException e) {
      e.printStackTrace();
    } catch (NotBoundException e) {
      e.printStackTrace();
    }
  }
} 
Client as well as server side code should compile with
   $> javac ObjIface.java RemoteIface2.java RemoteClass2.java StartRemoteObject2.java\   
      ObjClass.java AskRemote2.java 
As in the example without class download, the binaries needed on the server side, will be in a directory named server2
   $> mkdir server2 
and should contain the files RemoteClass2.class, RemoteIface2.class, StartRemoteObject.class, ObjIface.class, and the policy file java.policy, e.g.
   $> cp RemoteClass2.class RemoteIface2.class StartRemoteObject2.class ObjIface.class\
      java.policy server2/ 
The binaries needed on the client side will be in a directory named client
   $> mkdir client 
and should contain the files RemoteIface2.class, AskRemote2.class, and ObjClass.class, e.g.
   $> cp RemoteIface2.class AskRemote2.class ObjIface.class ObjClass.class client/ 
And, also on the client side, the ObjClass.class should be stored on the directory served by the http server as
   http://localhost/distrclasses/ 
The server should be started on it's own console with the sequence
   $> cd server2
   $> rmiregistry &
   $> java -Djava.security.policy=java.policy -Djava.security.manager \
      -Djava.rmi.server.codebase=http://localhost/distrclasses/ StartRemoteObject2 
and this console will not be available for anything else. Remember that rmiregistry as well as the JVM running StartRemoteObject2 should have access to the "server side" classes, those copied in the server directory. Remember to copy the client side class ObjClass.class in the proper directory, and the client should be started on another console with the sequence
   $> cd client
   $> java AskRemote2 localhost 
In this case, on the client console will appear
   $> Done with return value = 1 
And in the server console will appear
   $> Invoking the received object method 
As another, more general example, the class file ObjClass.class could be stored at the site
   http://<somesite>
and the server should be started with
   $> cd server2
   $> rmiregistry &
   $> java -Djava.security.policy=java.policy -Djava.security.manager \
      -Djava.rmi.server.codebase=http://<somesite> StartRemoteObject2 
Server and client should produce the same output, but the class ObjClass is downloaded from the site
    http://<somesite> 
The complete code can be found in downloadatserver.tar, including the client and server side directories with the corresponding .class files and some scripts to run the applications. This code can be directly used with the .class file already stored at a local http server or, changing the codebase property, with the .class file in the site http://<somesite> (in this last case, nothing else has to be done).

This first example of class download in the server side shows useful details:
    a) Every class that extending java.io.Serializable can be downloaded at runtime.
    b) Code (methods) in the class to be downloaded can be as complex as needed, i.e. it is expected that methods complexity do not complicate a "class serialization" which allows such a class to be downloaded at runtime.
    c) rmiregistry as well as the server side only have to know the class interface at startup and the place to look for unknown class/es at runtime.
Furthermore, this first simple/ied example can be used to show how to take advantage of a big host as a compute server where users can submit their jobs. Instead of installing binaries statically, an object capable of executing at the server side may be sent with Java RMI is. The server now receive as a parameter an object whose class defines some method that returns, in general, an object. Again, the server itself will be in charge of invoking the object's method locally (usually, as the first and only task).

3.2.- The second example: a compute server
In fact, the first example of class dowload should be enhanced just for the class received at the server side to handle other class/es. In particular, the method/s should be able to return any serializable object. An object "serializability" is specified as in the previous ObjIface, i.e. extending the class Serializable. The second example of dynamic class download at the server side will have the classes:
    a) TaskClass: the class donwloaded at the server side which has one public method: run(). This class is similar to ObjClass, but the method run() would return any serializable class, not only an integer value, which is returned by the justToSee() method of class ObjClass.
    b) RetClass: the serializable class returned by method run() of class TaskClass. RetClass would be the generalization of "int" in the previous example, which is returned by method justToSee() in the previous example.
Thus, two class interfaces are needed at the server side related to classses to be downloaded at runtime

 
/*  TaskIface.java  */  
import java.io.Serializable;  

public interface TaskIface extends Serializable 
{
  RetIface run(); 
}  

/*  RetIface.java  */  
import java.io.Serializable;  

public interface RetIface extends Serializable 
{ 
  /* Needed methods here, used in the implementation of run() */ 
} 
Interface implementations should be defined (and useful) at the client side and also available via http o ftp server to the server side
 
/*  RetClass.java  */  
public class RetClass implements RetIface 
{
  /* Implementations of the interface methods */ 
  /* Other methods here, not used in the implementation of run() */ 
}  

/*  TaskClass.java  */  
public class TaskClass implements TaskIface 
{ 
  public RetIface run()
  { 
    RetIface priv_obj;           /* There should be an object to return... */
    priv_obj = new RetClass(); 

    /* Some code here, depending on the application */     

    return priv_obj;
  }
} 
At this point, the classes to be downloaded at runtime in the server side are completely defined (even when those classes do not seem to make any useful work). The rest of the server side is as expected for a compute server: the RMI "classical" code combined with the tasks which return some serializable object
 
/*  CompServerIface.java  */  
import java.rmi.*;  /* Remote class and RemoteException exception */  

public interface CompServerIface extends Remote 
{   
  /* The method to be invoked from other JVMs */   
  public RetIface compute(TaskIface task) throws RemoteException; 
}  

/*  CompServerClass.java  */  
import java.rmi.*;  /* RemoteException exception */  

public class CompServerClass implements CompServerIface 
{   
  /* Constructor */ 
  public CompServerClass() {}  

  /* The method to be invoked from other JVMs */ 
  public RetIface compute(TaskIface task) throws RemoteException   
  { 
    System.out.println("Running received task...");
    return task.run();
  }
} 
There is only one more class needed at the server side, which creates, exports, and registers the server, "as usual" in RMI
 
/*  StartCompServer.java  */  
import java.rmi.server.UnicastRemoteObject;  /* exportObject */ 
import java.rmi.Naming; /* rebind */  

public class StartCompServer 
{   
  public static void main (String args[])
  {  
    try{  
      /* Create ("start") the object which has the remote method */
      CompServerClass robject = new CompServerClass();

      /* Obtain something to export to te client side... the "stub"... */ 
      CompServerIface riface = (CompServerIface) UnicastRemoteObject.exportObject(robject, 0); 

      /* Register the object on the rmiregistry */
      Naming.rebind("remote", riface);
    } catch (Exception e){
      System.out.println("Hey, something is missing");
      e.printStackTrace();
      System.out.println(e.getMessage());
    }
  } 
} 
The files (classes and interfaces) needed at the server side are
  CompServerIface.class  CompServerClass.class  StartCompServer.class  RetIface.class  TaskIface.class 
and, also, the classes RetClass.class and TaskClass.class should be also available via http o ftp server to the server side.

The client side is almos complete, since TaskClass and RetClass are already defined. There is only one more class needed at the client side, which creates and sends a task to the server, "as usual" in RMI

 
/*  SendTask.java  */  
import java.rmi.Naming;  /* lookup */ 
import java.rmi.registry.Registry;  /* REGISTRY_PORT */ 
import java.net.MalformedURLException;  /* Exceptions... */ 
import java.rmi.NotBoundException; 
import java.rmi.RemoteException;  

public class SendTask 
{   
  public static void main(String[] args) 
  { 
    /* Look for hostname and msg length in the command line */ 
    if (args.length != 1)
    {  
      System.out.println("1 argument needed: (remote) hostname");
      System.exit(1);
    }
    try {
      String rname = "//" + args[0] + ":" + Registry.REGISTRY_PORT + "/remote";
      CompServerIface remoteobj; 
      remoteobj = (CompServerIface) Naming.lookup(rname); 
      TaskClass oneTask = new TaskClass(); 
      RetClass retval; 
      retval = (RetClass) remoteobj.compute(oneTask); 
      System.out.println("Done with return value = " + retval);
    } catch (MalformedURLException e) {
      e.printStackTrace();
    } catch (RemoteException e) {
      e.printStackTrace();
    } catch (NotBoundException e) {
      e.printStackTrace();
    }
  } 
} 
The files (classes and interfaces) needed at the client side are
 
  CompServerIface.class  TaskIface.class  TaskClass.class  RetIface.class  RetClass.class  SendTask.class 
Now, the server is started as in the first example of this section:
   java -Djava.security.policy=java.policy -Djava.security.manager \
        -Djava.rmi.server.codebase=http://localhost/distrclasses/ \
        StartCompServer 
and this console will not be available for anything else. Remember that rmiregistry as well as the JVM running StartCompServer should have access to the "server side" classes, those copied in the server directory as well as the java.policy file. Remember to make available the classes to be dynamically downloaded (TaskClass.class and RetClass.class). The client should be started on another console with
   java SendTask localhost 
The compute server Java RMI example is complete now, and the server downloads at runtime two classes: TaskClass and RetClass via the "Java RMI environment". The complete code can be found in downloadatserver_compserver.tar, including the client and server side directories with the corresponding .class files and some scripts to run the applications. Classes TaskClass.class and RetClass.class have also been uploaded at
   http://://<somesite>  
thus, the server could be started with
   java -Djava.security.policy=java.policy -Djava.security.manager \
        -Djava.rmi.server.codebase=http://://<somesite> \
        StartCompServer 
This compute server differs from that given in [3] in
    a) The object returned by the method run() of class TaskClass is serializable (RetClass class extends Serializable). Furthermore, it seems to be reasonable that every object to be transferred on the network is an instance of a serializable class (i.e. a class which extends Serializable) instead of just being an Object instance as in [3].
    b) The compute server do not extends UnicastRemoteObject. There are not any reasons given in [3] by which the server class should extend UnicastRemoteObject.
    c) The server class CompServerClass is just a server, it does not have a main() method. Instead, there is one more class, StartCompServer, with only one method, main(), which creates, exports, and registers the server. Also, the way in which StartCompServer creates, exports, and registers the server is a little bit different from that given in [3].

3.3.- The third example: mobile agents
The first version of mobile agents in this document will be similar to that described in [4] (code in [5]), which do not have class download at all. Basically, mobile agent allows code and data to migrate from a computer to another on a decision made on the mobile agent itself. There are many specific efforts to implement mobile agents, and the examples in this document are given in terms of Java RMI, which lets to "simulate" the mobile agent behavior rather easily. The J ava RMI server side is almost the same, with an interface as RemoteIface.java

 
/*  RemoteIface.java  */  
import java.rmi.*;  /* Remote class and RemoteException exception */  

public interface RemoteIface extends Remote 
{
  /* The method to be invoked from other JVMs */
  public void receive(AgentClass agent) throws RemoteException; 
} 
i.e. the server should receive an object of class AgentClass, which will simulate the mobile agent on the distributed system. The implementation to this class is rather simple: just invoke (locally) a method like run() on the received object, which should be defined and implemented in class AgentClass. Thus, the implementation of RemoteIface could be as simple as this RemoteClass, in the file RemoteClass.java
 
/*  RemoteClass.java  */  
import java.rmi.*;  /* RemoteException exception */  

public class RemoteClass implements RemoteIface 
{   
  /* Constructor */ 
  public RemoteClass() {}

  /* The method to be invoked from other JVMs */ 
  public void receive(AgentClass agent) throws RemoteException 
  { 
    System.out.println("Agent received"); 
    agent.run();
  } 
} 
The class which creates, exports, and registers an object of RemoteClass is also known at this point of this document, in the file StartRemoteObject.java
 
/*  StartRemoteObject.java  */  
import java.rmi.server.UnicastRemoteObject;  /* exportObject */ 
import java.rmi.Naming;  /* rebind */  

public class StartRemoteObject 
{
  public static void main (String args[]) 
  { 
    /* There should be one command line param.: the name to register */ 
    if (args.length != 1) 
    { 
      System.out.println("1 argument needed: name to register");
      System.exit(1);
    } 
    try{   
      /* Create ("start") the object which has the remote method */ 
      RemoteClass robject = new RemoteClass();  

      /* Obtain something to export to te client side... the "stub"... */ 
      RemoteIface riface  = (RemoteIface) UnicastRemoteObject.exportObject(robject, 0);

      /* Register the object on the rmiregistry */ 
      Naming.rebind(args[0], riface);
    } catch (Exception e){
      System.out.println("Hey, something is missing");
      e.printStackTrace(); 
      System.out.println(e.getMessage());
    }
  }
} 
The only difference of this StartRemoteObject taking into account previous ones, is that the name to be registered is not hardcoded but is received as a command line parameter. Besides being a little bit more general, receiving the name to be registered as a command line parameter is necessary in this example in order to have several JVMs on the same computer, and each JVM-StartRemoteObject registers a (unique) name. In other words, there will be several "server side objects", and each one of them "represents" a host in a distributed system. Thus, there will be only one rmiregistry on the computer and several JVMs, where each JVM will contain a Java RMI server and the server will be registered with a unique name (the rmiregistry will complain otherwise).

Concepts behind this code were already clear before starting the mobile agent example. The "new" ideas (if there is something new at all) are found in the implementation of class AgentClass

 
/*  AgentClass.java  */  
import java.io.Serializable;  /* To implement... */ 
import java.rmi.Naming;  /* lookup */ 
import java.rmi.registry.Registry;  /* REGISTRY_PORT */  
import java.util.Vector;  

public class AgentClass implements Serializable 
{
  private Vector<String> hostList; // List of hostnames to go to private int hostIndex;
  // Host to visit next (from hostList) 

  /*******************************************************/   
  /* Assing the list of hostnames at agent creation */   
  public AgentClass(Vector<String> hl)
  { 
    hostList  = hl; 
    hostIndex = 0;
  }  
  /*******************************************************/ 
  /* The code to run on each host */ 
  public void run() 
  {
    System.out.println("Agent running...");  

    // Local processing should be here 
    try { Thread.sleep(3000);} catch (Exception e) {}      

    // Migrate mobile agent or stop running 
    if (hostIndex < hostList.size()) 
    {  
      // There is at least one more host to run on 
      String nextHost = (String) hostList.elementAt(hostIndex);   
      String rname = "//" + nextHost + ":" + Registry.REGISTRY_PORT + "/host" + hostIndex;
      hostIndex++; 
      System.out.println("About to lookup for " + rname); 
      try {
        RemoteIface remoteHost;
        remoteHost = (RemoteIface) Naming.lookup(rname); 
        remoteHost.receive(this); 
      } catch (Exception e) {
        System.out.println("Exception on lookup " + e);
      } 
    }
    else
    {
      // There is nothing else to do 
      System.out.println("No more migrations for this Agent"); 
    }
  }
} 
There are two important details in this class:
      a) The agent carries everything it needs to run on every host and, also, to migrate from host to host. Vector hostList contains every hostname on which the agent has to run, and the name under which the servers are registered are known a priori by the mobile agent (and should be agreed on the server side). Furthermore, the integer value hostIndex is used to point to the next host to visit as well as to control the end of the mobile agent run/migrations.
      b) The agent itself decides how, when, and where to migrate. Most of these tasks have already been explained before, except for the line
   remoteHost.receive(this); 
which implies that the mobile agent is sendind itself to the server side. Note that this line implies that the mobile agent loses the thread of control by the semantic of a Java RMI, which is the same as that of an invocation: control is returned only when the invocations ends. The server is just responsible for receiving the agent, which includes the invocation to method run() on the agent, since this implies that the agent continues "alive".

At this point, there is only one more task to do: a class that creates an agent and "starts" the agent running. This class could be defined in a file StartAgent.java as

 
/*  StartAgent.java  */  
import java.util.Vector;  

public class StartAgent 
{ 
  public static void main(String[] args)
  { 
    /* Look for hostname and msg length in the command line */ 
    if (args.length == 0)
    { 
      System.out.println("Command line parameters needed: hosts to run on");
      System.exit(1);
    }
    Vector<String> hostList = new Vector<String>();
    for (int i = 0; i < args.length; i++)
      hostList.addElement((String) args[i]); 

    AgentClass agent = new AgentClass(hostList); 
    agent.run();
  } 
} 
There is no more than the creation and the initial invocation to run() to an object of class AgentClass. This is a little bit different from the approach in [4], where the client looks for and sends the agent to the first server. Summarizing, classes and distributed application startup:
      a) Classes needed on the server side are
            AgentClass.class
            RemoteClass.class
            RemoteIface.class
            StartRemoteObject.class
i.e. the "three classical" classes for the server side (RemoteClass, RemoteIface, and StartRemoteObject) and the mobile agent class, AgentClass (there is not dynamic class download in this example).
      b) Classes needed on the client side are
            AgentClass.class
            RemoteIface.class
            StartAgent.class
i.e. the "three classical" classes for the client side without dynamic class download.
      c) Running several servers on a host is possible provided that the rmiregistry is running and every server registers a unique name. Starting two servers on its own console could be like
   $> cd <server side code>
   $> rmiregistry &
   $> java StartRemoteObject host0
in another terminal
   $> cd <server side code>
   $> java StartRemoteObject host1 
and, in fact, these last two commands could be repeated as many times as servers needed to be running (and host2, host3, ... hostn should be used as the names to register on each subsequent server).
      d) The client side is simple: just give the hostnames on which the mobile agent has to run. For two servers runnign on the local host, the commands on a terminal should be
   $> cd <client side code>
   $> java StartAgent localhost localhost 
If the servers are running on different hosts, the name registered on each host could be the same and the hardcoded line with the string to make the lookup on AgentClass.java could be
   String rname = "//" + nextHost + ":" + Registry.REGISTRY_PORT + "/server";
but it is not essentially different from previous code.

Before discussing a mobile agent with dynamic class download, several conceptual and implementation differences of this mobile agent from the approach proposed in [3] deserve some discussion:
      a) The approach in [3] is similar to that in [4] for the client side: the client looks for a server and sends a mobile agent. The client in this document just creates and "startups" an agent and the agent takes every decision on runtime, including migration and running several code on other hosts.
      b) The approach in [3] proposes to create a thread in every server in order to run the mobile agent locally. The server proposed in this document just runs the mobile agent as soon as the mobile agent is received. The advantages of the approach in this document are
            b.1) No explicit creation and handling of threads for mobile agents, simpler code.
            b.2) It's clear that mobile agents are simulated using Java RMI, i.e. the method invoked remotely does not complete until returned. The client ends running only when the run() method returns, which happens after the mobile agent has ran and returned from every server. There is an implicit "call chain" of remoteHost.receive(this) with their corresponding return executed before the client ends.
And there are some disadvantages too, at least a single call to remoteHost.receive(this) does not end until the agent.run() ends. If a thread is created inside the receive(...) method which will invoke agent.run(), then the receive(...) method ends and there is not pending return until every method returns, as explained in b.2). However, there will be as many threads waiting for results as pending returns. Also, on the Sun JVM implementations every remote method is executed by an independent thread, so the approach in this document is basically the same as that of [3] without using explicit threads.

The complete code for the mobile agent without class download can be found in agent_nodownload.tar, including the client and server side directories with the corresponding .class files and some scripts and/or instructions to run the applications. Dynamic class download lets the mobile agent to run code obtained at runtime instead of having the code in advance/statically. Moreover, dynamic class download allows running completely different code on each host where the agent is received for two reasons:
      a) The code could change from one download to the next in time. Thus, while the first host downloads a .class file at time t1, the second host downloads the same .class file at time t2, and the .class file could be changed/updated between t1 and t2.
      b) Each JVM downloads code from the site/server independently of every other JVM, since each JVM is "instructed" to download code via the option -Djava.rmi.server.codebase=... Thus, while a JVM could download classes from localhost, while others from some "public" ftp or http server. Of course, configuring different sources for diferent hosts could produce other issues and/or complex maintenance policies.

Dynamic class download for the mobile agent given in the example is rather straightforward. The class to be dynamically downladed is, clearly AgentClass. Class AgentClass should have an interface just like the one given in AgentIface.java

 
/*  AgentIface.java  */  
import java.io.Serializable;  /* To extend... */  

public interface AgentIface extends Serializable 
{ 
  /* The method to invoke locally at each host */ 
  public void run(); 
} 
Thus, the AgentClass should be changed to implement this interface, as in the "new" AgentClass.java
 
/*  AgentClass.java  */  
// import java.io.Serializable;  /* To implement... */ 
import java.rmi.Naming;  /* lookup */ 
import java.rmi.registry.Registry;  /* REGISTRY_PORT */ 
import java.util.Vector;  

public class AgentClass implements AgentIface 
{
  private Vector<String> hostList;  // List of hostnames to go to 
  private int hostIndex;  // Host to visit next (from hostList)

  /*******************************************************/
  /* Assing the list of hostnames at agent creation */ 
  public AgentClass(Vector<String> hl) 
  { 
    hostList  = hl; 
    hostIndex = 0; 
  } 

  /*******************************************************/ 
  /* The code to run on each host */ 
  public void run()
  { 
    System.out.println("Agent running..."); 

    // Local processing should be here 
    try {Thread.sleep(3000);} catch (Exception e) {} 

    // Migrate mobile agent or stop running 
    if (hostIndex < hostList.size()) 
    { // There is at least one more host to run on   
      String nextHost = (String) hostList.elementAt(hostIndex);
      String rname = "//" + nextHost + ":" + Registry.REGISTRY_PORT + "/host" + hostIndex; 
      hostIndex++;  
      System.out.println("About to lookup for " + rname);  

      try { 
        RemoteIface remoteHost; 
        remoteHost = (RemoteIface) Naming.lookup(rname); 
        remoteHost.receive(this); 
      } catch (Exception e) { 
        System.out.println("Exception on lookup " + e);
      } 
    } 
    else 
    { // There is nothing else to do 
      System.out.println("No more migrations for this Agent"); 
    } 
  } 
} 
where
   // import java.io.Serializable;  /* To implement... */ 
   ...   
   public class AgentClass implements AgentIface 
are the only two lines changed to the "previous" AgentClass.java, because the mobile agent does not change and, thus, the code remains almost the same. Code at the server side is a little bit different, since it should use the interface, as in the "new" RemoteIface.java and RemoteClass.java
 
/*  RemoteIface.java  */  
import java.rmi.*; /* Remote class and RemoteException exception */ 

public interface RemoteIface extends Remote 
{ 
  /* The method to be invoked from other JVMs */ 
  public void receive(AgentIface agent) throws RemoteException;
}

/*  RemoteClass.java  */  
import java.rmi.*; /* RemoteException exception */  

public class RemoteClass implements RemoteIface 
{ 
  /* Constructor */ 
  public RemoteClass() {}
 
  /* The method to be invoked from other JVMs */ 
  public void receive(AgentIface agent) throws RemoteException 
  { 
    System.out.println("Agent received"); 
    agent.run(); 
  }
} 
Classes StartRemoteObject and StartAgent can be used without changes taking into account the example above, i.e. (copied here for the sake of completeness)
 
/*  StartRemoteObject.java  */  
import java.rmi.server.UnicastRemoteObject;  /* exportObject */ 
import java.rmi.Naming;  /* rebind */  

public class StartRemoteObject 
{ 
  public static void main (String args[]) 
  { 
    /* There should be one command line param.: the name to register */ 
    if (args.length != 1) 
    { 
      System.out.println("1 argument needed: name to register"); 
      System.exit(1); 
    } 
    try{
      /* Create ("start") the object which has the remote method */ 
      RemoteClass robject = new RemoteClass(); 
      /* Obtain something to export to te client side... the "stub"... */ 
      RemoteIface riface  = (RemoteIface) UnicastRemoteObject.exportObject(robject, 0); 

      /* Register the object on the rmiregistry */ 
      Naming.rebind(args[0], riface); 
    } catch (Exception e) { 
      System.out.println("Hey, something is missing"); 
      e.printStackTrace(); 
      System.out.println(e.getMessage()); 
    } 
  } 
}  

/*  StartAgent.java  */  
import java.util.Vector;  
public class StartAgent 
{ 
  public static void main(String[] args) 
  { 
    /* Look for hostname and msg length in the command line */ 
    if (args.length == 0) 
    { 
      System.out.println("Command line parameters needed: hosts to run on"); 
      System.exit(1); 
    } 
    Vector<String> hostList = new Vector<String>(); 
    for (int i = 0; i < args.length; i++) 
      hostList.addElement((String) args[i]); 

    AgentClass agent = new AgentClass(hostList); 
    agent.run(); 
  } 
} 
There are minor differences in running the application at the server side (all of these details have been seen in a previous section of this document, but repeated here for the sake of completeness):
    a) The JVM should have a security manager, as that created at JVM startup with the option -Djava.security.manager
    b) The security manager needs a policy file just like the file java.policy containing
     grant 
     { 
       permission java.net.SocketPermission "*:1099", "connect, accept, resolve"; 
       permission java.net.SocketPermission "*:1024-65535", "connect, accept"; 
       permission java.net.SocketPermission "*:80", "connect";
     }; 
and given to the security manager at startup with the option -Djava.security.policy=java.policy
    c) An URL has to be given to the Java RMI environment for dynamic class download with the option -Djava.rmi.server.codebase as in -Djava.rmi.server.codebase=http://localhost/distrclasses/ where it's assumed that there is a local http server which has classes binaries under the directory distrclasses. In this example, there is only one class to be dynamically downladed: AgentClass.class.

Summarizing classes, files, and distributed application startup:
    a) Classes and files needed on the server side are
        AgentIface.class
        RemoteClass.class
        RemoteIface.class
        StartRemoteObject.class
        java.policy
i.e. the "classical" classes for the server side with dynamic class download and the policy file for the JVM security manager.
    b) Classes needed on the client side are
        AgentClass.class
        AgentIface.class
        RemoteIface.class
        StartAgent.class
i.e. the "classical" classes for the client side without dynamic class download (at server side).
    c) Running several servers on a host is possible provided that the rmiregistry is running and every server registers a unique name. Starting two servers on its own console could be like

   $> cd <server side code>
   $> rmiregistry &
   $> java -Djava.security.policy=java.policy -Djava.security.manager \
      -Djava.rmi.server.codebase=http://localhost/distrclasses/ \
      StartRemoteObject host0 
on another terminal
   $> cd <server side code>
   $> java -Djava.security.policy=java.policy -Djava.security.manager \
      -Djava.rmi.server.codebase=http://localhost/distrclasses/ \
      StartRemoteObject host1 
and, in fact these last two commands could be repeated as many times as servers needed to be running (and host2, host3, ..., hostn should be used as the names to register on each subsequent server). It is assumed that every class to be dynamically downloaded is available at http://localhost/distrclasses/
    d) The client side is simple: just give the hostnames on which the mobile agent has to run. For two servers running on the local host, the commands on a terminal should be
   $> cd <client side code>
   $> java StartAgent localhost localhost 

The complete code for the mobile agent with class download can be found in agent_downloadathost.tar, including the client and server side directories with the corresponding .class files and some scripts and/or instructions to run the applications.

The first mobile agent example (without dynamic class download) is just a "plain" example for RMI with some minor specific details: the agent looks for the server and sends itself as a parameter of the remote method invocation. The secont mobile agent example (with dynamic class download) just adds the dynamic class download configuration. Note that most of the differences of the mobile agent with class download taking into account the mobile agent without class download are at configuration level. Furthermore, the code for every class and new code is straightforward.

References
[1] Remote Method Invocation Home
http://java.sun.com/javase/technologies/core/basic/rmi/index.jsp

[2] Getting Started Using Java RMI
http://java.sun.com/j2se/1.5.0/docs/guide/rmi/hello/hello-world.html

[3] Remote Method Invocation - Distributed Computing for Java
http://java.sun.com/javase/technologies/core/basic/rmi/whitepaper/index.jsp

[4] M. L. Liu, Distributed Computing: Principles and Applications,
ISBN-10 0201796449, Addison Wesley, 2003.

[5] M. L. Liu, Distributed Computing, a textbook
http://users.csc.calpoly.edu/~mliu/book/index.html

[6] jGuru: Remote Method Invocation (RMI)
http://java.sun.com/developer/onlineTraining/rmi/RMI.html

[7] jGuru: Remote Method Invocation (RMI)
http://java.sun.com/developer/onlineTraining/rmi/

Report bugs/comments: ftinetti @ gmail . com (remove whitespaces), with subject "rmi & download"