Creating a Custom RMI Socket Factory


This page shows you the steps to follow to create and install a custom RMI socket factory. A custom RMI socket factory is useful if (1) you want your RMI client and server to talk across sockets that encrypt or compress your data, or (2) you want to use different types of sockets for different connections.

Installing your own RMI socket factory allows the RMI transport layer to use a non-TCP or custom transport protocol over IP, rather than TCP, provided by java.net.Socket , which RMI uses by default.

Before the release of the JDK1.2, it was possible to create a custom java.rmi.RMISocketFactory subclass that produced a type of socket other than java.net.Socket for use by the RMI transport. It was not possible, however, for an installed RMI socket factory to produce different types of sockets on an object-by-object basis.  For example in JDK1.1, an RMI socket factory could not produce SSL sockets for one object and use the Java Remote Method Protocol (JRMP) directly over TCP for a different object in the same Java Virtual Machine (JVM). Also before 1.2, it was necessary to spawn an instance of the rmiregistry that spoke only your custom socket protocol.

Then in Beta3 JDK1.2, it was possible for RMI clients to use a custom RMI socket factory, but the socket factory could not be downloaded, so the client had to be able to find the socket factory class locally.

Now, in this release of the JDK, it is possible to create a custom RMI socket factory that produces the type of socket connection you want when you want on a per-object basis, download a client-side socket factory, and continue to use the default rmiregistry.

The rest of this tutorial is laid out as follows:

Many people are interested in secure communication between RMI clients and servers. For the RMI/SSL story see The Scoop on RMI and SSL.


Creating an RMI Socket Factory That Produces a Single Type of Socket

There are four steps to creating a custom RMI socket factory, that produces a single type of socket.
  1. Decide upon the type of socket to be produced.
  2. Write a client-side socket factory that implements RMIClientSocketFactory.
  3. Implement the RMIClientSocketFactory createSocket method.
  4. Write a server-side socket factory that implements RMIServerSocketFactory.
  5. Implement the RMIServerSocketFactory createServerSocket method.

Step 1:
Decide Upon the Type of Socket to be Produced

The type of socket to be produced is an application-specific decision. You get to choose the type of socket that is appropriate for your application. If your server handles a lot of sensitive data, you might want a socket that encrypts the data. If your server deals with video, you are likely to need a socket that does compression.

For this example, the RMI socket factory will produce sockets that provide data compression. We will produce examples.rmisocfac.CompressionSocket sockets from the page Creating a New Socket Type.

Step 2:
Write a client-side socket factory that implements RMIClientSocketFactory

Begin the implementation of a client-side RMI socket factory by implementing the  RMIClientSocketFactory  interface. The custom socket factory for this example will be called CompressionClientSocketFactory.

Below is the code for the class CompressionClientSocketFactory as well the code for the next step, overriding the createSocket method. An explanation of that step follows the code example.

package examples.rmisocfac;

import java.io.*;
import java.net.*;
import java.rmi.server.*;

public class CompressionClientSocketFactory
    implements RMIClientSocketFactory, Serializable {

    public Socket createSocket(String host, int port)
        throws IOException
    {
        CompressionSocket socket =
            new CompressionSocket(host, port);
        return socket;
    }

}
 

Step 3:
Implement the RMIClientSocketFactory createSocket method.

Since the function of an RMI socket factory is to supply the RMI runtime with sockets, the CompressionClientSocketFactory needs to provide an implementation of the RMIClientSocketFactory createSocket method, so that it creates and returns sockets of the correct type -- CompressionSocket. Notice that in the above code, a CompressionSocket is created and returned.

Step 4:
Write a server-side socket factory that implements RMIServerSocketFactory

Begin the implementation of a server-side RMI socket factory by implementing the  RMIServerSocketFactory  interface. The custom socket factory for this example will be called CompressionServerSocketFactory.

Below is the code for the class CompressionServerSocketFactory as well the code for the next step, implementing the createServerSocket method. An explanation of that step follows the code example.

package examples.rmisocfac;

import java.io.*;
import java.net.*;
import java.rmi.server.*;
 
public class CompressionServerSocketFactory
    implements RMIServerSocketFactory, Serializable {

    public ServerSocket createServerSocket(int port)
        throws IOException
    {
        CompressionServerSocket server = new CompressionServerSocket(port);
        return server;
    }
}
 

Step 5:
Implement the RMIServerSocketFactory createServerSocket method.

Implementing createServerSocket in your RMI socket factory is almost identical to implementing createSocket, except createServerSocket needs to create and return a socket of type CompressionServerSocket.

Now that you have worked through one example of creating an RMI socket factory, you have all the experience necessary to move on to creating a socket factory capable of producing more than one type of socket, which is the next example.


