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.
Step 1:
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.
Decide Upon the Type of Socket to be ProducedFor 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:
Begin the implementation of a custom RMISocketFactory by extending RMISocketFactory. The custom RMISocketFactory for this example will be called CompressionRMISocketFactory.
Extend RMISocketFactoryBelow 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:
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.
Override the RMISocketFactory createSocket method.Step 4:
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.
Override the RMISocketFactory createServerSocket method.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.
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:
The custom RMISocketFactory will produce three types of sockets: XorSocket, CompressionSocket and the default, java.net.Socket.
Decide Upon the Type of Socket to be ProducedThe 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:
The above class, MultiRMISocketFactory, extends RMISocketFactory.
Extend RMISocketFactoryStep 3:
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.
Override the RMISocketFactory createSocket method.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:
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.
Override the RMISocketFactory createServerSocket method.
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:Step 1:
When using a custom RMISocketFactory in an application, you must first set the current RMISocketFactory by calling the RMISocketFactory class method
Set the Socket Factory to the Custom RMISocketFactory.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:
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:
Write a Remote Object Constructor that Calls the UnicastRemoteObject Constructor with the SocketType Parameter.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 constructorprotected UnicastRemoteObject(int port, SocketType socketType)
is called from the HelloImpl constructor.
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; }
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(); } } }
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(); } } }
Directions on how to compile and run the above "Hello World" example can be found here.