筆者最近在作產品,其中一個環節用到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; }