Intro

I've been fooling around with RMI today for fun and learning and got some serious headbanging when I couldn't get any of the examples I have in books by respected authors to work with JDK 1.5 (a.k.a. Tiger). All tutorials and examples on RMI say you should run rmic on the serverside objects you register in order to create stub and skeleton classes.

Yet if you do this with the 1.5 version of rmic you will

  1. not get a Skeleton class
  2. your server will not run, with an obscure error that the stub class (which IS generated) can not be found

Some serious delving into the JDK release notes told me that the skeleton class is indeed no longer created by rmic using the default options. It also hinted at the stub classes now being created by the JVM on the fly when you run the server and client applications. From this I theorised that my fault had been following the printed examples (all written for J2SDK 1.4)...

Time to put theory to the test

I deleted the generated _Stub class, crossed fingers, and started my server. Surprise surprise, it worked just fine. Next came the client (for the occasion moved to a different directory). It too worked as expected and called the server to get a response which arrived and was printed.

Now for some code :D

Step 1: define the remote interface

This can be any interface at all, except it MUST extend java.rmi.Remote. The only additional condition is that all method parameters and return types MUST be serializable (if not the data can't be flattened over the network, a logical restriction).

import java.rmi.*;

public interface SomeRemoteInterface extends Remote
{
	public String sayHi() throws RemoteException;
}

Step 2: define the server class

There are many ways to accomplish this, here is a simple yet effective solution for a small server.
The class you bind to the RMI registry MUST have a no-argument constructor which throws a RemoteException, this constructor may be empty.

import java.rmi.*;
import java.rmi.server.*;

public class RemoteServer extends UnicastRemoteObject implements SomeRemoteInterface
{
	public String sayHello()
	{
		return "Server says Hi";
	}
		
	public RemoteServer() throws RemoteException {}

	public static void main(String[] args) throws Exception
	{
		Naming.rebind("Hello", new RemoteServer());
	}
}

Step 3: the client

import java.rmi.*;

public class RemoteClient 
{
	public static void main(String[] args) throws Exception
	{
		MyRemote service = (SomeRemoteInterface)Naming.lookup("rmi://localhost/Hello");
		System.out.println(service.sayHi());
	}
}

I've deliberately not done any exception handling here, instead just dumping when an error occurs. To run all this, first of course compile server and client (the interface gets compiled automatically when you compile either).

>javac RemoteServer
>javac RemoteClient

Then start the RMI registry.

>start rmiregistry
(or >rmiregistry & on Unix)

Next start the server

>start java RemoteServer

Last but not least start the client and marvel at the friendly message you get

>java RemoteClient

Of course in a real scenario the client would run on a different machine from the registry and the server. In that case the interface must be available to both.

Some additional info after more testing and fooling around...

If the server is not the class implementing Remote you DO need to use rmic and create the stub class. From another example (O'Reilly's networking book)

import java.rmi.*;
import java.net.*;

public class FibonacciServer
{
	public static void main(String[] args)
	{
		try
		{
			FibonacciImpl f = new FibonacciImpl();
			Naming.bind("fibonacci", f);
			System.out.println("fibonacciserver listening");
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
	}
}
import java.rmi.*;
import java.rmi.server.*;
import java.math.BigInteger;

public class FibonacciImpl implements Fibonacci
{
	public FibonacciImpl() throws RemoteException
	{
		UnicastRemoteObject.exportObject(this);
	}

	public BigInteger getFibonacci(int n) throws RemoteException
	{
		return getFibonacci(new BigInteger(Long.toString(n)));
	}

	public BigInteger getFibonacci(BigInteger n) throws RemoteException
	{
		System.out.println("Calculating the " + n + "th Fibonacci number");
	}
}

This scenario requires the stub to be generated. It does not require the skeleton class when run under Tiger so the lack of that doesn't cause trouble.

How to create an RMI system

Intro

In this article, I lead you through the process of creating a very simple RMI system. (This example was inspired by the RMI demo in Orfali and Harkey's book on CORBA; I thought it was still too complicated, so I've taken the simplification a bit further). I've tried to make this example as bare-bones as possible in order to keep the focus on the steps needed to make an RMI program work. I've also tried to avoid skipping any steps that might throw a first-time user, and have tried to ensure that all steps are performed in the proper order (there are a couple of steps in which order matters). That being said, let's do some RMI!

Steps to creation of an RMI system

The short version

  1. Create an interface. (in this case, the interface is myRMIInterface.java)
  2. Create a class that implements the interface. (in this case, myRMIImpl.java)
  3. Create a server that creates an instance of this class
  4. Create a client that connects to the server object using Naming.lookup()
  5. Compile these classes
  6. Run the RMI interface compiler on the .class file of the implementation class (in this case, you'd say "rmic myRMIImpl")
  7. Start the RMI registry (on Windows NT/95, say "start rmiregistry")
  8. Start the server class ("start java myRMIServer")
  9. Run the client program ("java myRMIClient")

The long version

1. Create an interface

An interface is similar to a pure virtual class in C++; it defines the methods (and the arguments to the methods) that will be available in a class that implements it; however, it doesn't actually implement any of the methods. The interface I created for this example is myRMIInterface.java. It contains only one method; the method takes no arguments and returns an object of type java.util.Date. Note 2 things about this interface: it extends the java.rmi.Remote interface (all interfaces used in RMI must do this) and the method throws a java.rmi.RemoteException (every method in a remote object's interface must specify this exception in its "throws" clause; this exception is a superclass of all RMI exceptions that can be thrown. See the JDK 1.1 final docs (in the java.rmi section) for a complete list of exceptions that can be thrown.

public interface myRMIInterface extends java.rmi.Remote
{
        public java.util.Date getDate() throws java.rmi.RemoteException;
}

2. Create a class that implements the interface

In this example, the implementation is found in myRMIImpl.java. This class must extend java.rmi.UnicastRemoteObject and must implement the interface you created in step 1. In my example, the only method that needs to be implemented is getDate(), which returns the current date and time on the system. Note 2 things about the constructor: the call to super(), and the call to Naming.rebind(name, this). This call informs the RMI registry that this object is available with the name given in "String name". Other than that, this object simply implements all the methods declared in the interface.

import java.rmi.*;
import java.rmi.server.UnicastRemoteObject;

public class myRMIImpl extends UnicastRemoteObject implements myRMIInterface
{
        public myRMIImpl(String name) throws RemoteException
                {
                super();
                try
                        {
                        Naming.rebind(name, this);
                        }
                catch(Exception e)
                        {
                        System.out.println("Exception occurred: " + e);
                        }
                }
        public java.util.Date getDate()
                {
                return new java.util.Date();
                }
}

3. Create a server that creates an instance of the "impl" class

In this example, the server class is myRMIServer.java. In this case, the server is pretty simple. It does 2 things: 1) Installs a new RMISecurityManager (Note that RMI uses a different security manager from the security manager used for applets). 2) Creates an instance of the myRMIImpl class, and gives it the name "myRMIImplInstance". The myRMIImpl object takes care of registering the object with the RMI registry.
After this code is run, the object will be available to remote clients as "rmi://

import java.rmi.*;
import java.rmi.server.UnicastRemoteObject;

public class myRMIServer
{
        public static void main(String[] argv)
                {
                System.setSecurityManager(new RMISecurityManager());
                try
                        {
                        myRMIImpl implementation = new myRMIImpl("myRMIImplInstance");
                        }
                catch (Exception e)
                        {
                        System.out.println("Exception occurred: " + e);
                        }
                }
}

4. Create a client that connects to the server object using Naming.lookup().

In this example, the client class is myRMIClient.java. The client first installs a new RMI Security Manager (see previous step), then uses the static method Naming.lookup() to get a reference to the remote object. Note that the client is using the interface to hold the reference and make method calls. You should make sure you've created your interface before you try to build the client, or you'll get "class not found" errors when you try to compile your client.

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

public class myRMIClient
{
        public static void main(String[] argv)
                {
                System.setSecurityManager(new RMISecurityManager());
                if (argv.length != 1)
                        {
                        System.out.println("usage: java myRMIClient <IP address of host running RMI server>");
                        System.exit(0);
                        }
                String serverName = argv[0];
                try
                        {
                                        //bind server object to object in client
                        myRMIInterface myServerObject = (myRMIInterface) Naming.lookup("rmi://"+serverName+"/myRMIImplInstance");

                                        //invoke method on server object
                        Date d = myServerObject.getDate();
                        System.out.println("Date on server is " + d);
                        }
                catch(Exception e)
                        {
                        System.out.println("Exception occured: " + e);
                        System.exit(0);
                        }
                System.out.println("RMI connection successful");
                }
        

}

5. Compile these classes

Just do "javac *.java". Piece of cake. :-)

6. Run the RMI interface compiler on your implementation class.

This step generates some additional Java classes. They're stubs and skeletons used by RMI; you don't have to worro about what's in them. Note: You only need to run rmic on the class that implements your RMI interface. In this case, you'd do "rmic myRMIImpl". Also note that the rmic compiler runs on a .class file, not a .java file.

7. Start the RMI registry

OK. You're done with development at this point; you've built all the code you need to run this example. Now you're setting up the environment so that you can run it. rmiregistry is a program that comes with the JDK 1.1 final; you can find it in the "bin" directory of your JDK installation. Under Windows 95 or NT, you can simply say "start rmiregistry" on the command line, which will cause the RMI registry to be started in its own DOS window. The RMI registry must be started before you can start your server.

8. Start your RMI server program

Under Windows 95 or NT, say "start java myRMIServer" on the command line. This starts the server running. As we discussed earlier, the server then creates an instance of myRMIImpl and makes it known to the RMI server as "myRMIImplInstance".

9. Run your client program

Say "java myRMIClient". The program will ask the RMI registry for a reference to "myRMIImplInstance". After it has this reference, the client can invoke any methods declared in the myRMIInterface interface as if the object were a local object.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.