在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); } }