Creating a Custom RMISocketFactory


This page shows you the steps to follow to create and install a custom java.rmi.server.RMISocketFactory. A custom RMISocketFactory 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 RMISocketFactory allows the RMI transport layer to use a non-TCP or custom transport protocol, 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 RMISocketFactory that produced a type of socket other than java.lang.Socket for use by the RMI transport. However, it was not possible to create an RMISocketFactory that produced more than one type of socket. Now, with the introduction of the class java.rmi.server.SocketType, it is possible to create a custom RMISocketFactory that produces the type of socket connection you want when you want on a per-object basis.

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 RMISocketFactory that Produces a Single Type of Socket

There are four steps to creating a custom RMISocketFactory, that produces a single type of socket.
  1. Decide upon the type of socket to be produced.
  2. Extend RMISocketFactory.
  3. Override the RMISocketFactory createSocket method.
  4. Override the RMISocketFactory 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 RMISocketFactory will produce sockets that provide data compression. We will produce examples.rmisocfac.CompressionSocket sockets from the page Creating a New Socket Type.

Step 2:
Extend RMISocketFactory

Begin the implementation of a custom RMISocketFactory by extending RMISocketFactory. The custom RMISocketFactory for this example will be called CompressionRMISocketFactory.

Below is the code for extending the class RMISocketFactory as well the code for steps three and four, overriding the createSocket and createServerSocket methods. An explanation of steps three and four follows the code example.

package examples.rmisocfac;

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

public class CompressionRMISocketFactory extends RMISocketFactory {

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

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

Step 3:
Override the RMISocketFactory createSocket method.

Since the function of an RMISocketFactory is to supply the RMI runtime with sockets, the CompressionRMISocketFactory needs to override the RMISocketFactory 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:
Override the RMISocketFactory createServerSocket method.

Overriding createServerSocket in your subclass of RMISocketFactory is almost identical to overriding createSocket, except createServerSocket needs to be overridden to create and return a socket of type CompressionServerSocket.

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


Creating an RMISocketFactory that Produces More Than One Type of Socket

To create an RMISocketFactory capable of producing more than one type of socket, you start by following the steps for creating a RMISocketFactory which produces a single socket type. Only a little more information is necessary.

An RMISocketFactory that produces more than one type of socket needs to provide a means of specifying which type of socket should be returned by createSocket and createServerSocket. Fortunately, the framework for this is already built into RMI. In addition to the methods

public Socket createSocket (String host, int port)
public ServerSocket createServerSocket (int port)

the class RMISocketFactory also declares the methods

public Socket createSocket (String host, int port, SocketType type)
public ServerSocket createServerSocket (int port, SocketType type)

which take objects of type SocketType as parameters. This additional parameter gives the RMISocketFactory access to methods and data specific to the type of socket being created. Typically, there is a SocketType object for each different type of socket.

The class SocketType has three data members.

private String protocol
private byte[] refData
private Object serverData

All three of these members are set by the programmer in the the constructor of SocketType. The member protocol refers to the name of the protocol a particular socket type is using to communicate. For example, if we were creating a SocketType object for the CompressionSocket from the above example, we could have chosen to set the member protocol to the string "compression". The other two members, refData and serverData can contain any extra data required by the protocol.

With this information in hand, we are now ready to create an RMISocketFactory that can produce more than one type of socket. For this example, the custom RMISocketFactory class will be named MultiRMISocketFactory, because it supports multiple socket types.

Now, following the steps to creating a custom RMISocketFactory outlined in the previous example, we will first decide which types of sockets to produce.

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

The custom RMISocketFactory 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.

For the XorSockets, the SocketType.protocol field will be set to "xor" in its constructor and for the CompressionSockets, the protocol field will be set to "compression".

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

            
package examples.rmisocfac;

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

public class MultiRMISocketFactory extends RMISocketFactory {
   
    /* Get the default RMISocketFactory */
    private RMISocketFactory defaultFactory 
        = RMISocketFactory.getDefaultSocketFactory();

    /* 
     * Override createSocket to call the default 
     * RMISocketFactory's createSocket method. This
     * way, you'll get a TCP connection if you don't
     * specify another SocketType
     */
    public Socket createSocket(String host, int port) 
        throws IOException 
    {
        return defaultFactory.createSocket(host, port);
    }

    /* 
     * Override createServerSocket to call the default
     * RMISocketFactory's createServerSocket method.
     */
    public ServerSocket createServerSocket(int port) 
        throws IOException 
    {
        return defaultFactory.createServerSocket(port);
    }

    /*
     * Override createSocket to create the type of socket 
     * specified by the SocketType parameter.
     */
    public Socket createSocket(String host, int port, SocketType type)
        throws IOException
    {
        Socket socket;
        String protocol = type.getProtocol();
        if(protocol.equals("xor")) 
            // Use default bit pattern for the XorSocket.
            socket = new XorSocket(host, port, (byte)0);
        else if (protocol.equals("compression")) 
            socket = new CompressionSocket(host, port);
        else 
            throw new IOException("protocol " + protocol + 
                " not supported");

        return socket;
    }

