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 Java TM 2
SDK, v1.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 Java SDK, v1.2 Beta3, 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 Java 2 SDK, v1.2, 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.
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.
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.
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
public class MultiServerSocketFactory implements RMIServerSocketFactory, Serializable { /* * Get the default RMISocketFactory */ private static RMISocketFactory defaultFactory
= RMISocketFactory.getDefaultSocketFactory();
/* * 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]);
Step 5:
Implement the RMIServerSocketFactory createServerSocket method.
Overriding createServerSocket is almost identical to overriding
createSocket. As in step three, the RMIServerSocketFactorycreateServerSocket 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:
In the remote object implementation, write a constructor that calls the
UnicastRemoteObject (or Activatable) constructor that
takes RMIClientSocketFactory and RMIServerSocketFactory
parameters.
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
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:
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.
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.
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());