Friday, July 14, 2006

Java RMI Service on Multiple Home (IP address) Computer

Synopsis

When a Java RMI service is started on a server that has multiple IP addresses belonging to different network enabled, the service is accessible from only one of the network.

Reason

The reason why it can work on only one of the network is because when the remote object is exported, the default endpoint is accociated to one of the IP addresse. Later when the stub
tries to contact the remote object, it uses the IP defined in the endpoint to make the connection.
If the invoking is initiated from another network that does not has any route to the IP specified in the endpoint, the network can not be established and the invoking will certainly
result in failure.

Solution

The way to solve this problem is to write customized client socket factory to avoid using the default endpoint.

The thoery behind this is because as a TCP/IP server, the RMI service itself is available through all of the IPs by default, and actually the remote stub is retrievable through
every one of the IP. It does not work on some of the IPs is just becuase it will fail at the phase to
invoke the remote method provided by the remote object. it will try to contact a remote object
resides in another network.

In one of my solution, I put all the supported server IP addresses into the client socket factory and "ping" them until one is available before openning a socket from client to the proper server that is represented by the proper IP address.

The ideal way is to use the IP of the server directly. But I'd like to leave somebody else to enhance it.

The Sample Code


public class MSJRMIClientSocketFactory implements RMIClientSocketFactory,
Serializable {
protected ArrayList hostIPs = new ArrayList();
private static final long serialVersionUID = 57282513633491000L;
public MSJRMIClientSocketFactory() throws Exception {
Enumeration nics = null;
try {
nics = NetworkInterface.getNetworkInterfaces();
} catch (SocketException e) {
throw new Exception("There's no network interface available.");
}
while (nics != null && nics.hasMoreElements()) {
java.net.NetworkInterface ni = (java.net.NetworkInterface) (nics
.nextElement());
Enumeration eoip = ni.getInetAddresses();
while (eoip.hasMoreElements()) {
InetAddress ip = (InetAddress) eoip.nextElement();
if (!"127.0.0.1".equals(ip.getHostAddress()))
hostIPs.add(ip);
}
}
}
// isReachable is not the expected method
// the port is used to connect to the remote service that is on the remote
// computer
protected String getFirstLiveIP(int port) {
String theLiveIP = null;
Socket testSocket = null;
for (int i = 0; i < ip =" (InetAddress)" testsocket =" new" theliveip =" ip.getHostAddress();" reachablehost =" null;" reachablehost =" getFirstLiveIP(port);">


Use it in the code

If your remote object is already a type of UnicastRemoteObject, you simply use the constructor of
super(port,ClientSocketfactory,ServerSocketfactory)
to initiate it.
Since the socket returned is still the normal socket, so the server socket factory is not
need to be customized. Simple give it a null instead.

If you are using the "exportObject" method to get a stub, similiar to above, initiate the client
socket factory and use it as one of the parameter.

No comments: