Here's a brief explanation of the process of developing an RMI application in java. I wrote this because when I started learning about RMI, I ended up reading five different chapters, from five different books to get all the details I needed. Most of the books discussed the theory and design behind RMI exhaustively, but spent very little time (if any) laying out the actual steps you need to take. Every one of them left out or got wrong some trivial detail that cost me a couple hours of pain to learn about. Hence this page is focused on laying out all of the details, in sequence, hopefully concisely, so you don't lose track of them.
There's a link near the bottom to the pages where I learned this. Go read them.
Note: the examples here aren't complex or sophisticated. That's the intent; they are deliberately simple. This page is not about designing an RMI application, it's about implementing one. I'd like to add some more sophisticated examples later, but meanwhile...
Copyright 1999, Steven J. Owens, All Rights Reserved. Permission
is granted to freely print and distribute this for personal use,
provided you leave the document intact and include this copyright
notice. Permission is granted to freely excerpt sections of this
document, provided you properly attribute your excerpt. Contact the
author for permission to use in a formal publication (which will
probably be given).
import java.rmi.*; import java.rmi.server.*; |
% rmiregistry |
LocateRegistry.createRegistry(someport) ; |
Note 1: rmic generates java source, then compiles
it, then deletes the generated source. Since some obscure bugs can
crop up in the generated source, I like to use
rmic -keepgenerated |
Note 2: rmic sometimes gets funky, I often find it's a good idea to kill all my processes, log out, log back in, and then do the rmic commands, to make sure there isn't odd data lying around in memory.
Note 3: In development it's often easier to just have both the client and the server use the same set of class files; in production there are techniques for serving the stub and skeleton files via an HTTP server, but we won't go into them here.
import java.rmi.*; public class yourRMIClientClass { // This Will Not Compile System.setSecurityManager(new RMISecurityManager()) ; Remote remoteObject = Naming.lookup("//somebox.com/YourRMIServer") ; YourRMIServerInterface blah; if (remoteObject instanceof YourRMIServerInterface) { blah = (YourRMIServerInterface)remoteObject ; } blah.yourRemoteMethod(arguments) ; } |
Client version 2 (with comments):
import java.rmi.*; public class yourRMIClientClass { // This Will Not Compile // Set the client security manager System.setSecurityManager(new RMISecurityManager()) ; // Instantiate an RMI remote object by calling the RMI registry // with the name of your RMI service. Note that // Naming.lookup("name") below is just syntactic sugar for // LocateRegistry.getRegistry("host", port).rebind("name") // See "Naming" near the end of this article. Remote remoteObject = Naming.lookup("YourRMIServerURL") ; // Narrow down the remote object to your RMI server interface with // a simple cast to your interface. Of course, check first to // make sure the remote object is an instance of your interface YourRMIServerInterface blah ; if (remoteObject instanceof YourRMIServerInterface) { blah = (YourRMIServerInterface) remoteObject ; } // Now just call the method on your server class. blah.yourRemoteMethod(arguments) ; } |
Now go back and put in appropriate exception handling.
Actually, this is trickier than that, because there are RMI-specific
exceptions you have to watch out for, but I decided to leave them out
of the first cut.
Client version 3 (with exception handlers):
import java.rmi.*; public class yourRMIClientClass { // This Will Compile But Won't Do Anything public yourRMIClientClass() { // Set the client security manager try { System.setSecurityManager(new RMISecurityManager()) ; } catch (java.rmi.RMISecurityException exc) { System.out.println("Security violation " + exc.toString()) ; throw new RuntimeException("Fall down, go boom", e) ; } // Instantiate an RMI remote object by calling the RMI registry with the // name of your RMI service. try { Remote remoteObject = Naming.lookup("YourRMIServerURL") ; } catch (java.rmi.NotBoundException exc) { System.out.println("Error in lookup() " + exc.toString()) ; throw new RuntimeException("Fall down, go boom", e) ; } // Narrow down the remote object to your RMI server interface with // a simple cast to your server interface. Of course, check // first to make sure the remote object is an instance of your // interface. YourRMIServerInterface blah ; if (remoteObject instanceof YourRMIServerInterface) { blah = (YourRMIServerInterface) remoteObject ; } // Now just call the method on your server class. try { blah.yourRemoteMethod(arguments) ; } catch (java.rmi.RemoteException exc) { System.out.println("Error in invocation " + exc.toString()) ; throw new RuntimeException("Fall down, go boom", e) ; } } } |
This still isn't enough, of course, there's nothing here to actually instantiate the client object and pump requests through it and use it. That's all vanilla java, though, so you should be able to figure it out. Hopefully I'll get a chance to come back and add some more sample code. (sorry, but I've got this day job... :-)
Note: Strictly speaking, although the interface has to declare it throws all RemoteExceptions, the methods only need to throw and catch specific RemoteExceptions. However, I'm lazy at the moment so I'm not going to list them here, just yet.
Okay, here's a first cut. In this version, the constructor builds the remote object and stashes it in an instance variable. Some other class instantiates yourRMIClientClass, then calls doRMICall with the string argument. doRMICall does the remote call and returns the resulting boolean.
Client version 4:
import java.rmi.*; public class yourRMIClientClass { YourRMIServerInterface theRemoteServer ; public yourRMIClientClass() { // Set the client security manager try { System.setSecurityManager(new RMISecurityManager()) ; } catch (java.rmi.RMISecurityException exc) { System.out.println("Security violation " + exc.toString()) ; throw new RuntimeException("Fall down, go boom", e) ; } // Instantiate an RMI remote object by calling the RMI registry with the // name of your RMI service. try { Remote remoteObject = Naming.lookup("YourRMIServerURL") ; } catch (java.rmi.NotBoundException exc) { System.out.println("Error in lookup() " + exc.toString()) ; throw new RuntimeException("Fall down, go boom", e) ; } // Narrow down the remote object to your RMI server interface with // a simple cast to your server interface. Of course, check // first to make sure the remote object is an instance of your // interface. if (remoteObject instanceof YourRMIServerInterface) { theRemoteServer = (YourRMIServerInterface) remoteObject ; } } public int doRMICall(String arguments) { // Now just call the method on your server class. try { return this.theRemoteServer.yourRemoteMethod(arguments) ; } catch (java.rmi.RemoteException exc) { System.out.println("Error in invocation " + exc.toString()) ; throw new RuntimeException("Fall down, go boom", e) ; } } } |
// Need the interface to generate the local object. import java.rmi.*; public interface YourRMIServerInterface extends Remote { int yourRemoteMethod(String argument) throws RemoteException; } |
Server Version 1:
import java.rmi.*; import java.rmi.server.*; public class YourServerClass extends UnicastRemoteOBject implements YourRMIServerInterface; { YourServerClass() throws RemoteException { super() ; } public int yourRemoteMethod(String argument) throws RemoteException { // do something useful here. } public static void main(String args[]) { System.setSecurityManager(new RMISecurityManager()) ; YourServerClass serverblah = new YourServerClass() ; Naming.rebind("YourRMIServer", serverblah) ; } } |
Server version 2:
// Now your server class has to implement the interface // and to be able to do RMI server stuff it has to extend UnicastRemoteObject import java.rmi.*; import java.rmi.server.*; public class YourServerClass extends UnicastRemoteOBject implements YourRMIServerInterface; { // UnicastRemoteObject is descended from Remote, so in theory it could // also be an RMI client without much extra work. // You have to implement two constructors YourServerClass() throws RemoteException { super() ; } public int yourRemoteMethod(String argument) throws RemoteException { // do something useful here. } // Now you need a main method to kick it all off when you run the server. public static void main(String args[]) { try { System.setSecurityManager(new RMISecurityManager()) ; } catch (java.rmi.RMISecurityException exc) { System.out.println("Security violation " + exc.toString()); throw new RuntimeException("Fall down, go boom", e) ; } // It has to instantiate itself so it's running YourServerClass serverblah = new YourServerClass() ; // call Naming.rebind to pass the registry its name and a reference to it. Naming.rebind("YourRMIServer", serverblah) ; } } |
Adrian Colley was kind enough to point out that java.rmi.Naming may be a bit misleading. To clarify, java.rmi.Naming is not a naming service like COS::Naming. The syntax:
java.rmi.Naming.rebind("//host:part/name");
|
is just syntactic sugar that saves you the trouble of having to type:
LocateRegistry.getRegistry("host",port).rebind("name");
|
When the server object registers itself with RMI by calling...
rebind(name, ref);
|
...or the client object looks up the server by calling...
lookup(name);
|
...the name you pass to the method looks like a URL. Specifically, it's a String object that contains a URL formatted address where the host is your hostname or host IP numbers.
Most of the following is according to the first bullet point in the
last at the bottom of this page:
http://java.sun.com/docs/books/tutorial/rmi/implementing.html
Highly recommended reading; I was going to quote the relevant section here, but their copyright statement at http://java.sun.com/docs/books/tutorial/information/copyright.html looks fairly aggressive. I think my usage of it here would fall under "fair use" but I'm not certain they would think so, so...
Remember, name looks like a URL, like this:
http://yourhostname.com/yourrmiservername |
You can leave out the hostname and it'll default to the local host, but your system still has to have an active TCP/IP connection to test things. You can't use "localhost", according to Bruce Eckel's book, Thinking In Java, pages 900-904. (Find the full text at www.bruceeckel.com, this is extremely recommended reading for java programming in general).
You can leave out the protocol identifier string (in a typical web href, that's the part that looks like "http:") so it'd look like this:
//yourhostname.com/yourrmiservername |
If you leave the port out, it defaults to 1099. If you want to specify the port number, you must include the full hostname specification:
//yourhostname.com:1234/yourrmiservername |
Of course, if you're using the special port, you have to start your rmiregistry server up on that port. Just include the port number as an argument:
% rmiregistry 1234 |
LocateRegistry.createRegistry(1234) ; |