    /*
     * Override createServerSocket to create the type of socket
     * specified by the SocketType parameter.
     */
    public ServerSocket createServerSocket(int port, SocketType type)
        throws IOException
    {
        ServerSocket server;
        String protocol = type.getProtocol();
        if(protocol.equals("xor")) 
            // Use default pattern.
            server = new XorServerSocket(port, (byte)0);
        else if (protocol.equals("compression")) 
            server = new CompressionServerSocket(port);
        else 
            throw new IOException("protocol " + protocol + 
                " not supported");

        return server;
    }
}

Step 2:
Extend RMISocketFactory

The above class, MultiRMISocketFactory, extends RMISocketFactory.

Step 3:
Override the RMISocketFactory 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 with a parameter of type SocketType so that the appropriate type of socket can be produced.

Accordingly, the method

public Socket createSocket(String host, int port, SocketType type)

is overridden to create and return the appropriate type of socket for the given SocketType. If the protocol field of the parameter type 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.

Notice that an exception is thrown if createServer is passed a SocketType object whose protocol member is not one of the protocols supported by the current RMISocketFactory.

Step 4:
Override the RMISocketFactory createServerSocket method.

Overriding createServerSocket is almost identical to overriding createSocket. As in step three, the RMISocketFactory createServerSocket method, with a parameter of type SocketType, is overriden. Also as in step 3, the type of socket created and returned is determined by the protocol field of the SocketType parameter type.

Using Your Custom Socket Factory in an Application

There are two steps to using a custom RMISocketFactory in an application.
  1. In both the client and the server, set the socket factory to the custom RMISocketFactory.
  2. Optionally, in the remote object implementation, write a constructor that calls the UnicastRemoteObject constructor with the SocketType parameter.
Note: Step 2 is necessary when the RMISocketFactory you plan to use produces more than one type of socket, or when you want to use the flexible lookup mechanism available with JDK1.2Beta3. After you've finished reading this page, you may want to have a look at the  tutorial on Using the RMISocketFactory Flexible Lookup Mechanism.

Step 1:
Set the Socket Factory to the Custom RMISocketFactory.

When using a custom RMISocketFactory in an application, you must first set the current RMISocketFactory by calling the RMISocketFactory class method

public synchronized static void setSocketFactory(RMISocketFactory fac)

with an argument that is the same type as the RMISocketFactory you wish to set. This process is no more complex than setting the RMISecurityManager.

For example, to install a MultiRMISocketFactory instance as the RMISocketFactory, the following lines of code would be included in both the client and the server:

    try
    {
        RMISocketFactory.setSocketFactory(new MultiRMISocketFactory());
    } catch (IOException e) {
        System.err.println("Exception Occurred: " +
            "setSocketFactory failed"); 
        e.printStackTrace();
    }

Step 2:
Write a Remote Object Constructor that Calls the UnicastRemoteObject Constructor with the SocketType Parameter.

If you set a custom RMISocketfactory that supports more than one type of socket, then you need a way to tell the RMI runtime which type of socket 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, SocketType socketType)

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

    public HelloImpl(String s) throws RemoteException {
        super(0, new SocketType("xor", null, null));
        name = s;
    }
Notice that the UnicastRemoteObject constructor

protected UnicastRemoteObject(int port, SocketType socketType)

is called from the HelloImpl constructor.

Once your custom RMISocketFactory is set, sockets of the desired type will be used for your RMI client-server application. If you want to change from using a RMISocketFactory that produces only one type of socket to an RMISocketFactory that produces more than one type of socket, the transition is slightly more complex because you have to:
  1. Make sure you use the UnicastRemoteObject constructor with a parameter of type SocketType.
  2. Make sure you change any references to the old SocketType to references to the new SocketType.
Next is a version of the "Hello World" example that sets a RMISocketFactory of type MultiRMISocketFactory and then communicates 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. If you'd like to see how applets may use a custom RMISocketFactory, please refer to Using the RMISocketFactory Flexible Lookup Mechanism. 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 RMISocketFactory is set to a socket factory of type MultiRMISocketFactory at the beginning of main by calling to the method setSocketFactory. Otherwise the class HelloClient is no different than before.
        package examples.rmisocfac;
 
        import java.rmi.*;
        import java.rmi.server.*;
        import java.net.URL.*;
 
        public class HelloClient {
 
            private static String message = "";
        
            public static void main(String args[])
            {
                try {
                    // create and install a socket factory 
                    RMISocketFactory.setSocketFactory(new 
                        MultiRMISocketFactory());  
                        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.

First, the constructor has been changed to call the version of the UnicastRemoteObject constructor that takes a SocketType object as a parameter. Second, a piece of code was added to set the RMISocketFactory to a factory of type MultiRMISocketFactory.

        package examples.rmisocfac;

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

        public class HelloImpl
            extends UnicastRemoteObject
            implements Hello
        {
            /* Name of remote object in registry */
            private String name;

            /* 
             * Constructor calls constructor of superclass with
             * SocketType parameter.
             */
            public HelloImpl(String s) throws RemoteException {
                super(0, new SocketType("xor", null, null));
                name = s;
            }

            /* 
             * 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 socket factory
                try {
                    RMISocketFactory
                        .setSocketFactory(new MultiRMISocketFactory());
                } catch (IOException e) {
                    System.err.println("HelloImpl: setSocketFactory failed");
                    e.printStackTrace();
                }

                // Create and install a security manager
                System.setSecurityManager(new RMISecurityManager());

                try {
                    HelloImpl obj = new HelloImpl("HelloServer");
                    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 © 1996, 1997, 1998 Sun Microsystems, Inc. All rights reserved.