These methods are simply translations of the standard constructors for Socket
and ServerSocket
. That is, you can imagine writing an implementation of RMIClient
- SocketFactory
that simply called a constructor on the Socket
class, as in the following code snippet:
public Socket createSocketString host, int port { return new Socket host, port;
} It turns out that writing the complete socket factory is only a little bit more subtle than this. Heres
the complete code for a client socket factory that uses our MonitoringSocket
: public class MonitoringSocket_RMIClientSocketFactory implements
RMIClientSocketFactory, Serializable { private int _hashCode =
MonitoringSocket_RMIClientSocketFactory.hashCode ; public Socket createSocketString host, int port {
try { return new MonitoringSockethost, port;
} catch IOException e{}
return null; }
public boolean equalsObject object { if object instanceof
MonitoringSocket_RMIClientSocketFacto ry { return true;
} return false;
} public int hashCode {
return _hashCode; }
} This class does three things: it creates instances of
MonitoringSocket , it implements
Serializable , and it overrides
equals and
hashCode correctly. Its clear why our
socket factory needs to create instances of MonitoringSocket
; but the other two tasks are just as important.
18.1.1.1 Implementing Serializable
An implementation of RMIClientSocketFactory
needs to implement Serializable
because the type of socket that a server uses is entirely a server-side property. In order for the client to even connect to the server, it must already be using the correct socket type. In order for
the RMI runtime on the client to find out what type of socket to use, it must deserialize an instance of the appropriate socket factory. This happens automatically when the client
deserializes the stub™the stub has a reference to an instance of an implementation of
RMIClientSocketFactory . When the stub is bound into a naming service, serialization
creates a copy of the correct socket factory. When the client obtains the stub from the naming service, it also obtains a copy of the socket factory, and can therefore connect to the server.
18.1.1.2 Implementing equals and hashCode
The RMI runtime uses the socket factory instance in two ways: to create sockets and to index already existing sockets. In other words, the RMI runtime performs the following sequence of
actions when a stub wants to send a message:
1. The runtime gets the stubs instance of RMIClientSocketFactory
from the stub. 2. The runtime then uses the stubs instance of
RMIClientSocketFactory as a key into
a hashtable of open but currently unused sockets. 3. If this retrieval fails, the RMI runtime then uses the stubs instance of
RMIClientSocketFactory to create a socket.
4. When the remote method invocation is finished, the client runtime returns the socket to the hashtable in step 2.
You may think that theres a better object to use as the hashtable key in step 2. For example, the stub itself may seem like an obvious choice. However, the stub doesnt work. Why? Two stubs to
distinct servers, even if theyre using the same socket factory, have to return different hashcodes and return
false when
equals is called. Using the stubs to index sockets
means that sockets can be reused across calls to a specific server but not across calls to distinct servers.
You cant use the stubs class object or the class of the stubs instance of RMIClientSocketFactory
either. Using either of these as the key to the hashtable ignores the possibility that the client socket factory has state and may return differently configured sockets at
different times. Consider, for example, the following, perfectly legal, client socket factory: public class PropertyBasedMonitoringSocket_RMIClientSocketFactory
implements RMIClientSocketFactory, Serializable {
private static final String USE_MONITORING_SOCKETS_PROPERTY = com.ora.rmibook.useMonitoringSockets;
private static final String TRUE = true; private int _hashCode =
PropertyBasedMonitoringSocket_RMIClientSocketFactory. hashCode ;
private boolean _isMonitoringOn; public PropertyBasedMonitoringSocket_RMIClientSocketFactory
{ String monitoringProperty = System .getProperty
USE_MONITORING_SOCKETS_PROPERTY; if null=monitoringProperty monitoringProperty.
equalsIgnoreCaseTRUE { _isMonitoringOn = true;
_hashCode++; }
else { _isMonitoringOn = false;
} return;
} public Socket createSocketString host, int port {
try { if _isMonitoringOn {
return new MonitoringSockethost, port; }
else { return new Sockethost, port;
} }
catch IOException e{} return null;
} public boolean equalsObject object {
if object instanceof PropertyBasedMonitoringSocket_RMIClientSocketFactory
{ return true;
} return false;
} public int hashCode {
return _hashCode; }
} This does something a little unusual. As part of initialization, it checks a system property to see
whether monitoring sockets should be used. Different servers may be running in different JVMs and have different settings for this value especially since, as written, this depends on a
parameter that can be set from the command line using a -D
argument. Consequently, the following two command-line invocations will result in different types of sockets being used:
java -Dcom.ora.rmibook.useMonitoringSockets=false ... java -Dcom.ora.rmibook.useMonitoringSockets=true ...
That a factory can have state has another interesting consequence. Serializing the socket factory can cause
problems. If a server is launched and a stub is serialized, it would be very inconvenient for the socket factory to change
the socket type afterwards. Any client that downloaded the old stub would wind up using the wrong type of socket when
attempting to connect to the server. Practically speaking, this means that socket factories are configured very early in the
server JVMs lifecycle, usually by system parameters, as we did in the code example.
However, if we use the stubs class object or the socket factorys class object as the key into the hashtable of available sockets, this distinction between the servers would be lost, and the wrong
type of socket might be used.
Almost all of this discussion applies equally well to the implementation of an RMIServerSocketFactory
. A factory that produces server sockets isnt sent over the wire to a client, so it need not be serializable.
On the other hand, the servers RMI runtime uses equals
and hashCode
internally. Because most servers in any given application use the same types of sockets, it makes sense to
have servers share sockets. In order to handle this, RMI maintains a mapping from socket
factories to open sockets. When a server needs a socket, RMI uses a map, which is keyed on the associated socket factory to see if there are any available sockets. In order for RMI to do this
effectively, we must override equals
and hashCode
. Heres our implementation of a server socket factory using
MonitoringSocket :
public class MonitoringSocket_RMIServerSocketFactory implements RMIServerSocketFactory, Serializable {
private int _hashCode = MonitoringSocket_RMIServerSocketFactory.hashCode ;
public ServerSocket createServerSocketint port { try {
return new MonitoringServerSocketport; }
catch IOException e{} return null;
} public boolean equalsObject object {
if object instanceof MonitoringSocket_RMIServerSocketFactory {
return true; }
return false; }
public int hashCode { return _hashCode;
} }
Deployment
Ive mentioned previously that, in most cases, the stub classes need to be deployed with the clients. The reality is actually a little more
complicated. The stub classes also need to be on the classpath of any naming services that are involved, and they need to be on the classpath
of the activation daemon as well.
The same holds true for socket factories. The associated classes need to be available to the naming service and the activation daemon, as well as
to the client.
In addition, theres a security issue. Code that uses nonst andard sockets needs special security exemptions, called socket permissions, in order to
execute. And these permissions must also be set at the naming service and the activation daemon.
Well discuss security permissions more thoroughly in Chapt er 20
.
18.2 Incorporating a Custom Socket into an Application