Java Remote Method Invocation (RMI) is a technology that makes it very easy to implement server client architectures. Java RMI lets you to make method calls to remote objects without having the client treat the remote object very differently from a local object. I've been using RMI a fair amount recently, so I thought I'd go over some of the more interesting things I've learned. Primarily, how it works and how to control the ports it uses.
A Brief Overview about how RMI Works
First there are the "Stubs." Stubs are essentially specialized proxies to a specific server. Internally, they hold the IP and Port to a server. They know how to make requests to the server and how to process the response from that server. They're Java objects and are used exactly the same as a local object once you've obtained the stub.
Then there's the "RMI Registry." The RMI Registry is a server that stores name to stub matchings. All its clients know the IP and Port number for the Registry. The clients contact the registry to get stub objects for servers whose IP and Port number they don't necessarily know. The Registry listens on port 1099 unless configured otherwise.
Finally, there's a server run for each stub that's registered in the RMI Registry.
And so the following happens when RMI is used:
- The RMI Registry that stores name to stub pairings is created.
- Whenever a new name and stub is bound in the Registry, a matching server is created (usually on a dynamic port) for the stub to communicate with.
- The client knows the IP and Port of an RMI Registry. The client uses that IP and Port to ask the registry for the stub matching a certain name.
- When a client calls a method on the stub, the stub contacts the matching server to process the request. The matching server processes the response and returns a result to the stub. The stub in turn processes the response and returns a value as if the method was called locally.
But You Don't Care How It Works, do you?
The great thing about RMI is that the programmer doesn't really have to worry about the network communications at all. They just need to get a Java object (the stub) from the Registry and make method calls on it. The stub, skeleton, and the registry do all the network stuff for them. So all a programmer really cares about is implementing RMI. For that information, Java's own tutorial is probably the best source. So go and read it at http://java.sun.com/docs/books/tutorial/rmi/server.html. Really, the rest of this article won't make sense without knowing how to use RMI.
Stop Randomly Choosing Ports!!!
For the project I was working on, it was important to control the IP and the Ports RMI used. I was using Solaris 10 with Trusted Extensions and wanted Cross Domain capabilities, which required me to explicitly identify certain ports as "Multi Level Ports." You can see how dynamically assigning ports to RMI servers was no good for me. Furthermore, I was using several different IP Addresses on the machine and did not want RMI to to accept connections on all of them. So what this really came down to was controlling the sockets that are normally dynamically created by the Registry and RMI Servers.
I wrote my own RMIServerSocketFactory to gain control over the sockets in RMI. It is as follows:
- ///////////////////////////////////////////////////////////////////////////
- //
- // CustomSocketFactory - This class is used to provide client and server
- // sockets with configurable ports and ip addresses. By default,
- // RMISocketFactory chooses these dynamically. Note that the backlog
- // for ServerSockets is hardcoded.
- //
- ///////////////////////////////////////////////////////////////////////////
- import java.io.IOException;
- import java.io.Serializable;
- import java.net.InetAddress;
- import java.net.ServerSocket;
- import java.net.Socket;
- import java.rmi.server.RMISocketFactory;
- /**
- * CustomSocketFactory
- */
- public class CustomSocketFactory extends RMISocketFactory implements Serializable
- {
- /**
- * The InetAddress to be used for the Sockets this factory serves.
- */
- private InetAddress ipInterface = null;
- /**
- * The port to be used with Sockets this factory serves. By
- * default this value is set to -1 (which is an impossible port),
- * which tells the factory to allow the port be be dynamically determined.
- */
- private int port = -1;
- /**
- * Default Constructor. IP and Port will be dynamically assigned.
- */
- public CustomSocketFactory() {}
- /**
- * Constructor that specifies the INetAddress to use. The port will still
- * be served dynamically.
- *
- * @param ipInterface The InetAddress to be associated with Sockets served by
- * this factory.
- */
- public CustomSocketFactory(InetAddress ipInterface)
- {
- this.ipInterface = ipInterface;
- }
- /**
- * Constructor that specifies the INetAddres and port to use.
- *
- * @param ipInterface The INetAddress to be associated with Sockets served by
- * this factory.
- * @param port The port to be associated with Sockets served by this
- * factory.
- */
- public CustomSocketFactory(InetAddress ipInterface, int port)
- {
- this.ipInterface = ipInterface;
- this.port = port;
- }
- /**
- * Returns a ServerSocket with the ip address and port used dependant
- * on the constructor used.
- *
- * @param port The dynamic port to be used for the socket served. This
- * is not used if the constructor specifying a port was used
- * for this factory.
- * @return A socket.
- */
- public ServerSocket createServerSocket(int port) throws IOException
- {
- ServerSocket serverSocket = null;
- //Decides whether or not to use the supplied port
- if( this.port != -1 )
- {
- port = this.port;
- }
- try
- {
- //NOTE: The backlog is hardcoded to 50 here. This was
- // done for no particular reason and could be dynamically
- // set by making new method calls.
- serverSocket = new ServerSocket(port, 50, ipInterface);
- } catch( IOException e ) {
- throw e;
- }
- return serverSocket;
- }
- /**
- * Returns a Socket with the ip address and port used dependant
- * on the constructor used.
- *
- * @param port The dynamic port to be used for the socket served. This
- * is not used if the constructor specifying a port was used
- * for this factory.
- * @param dummy This INetAddress is not used.
- * @throws Exception Throws for any exception.
- * @return A socket.
- */
- public Socket createSocket( String dummy, int port) throws IOException
- {
- if( this.port != -1 )
- {
- port = this.port;
- }
- return new Socket( ipInterface, port );
- }
- /**
- * Returns true if the object passed in is identical to this object.
- *
- * @param that The object to be compared with this object.
- * @return True if equal, otherwise false.
- */
- public boolean equals( Object that )
- {
- return (that != null && that.getClass() == this.getClass() );
- }
- }
- import java.rmi.Remote;
- import java.rmi.RemoteException;
- public interface AddServerInterface extends Remote
- {
- public int add( int a, int b ) throws RemoteException;
- }
- import java.net.InetAddress;
- import java.rmi.RemoteException;
- import java.rmi.registry.LocateRegistry;
- import java.rmi.registry.Registry;
- import java.rmi.server.UnicastRemoteObject;
- public class AddServer implements AddServerInterface
- {
- public AddServer() throws Exception
- {
- //Set up the RMI. Use hard coded values for this example.
- String registryIp = "10.10.1.1";
- int registryPort = 3645;
- String serverIp = "10.10.1.1";
- int serverPort = 3646;
- //Create the Add Server, supplying the Socket Factories to assign our IP and Port.
- CustomSocketFactory clientSocketFactory = new CustomSocketFactory( InetAddress.getByName(serverIp), serverPort );
- CustomSocketFactory serverSocketFactory = new CustomSocketFactory( InetAddress.getByName(serverIp), serverPort );
- AddServerInterface stub = (AddServerInterface)UnicastRemoteObject.exportObject(this,serverPort,clientSocketFactory,serverSocketFactory);
- //Get or Create the RMI registry.
- Registry registry = null;
- try
- {
- //Create Registry
- CustomSocketFactory sf = new CustomSocketFactory( InetAddress.getByName(registryIp), registryPort );
- registry = LocateRegistry.createRegistry( registryPort, null, sf );
- }
- catch( RemoteException re )
- {
- //Registry was already created, so just connect
- registry = LocateRegistry.getRegistry(registryPort);
- }
- //Bind the Add Server to the registry
- registry.rebind( "AddServer", stub );
- }
- public int add( int a, int b ) throws RemoteException
- {
- return a+b;
- }
- public static void main( String args[] ) throws Exception
- {
- try
- {
- AddServer addServer = new AddServer();
- } catch ( Exception e ) {
- System.out.println( "Main error: " );
- e.printStackTrace();
- }
- }
- }
- //Required for RMI Support
- import java.net.InetAddress;
- import java.rmi.Remote;
- import java.rmi.registry.LocateRegistry;
- import java.rmi.registry.Registry;
- public class AddClient
- {
- public AddClient() {}
- public static void Main( String args[] )
- {
- try
- {
- //Connect to the add server.
- Registry registry = LocateRegistry.getRegistry( "10.10.1.1", 3645 );
- Remote remote = registry.lookup( "AddServer" );
- if( remote == null )
- {
- System.out.println( "Could not find it" );
- }
- AddServerInterface addServer = (AddServerInterface)remote;
- System.out.println( "1 + 1 = " + addServer.add( 1, 1 ) );
- } catch ( Exception e ) {
- e.printStackTrace();
- }
- }
- }
No comments:
Post a Comment