解決InetAddress.isReachable(timeout)在windows xp始終返回false的bug

筆者最近在作產品,其中一個環節用到ping測試主機是否在線。java

開發環境:Windows 7 64bit+JDK1.8 x64編程

如下是檢測主機是否在線,開發環境中測試經過windows

public static boolean hostAvailabilityCheck(String host,int timeout){        
        try {                        
            InetAddress inet = InetAddress.getByName(host);
            System.out.println("Sending Ping Request to " + inet);
            if(inet.isReachable(timeout)){                
                System.out.println("Host is reachable");                
                return true;
            }
        } catch (IOException e) {
            System.out.println("Host is NOT reachable");
            e.printStackTrace();
        }
        return false;
    }

正常運行結果api

當在windows XP下就運行不正常了,inet.isReachable處始終返回false。Google了一番(java inetaddress isreachable windows xp did not work),安全

肯定這是個bug,有多是由於Microsoft放棄了Windows XP等再也不維護的操做系統,Oracle公司也再也不向後兼容這些系統了。網上反饋bug的比比皆是。網絡

JDK-5061568 : java.net.InetAddress.isReachable() kills Windows networking文章說這個bug會一直重現(This bug can be reproduced always.)框架

JDK-5061571 : InetAddress#isReachable does not send PINGs but only TCP echos函數

JDK-6595834 : InetAddress.isReachable is not thread safe when using ICMP ECHO.測試

以上三篇文章就指出了這個方法存在的一些問題:非線程安全、只發送TCP ECHO,對於框架設計人員來講,做爲客戶端編程人員咱們關注的是api是否this

可以達到預期目的,但是jdk1.8.0_144仍舊沒有解決這個問題。

筆者最終經過網絡搜索,在stackoverflow上找到了解決方法。

解決方法一(推薦):

出處:Why does InetAddress.isReachable return false, when I can ping the IP address?

// in case of Linux change the 'n' to 'c' 若是是Linux系統,請將n改爲c
    Process p1 = java.lang.Runtime.getRuntime().exec("ping -n 1 www.google.com");
    int returnVal = p1.waitFor();
    boolean reachable = (returnVal==0);

windows系統批處理返回值非0即失敗。因此,若是以上返回爲0說明ping的通,若是返回2則失敗。

解決方法二:

出處:Odd InetAddress.isReachable() issue

說明:須要第三方庫JNA支持,須要必定的操做系統知識。此方法將會經過JNA調用COM組件來調用Windows API函數,

相關DLL是IPHLPAPI.DLL和 WSOCK32.DLL

public interface InetAddr extends StdCallLibrary {
    InetAddr INSTANCE = (InetAddr) 
            Native.loadLibrary("wsock32.dll", InetAddr.class);

    ULONG inet_addr(String cp);                     //in_addr creator. Creates the in_addr C struct used below
}

public interface IcmpEcho extends StdCallLibrary {
    IcmpEcho INSTANCE = (IcmpEcho)
            Native.loadLibrary("iphlpapi.dll", IcmpEcho.class);

    int IcmpSendEcho(
            HANDLE IcmpHandle,                      //Handle to the ICMP
            ULONG DestinationAddress,               //Destination address, in the form of an in_addr C Struct defaulted to ULONG
            Pointer RequestData,                    //Pointer to the buffer where my Message to be sent is
            short RequestSize,                      //size of the above buffer. sizeof(Message)
            byte[] RequestOptions,                  //OPTIONAL!! Can set this to NULL
            Pointer ReplyBuffer,                    //Pointer to the buffer where the replied echo is written to
            int ReplySize,                          //size of the above buffer. Normally its set to the sizeof(ICMP_ECHO_REPLY), but arbitrarily set it to 256 bytes
            int Timeout);                           //time, as int, for timeout

    HANDLE IcmpCreateFile();                        //win32 ICMP Handle creator

    boolean IcmpCloseHandle(HANDLE IcmpHandle);     //win32 ICMP Handle destroyer
}
public void SendReply(String ipAddress) {
    final IcmpEcho icmpecho = IcmpEcho.INSTANCE;
    final InetAddr inetAddr = InetAddr.INSTANCE;
    HANDLE icmpHandle = icmpecho.IcmpCreateFile();
    byte[] message = new String("thisIsMyMessage!".toCharArray()).getBytes();
    Memory messageData = new Memory(32);                    //In C/C++ this would be: void *messageData = (void*) malloc(message.length);
    messageData.write(0, message, 0, message.length);       //but ignored the length and set it to 32 bytes instead for now
    Pointer requestData = messageData;
    Pointer replyBuffer = new Memory(256);
    replyBuffer.clear(256);

    // HERE IS THE NATIVE CALL!!
    reply = icmpecho.IcmpSendEcho(icmpHandle, 
            inetAddr.inet_addr(ipAddress), 
            requestData, 
            (short) 32, 
            null, 
            replyBuffer, 
            256, 
            timeout);
    // NATIVE CALL DONE, CHECK REPLY!!

    icmpecho.IcmpCloseHandle(icmpHandle);
}

public boolean IsReachable () {
    return (reply > 0);
}

筆者簡單封裝了一下代碼

class IcmpEchoPatch{        
    int reply;
    public void SendReply(String host,int timeout) {
        final IcmpEcho icmpecho = IcmpEcho.INSTANCE;
        final InetAddr inetAddr = InetAddr.INSTANCE;
        HANDLE icmpHandle = icmpecho.IcmpCreateFile();
        byte[] message = new String("thisIsMyMessage!".toCharArray()).getBytes();
        Memory messageData = new Memory(32);                    //In C/C++ this would be: void *messageData = (void*) malloc(message.length);
        messageData.write(0, message, 0, message.length);       //but ignored the length and set it to 32 bytes instead for now
        Pointer requestData = messageData;
        Pointer replyBuffer = new Memory(256);
        replyBuffer.clear(256);

        // HERE IS THE NATIVE CALL!!
        reply = icmpecho.IcmpSendEcho(icmpHandle, 
                inetAddr.inet_addr(host), 
                requestData, 
                (short) 32, 
                null, 
                replyBuffer, 
                256, 
                timeout);
        // NATIVE CALL DONE, CHECK REPLY!!
        icmpecho.IcmpCloseHandle(icmpHandle);
    }

    public boolean IsReachable () {
        return (reply > 0);
    }
}

 

 最終hostAvailabilityCheck方法代碼以下:

public static boolean hostAvailabilityCheck(String host,int timeout){        
        try {
            
            HardWareIdentifier resolver = HardWareIdentifier.getDefault(); //替換爲你的方法
            if(resolver.isMinSupportPlat()){ //替換爲你的方法判斷操做系統
                IcmpEchoPatch ping = new IcmpEchoPatch();
                ping.SendReply(host, timeout);
                if(ping.IsReachable()){
                    System.out.println("Host is reachable");                
                    return true;
                }
            }else{
                InetAddress inet = InetAddress.getByName(host);
                System.out.println("Sending Ping Request to " + inet);
                if(inet.isReachable(timeout)){                
                    System.out.println("Host is reachable");                
                    return true;
                }
            }
        } catch (IOException e) {
            System.out.println("Host is NOT reachable");
            e.printStackTrace();
        }
        return false;
    }
相關文章
相關標籤/搜索