Creating an RMI Socket Factory that Produces More Than One Type of Socket

To create an RMI socket factory capable of producing more than one type of socket, you start by following the steps for creating a factory which produces a single socket type. Only a little more information is necessary to create a "wrapper" for the different socket types.

For this example, the custom RMIClientSocketFactory class will be named MultiClientSocketFactory, because it supports multiple socket types, and likewise, the custom RMIServerSocketFactory class will be named MultiServerSocketFactory. Each of these socket factories has a constructor that specifies which protocol should be supported for this instance of the object.

Now, following the steps to create a custom socket factory, from the previous example, we will first decide which types of sockets to produce.

Step 1:
Decide Upon the Type of Socket to be Produced

This custom RMI socket factory will produce three types of sockets: XorSocket, CompressionSocket and the default, java.net.Socket.

The source code for the implementation of sockets of type XorSocket can be found here.

Step one is now complete. Below is the source code for steps 2-5.  Following the code is an explanation of each of the remaining steps.

            
package examples.rmisocfac;

import java.io.*;
import java.net.*;
import java.rmi.server.*;
public class MultiClientSocketFactory
    implements RMIClientSocketFactory, Serializable
{
    /* 
     * Get the default RMISocketFactory 
     */
    private static RMISocketFactory defaultFactory =
        RMISocketFactory.getDefaultSocketFactory();

    private String protocol;
    private byte[] data;

    public MultiClientSocketFactory(String protocol, byte[] data) {
        this.protocol = protocol;
        this.data = data;
    }  
    
    /*  
     * Override createSocket to call the default 
     * RMIClientSocketFactory's createSocket method. This 
     * way, you'll get a TCP connection if you don't 
     * specify compression or xor 
     */ 
    public Socket createSocket(String host, int port)
        throws IOException
    {
        if (protocol.equals("compression")) {
            return new CompressionSocket(host, port);

        } else if (protocol.equals("xor")) {
            if (data == null || data.length != 1)
                throw new IOException("invalid argument for XOR protocol");
            return new XorSocket(host, port, data[0]);
 
        }
 
        return defaultFactory.createSocket(host, port);
    }
}

Step 2:
Implement  RMIClientSocketFactory

The above class, MultiClientSocketFactory, implements RMIClientSocketFactory.
 

Step 3:
Implement the RMIClientSocketFactory createSocket method.

Since the socket factory created in this example can produce two different types of sockets in addition to sockets of the default type, it is necessary to override the createSocket method. If the protocol field of the MultiClientSocketFactory constructor is equal to "xor" then a XorSocket is created and returned. If the protocol field is equal to "compression" then a CompressionSocket is created and returned.
 

Step 4:
Write a server-side socket factory that implements RMIServerSocketFactory

package examples.rmisocfac;

import java.io.*;
import java.net.*;
import java.rmi.server.*;

public class MultiServerSocketFactory
    implements RMIServerSocketFactory, Serializable
{
    /*
     * Get the default RMISocketFactory
     */
    private static RMISocketFactory defaultFactory =
        RMISocketFactory.getDefaultSocketFactory();

    private String protocol;
    private byte[] data;

    public MultiServerSocketFactory(String protocol, byte[] data) {
        this.protocol = protocol;
        this.data = data;
    }

    /*
     * Override createServerSocket to call the default
     * RMIServerSocketFactory's createServerSocket method, if
     * an invalid protocol is specified.
     */
    public ServerSocket createServerSocket(int port)
        throws IOException
    {
        if (protocol.equals("compression")) {
            return new CompressionServerSocket(port);

        } else if (protocol.equals("xor")) {
            if (data == null || data.length != 1)
                throw new IOException("invalid argument for XOR protocol");
            return new XorServerSocket(port, data[0]);

        }

        return defaultFactory.createServerSocket(port);
    }
}

Step 5:
Implement the RMIServerSocketFactory createServerSocket method.

Overriding createServerSocket is almost identical to overriding createSocket. As in step three, the RMIServerSocketFactory createServerSocket method, the type of socket created and returned is determined by the protocol field of the MultiClientSocketFactory constructor.

Using Your Custom Socket Factory in an Application

There are only two more steps to complete when using a custom RMI socket factory for a remote object:
  1. In the remote object implementation, write a constructor that calls the UnicastRemoteObject (or Activatable) constructor that takes RMIClientSocketFactory and RMIServerSocketFactory parameters.
  2. Write a java.security.policy file that allows your program to create sockets.
