在RMI调用中,有三个角色:Server,Client,Registry。
Server向Registry注册自己提供的服务的连接信息,其中包含Server自己的IP;Client通过Registry获取Server的连接信息,然后连接Server,调用Server提供的服务。
这三者可以在不同的机器上,RMI也是按照这样的场景设计的。虽然经常会遇到Server和Registry在同一个机器上甚至同一个Java进程里情况,尤其是测试的时候。
当Server所在的机器有多个IP并且Client和Server不在同一个机器的时候,问题来了:
Client并不知道Server的IP,它是通过Registry获得Server的IP(这个描述不严谨,实际上Client通过Registry获得的并不只是一个IP)。
而Registry和Server也都无法自动的确定Client应该用Server的哪个IP和Server通讯。
所以Server向Registry注册的自己的连接信息的时候,连接信息里面的IP,只是随便从自己的多个IP里选择了一个。
这个随便选择的IP,不一定是Client能用来和Server通讯的IP。
然后Client连接Server的时候就会ConnectException。
官方文档给的解决方案是设置Server所在的jvm的系统属性java.rmi.server.hostname,然后Client会通过设置的值连接Server。
准确的说是Server向Registry注册自己的连接信息的时候,不再是随便选择一个IP,而是java.rmi.server.hostname的值。
那如果无法在Server端正确设置java.rmi.server.hostname的值,比如Server有两个IP:192.168.1.2和10.0.0.2,然后有两个Client分别使用这两个IP才能和Server通讯,那就不能设置java.rmi.server.hostname为确定的值了。
这样的情况怎么办了?
前面已经说了,Client通过Registry获得Server的IP,这种说法是不严谨的。
实际上,Client获得的是一个java.rmi.server.RMIClientSocketFactory的实例。
参见java.rmi.server.UnicastRemoteObject的构造函数UnicastRemoteObject(int port, RMIClientSocketFactory csf, RMIServerSocketFactory ssf) throws RemoteException
或者静态函数exportObject(Remote obj, int port, RMIClientSocketFactory csf, RMIServerSocketFactory ssf) throws RemoteException
这两个函数里的RMIClientSocketFactory类型的参数,是在Server端创建,然后传到Client端,在Client连接Server的时候调用的。
这意味着,在Client连接Server之前,就已经拿到这个在Server端创建的对象了。
所以可以通过这个对象传递一些信息到Client,以协助Client正确的连接Server。
比如,把Server所有的IP都放到RMIClientSocketFactory对象里,然后在Client连接Server的时候一个一个的试。
像这样:
import java.io.IOException;
import java.io.Serializable;
import java.net.Socket;
import java.rmi.server.RMIClientSocketFactory;
public class RMIClientSocketFactoryImpl implements RMIClientSocketFactory, Serializable {
private static final long serialVersionUID = -2866768131646972083L;
@Override
public Socket createSocket(String host, int port) throws IOException {
return new Socket(host, port);
}
}
import java.io.IOException;
import java.io.Serializable;
import java.net.InetAddress;
import java.net.Socket;
import java.rmi.server.RMIClientSocketFactory;
public class TryReachBeforeCreateSocketFactory implements RMIClientSocketFactory, Serializable {
private static final long serialVersionUID = 270963626626446102L;
private String[] ips;
private RMIClientSocketFactory socketFactory;
private transient String ip = null;
public TryReachBeforeCreateSocketFactory(String[] localIps, RMIClientSocketFactory clientSocketFactory) {
ips = localIps.clone();
socketFactory = clientSocketFactory;
}
@Override
public Socket createSocket(String host, int port) throws IOException {
if(ip != null) {
return socketFactory.createSocket(ip, port);
}
for (int i = 0; i < ips.length; i++) {
String trying = ips[i];
try {
if (InetAddress.getByName(trying).isReachable(3000)) {
Socket socket = socketFactory.createSocket(trying, port);
ip = trying;
return socket;
}
} catch (IOException e) {
}
}
return socketFactory.createSocket(host, port);
}
}