投机取巧解决多IP时RMI调用的java.rmi.ConnectException

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

}

你可能感兴趣的:(投机取巧解决多IP时RMI调用的java.rmi.ConnectException)