Note: In this example, for simplicity,  we will use a policy file that gives global permission to anyone from anywhere. Do not use this policy file in a production environment. For more information on how to properly open up permissions using a java.security.policy file, please refer to to the following documents
http://java.sun.com/products/jdk/1.2/docs/guide/security/PolicyFiles.html
http://java.sun.com/products/jdk/1.2/docs/guide/security/permissions.html
 

Step 1:
Write a Remote Object Constructor that Calls the UnicastRemoteObject Constructor that takes RMIClientSocketFactory and RMIServerSocketFactory parameters.

If you create your own RMI socket factory, then you need a way to tell the RMI runtime which type of socket factory to use. Assuming the server extends UnicastRemoteObject, this notification is accomplished by creating a remote object constructor that calls the following version of the UnicastRemoteObject constructor:

protected UnicastRemoteObject(int port, RMIClientSocketFactory csf,
    RMIServerSocketFactory ssf)

Below is a HelloImpl constructor from the original RMI "Hello World" example appropriately modified for a socket of type XorSocket.

    public HelloImpl(String protocol, byte [] pattern)
       throws RemoteException
    {
       super(0, new MultiClientSocketFactory(protocol, pattern),
           new MultiServerSocketFactory(protocol, pattern));
    }
 

Notice that the UnicastRemoteObject constructor,

protected UnicastRemoteObject(int port, RMIClientSocketFactory csf,
    RMIServerSocketFactory ssf)

is called from the HelloImpl constructor. Once your custom socket factory is set, sockets of the desired type will be used for your RMI client-server application.

Step 2:
Write a java.security.policy file that allows your program to create sockets.

The policy file for this example, is not secure and should not be used in a production environment.

The policy file for this example looks like this:

grant {
 // Allow everything for now
 permission java.security.AllPermission;
};

Next is a version of the "Hello World" example that uses MultiClientSocketFactory and MultiServerSocketFactory to communicate using sockets of type XorSocket.


This example has changed from the original "Hello World" example in the RMI tutorial. Most notably, the client in this example is not an applet. In addition, the client class in this example is called HelloClient. The last difference is that all of the classes are in the examples.rmisocfac package.

It is important to recognize that this example assumes that the client, server, and registry are all run on the same machine.

Below is the interface Hello, from the file Hello.java. Notice that, except for the package name, this interface has not changed from the original Hello.java.

        package examples.rmisocfac;
 
        public interface Hello extends java.rmi.Remote {
            String sayHello() throws java.rmi.RemoteException;
        }
Take a look at the modified version of the client class, HelloClient.java. Notice that the RMISecurityManager is installed at the beginning of main. Otherwise the class HelloClient is no different than before.
        package examples.rmisocfac;
 
        import java.rmi.*;
 
        public class HelloClient {
 
            private static String message = "";
        
            public static void main(String args[]) {

               //Create and install a security manager
               if (System.getSecurityManager() == null)
                   System.setSecurityManager(new RMISecurityManager());

               try {
                   Hello obj = (Hello) Naming.lookup("/HelloServer");
                   message = obj.sayHello();
                   System.out.println(message);

               } catch (Exception e) {
                   System.out.println("HelloClient exception: " +
                                       e.getMessage());
                   e.printStackTrace();
               }

            }
 
        }
Last in this example is the source for HelloImpl from the file HelloImpl.java. Besides the change in the package name, two modifications have been made so that sockets of type "xor" will be used for the RMI calls between the client and the server. Note that the constructor has been changed to call the version of the UnicastRemoteObject constructor that takes a client and server socket factory as parameters.
        package examples.rmisocfac;

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

        public class HelloImpl extends UnicastRemoteObject implements Hello {
 
            /*
             * Constructor calls constructor of superclass with
             * client and server socket factory  parameters.
             */
            public HelloImpl(String protocol, byte [] pattern) throws RemoteException {
                super(0, new MultiClientSocketFactory(protocol, pattern),
                         new MultiServerSocketFactory(protocol, pattern));
            }

            /*
             * Remote method returns String "Hello World!"
             * when invoked.
             */
            public String sayHello() throws RemoteException {
                return  "Hello World!";
            }
 
            public static void main(String args[]) {

                //Create and install a security manager
                if (System.getSecurityManager() == null)
                    System.setSecurityManager(new RMISecurityManager());

                byte [] aPattern = { (byte)1011 };
                try {
                    HelloImpl obj = new HelloImpl("xor", aPattern);
                    Naming.rebind("/HelloServer", obj);
                    System.out.println("HelloServer bound in registry");
                } catch (Exception e) {
                    System.out.println("HelloImpl err: " + e.getMessage());
                    e.printStackTrace();
                }
            }
        }

To download this example and the supporting files, click here.

Directions on how to compile and run the above "Hello World" example can be found here.



Copyright © 1998 Sun Microsystems, Inc. All rights reserved.