在網絡編程中,判斷兩台機器之間的連通性是基礎且關鍵的需求。雖然系統層面常用 ping 命令驗證,但 Java 標準庫並未直接提供等效的 API。本文將深入探討如何利用 java.net.InetAddress 和 java.net.Socket 等核心類,實現多場景下的網絡可達性檢測,並附帶完整代碼示例與輸出分析。
InetAddress 類提供了兩個 isReachable 方法:
boolean isReachable(int timeout)boolean isReachable(NetworkInterface netif, int ttl, int timeout)基本用法是通過目標 IP 構造 InetAddress 對象,調用 isReachable 測試連通性。由於遠端可能有多個 IP,實際應用中需迭代測試所有地址。
void isAddressAvailable(String ip) {
try {
InetAddress address = InetAddress.getByName(ip);
// 判斷 IP 版本
if (address instanceof Inet4Address) {
System.out.println(ip + " is ipv4 address");
} else if (address instanceof Inet6Address) {
System.out.println(ip + " is ipv6 address");
} else {
System.out.println(ip + " is unrecognized");
}
// 不指定網絡接口的測試
if (address.isReachable(5000)) {
System.out.println("SUCCESS - ping " + ip + " with no interface specified");
} else {
System.out.println("FAILURE - ping " + ip + " with no interface specified");
}
// 遍歷所有本地網絡接口測試
System.out.println("\n-------Trying different interfaces--------\n");
Enumeration<NetworkInterface> netInterfaces = NetworkInterface.getNetworkInterfaces();
while (netInterfaces.hasMoreElements()) {
NetworkInterface ni = netInterfaces.nextElement();
System.out.println("Checking interface, DisplayName:" + ni.getDisplayName() + ", Name:" + ni.getName());
if (address.isReachable(ni, 0, 5000)) {
System.out.println("SUCCESS - ping " + ip);
} else {
System.out.println("FAILURE - ping " + ip);
}
Enumeration<InetAddress> ips = ni.getInetAddresses();
while (ips.hasMoreElements()) {
System.out.println("IP: " + ips.nextElement().getHostAddress());
}
System.out.println("-------------------------------------------");
}
} catch (Exception e) {
System.out.println("error occurs.");
e.printStackTrace();
}
}
--------------START--------------
10.13.20.70 is ipv4 address
SUCCESS - ping 10.13.20.70 with no interface specified
-------Trying different interfaces--------
Checking interface, DisplayName:MS TCP Loopback interface, Name:lo
FAILURE - ping 10.13.20.70
IP: 127.0.0.1
-------------------------------------------
Checking interface, DisplayName:Intel(R) Centrino(R) Advanced-N 6200 AGN - Teefer2 Miniport, Name:eth0
FAILURE - ping 10.13.20.70
IP: 9.123.231.40
-------------------------------------------
Checking interface, DisplayName:Intel(R) 82577LM Gigabit Network Connection - Teefer2 Miniport, Name:eth1
SUCCESS - ping 10.13.20.70
-------------------------------------------
Checking interface, DisplayName:WAN (PPP/SLIP) Interface, Name:ppp0
SUCCESS - ping 10.13.20.70
IP: 10.0.50.189
-------------------------------------------
--------------END--------------
從輸出可見,isReachable 不指定接口時由系統自動選擇路由;指定接口則能精確判斷數據包從哪個網卡發出。但 Java 無法直接訪問 ICMP 協議,也無法得知本地哪個 IP 可被遠端訪問,這引出了下一節的 Socket 方案。
某些場景(如 FTP 服務回連、事件通知註冊)需確定本地哪個 IP 可被遠端訪問。由於單網卡可綁定多 IP,InetAddress.isReachable 無法區分。解決思路:枚舉本地所有 IP,嘗試建立 TCP 連接到遠端開放端口(如 SSH 22、ECHO 7),連接成功即表示該本地 IP 可達遠程網絡。
boolean isReachable(InetAddress localInetAddr, InetAddress remoteInetAddr, int port, int timeout) {
boolean isReachable = false;
Socket socket = null;
try {
socket = new Socket();
// 端口設為 0 讓系統自動分配可用端口
SocketAddress localSocketAddr = new InetSocketAddress(localInetAddr, 0);
socket.bind(localSocketAddr);
InetSocketAddress endpointSocketAddr = new InetSocketAddress(remoteInetAddr, port);
socket.connect(endpointSocketAddr, timeout);
System.out.println("SUCCESS - connection established! Local: " + localInetAddr.getHostAddress() + " remote: " + remoteInetAddr.getHostAddress() + " port " + port);
isReachable = true;
} catch (IOException e) {
System.out.println("FAILURE - CAN not connect! Local: " + localInetAddr.getHostAddress() + " remote: " + remoteInetAddr.getHostAddress() + " port " + port);
} finally {
if (socket != null) {
try { socket.close(); } catch (IOException e) { /* ignore */ }
}
}
return isReachable;
}
void printReachableIP(InetAddress remoteAddr, int port) {
String retIP = null;
try {
Enumeration<NetworkInterface> netInterfaces = NetworkInterface.getNetworkInterfaces();
while (netInterfaces.hasMoreElements()) {
NetworkInterface ni = netInterfaces.nextElement();
Enumeration<InetAddress> localAddrs = ni.getInetAddresses();
while (localAddrs.hasMoreElements()) {
InetAddress localAddr = localAddrs.nextElement();
if (isReachable(localAddr, remoteAddr, port, 5000)) {
retIP = localAddr.getHostAddress();
break;
}
}
}
} catch (SocketException e) {
System.out.println("Error occurred while listing all the local network addresses.");
}
if (retIP == null) {
System.out.println("NULL reachable local IP is found!");
} else {
System.out.println("Reachable local IP is found, it is " + retIP);
}
}
--------------START--------------
FAILURE - CAN not connect! Local: 127.0.0.1 remote: 10.8.1.50 port 22
FAILURE - CAN not connect! Local: 9.123.231.40 remote: 10.8.1.50 port 22
SUCCESS - connection established! Local: 10.0.50.189 remote: 10.8.1.50 port 22
Reachable local IP is found, it is 10.0.50.189
--------------END--------------
結果清晰顯示:只有 10.0.50.189 能通過 SSH 端口連通遠端,這正是遠端可回連的有效地址。
在雙棧網絡中,可通過以下優化提升效率:
// 僅處理同協議族地址對
if (!((localInetAddr instanceof Inet4Address && remoteInetAddr instanceof Inet4Address) ||
(localInetAddr instanceof Inet6Address && remoteInetAddr instanceof Inet6Address))) {
continue; // 跳過跨協議組合
}
// 跳過環回、任意、鏈路本地地址
if (localAddr.isLoopbackAddress() || localAddr.isAnyLocalAddress() || localAddr.isLinkLocalAddress()) {
continue;
}
本文系統介紹了三種 Java 網絡可達性檢測方案:
對於更複雜的需求(如 ICMP 級別控制、跨平台底層調用),可考慮 JNI 方案,但會引入平台相關性。實際項目中應根據業務場景靈活選擇並組合上述方法。