生產環境常需獲取服務器真實 IP 和 MAC(如註冊服務發現、分布式鎖、審計日誌)。本文提供生產級工具類,自動過濾回環、虛擬、未啟用網卡,優先返回 IPv4 物理網卡地址,並補充作業系統判斷、HTTP 代理設置等常用網絡工具方法。
import java.net.*;
import java.util.Enumeration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public final class NetworkUtil {
private static final Logger log = LoggerFactory.getLogger(NetworkUtil.class);
/** 獲取首個有效 IPv4 地址 (過濾回環、虛擬、未啟用網卡) */
public static String getLocalIpAddress() {
try {
Enumeration interfaces = NetworkInterface.getNetworkInterfaces();
while (interfaces.hasMoreElements()) {
NetworkInterface ni = interfaces.nextElement();
// 關鍵過濾條件
if (ni.isLoopback() || ni.isVirtual() || !ni.isUp()) continue;
Enumeration addresses = ni.getInetAddresses();
while (addresses.hasMoreElements()) {
InetAddress addr = addresses.nextElement();
if (addr instanceof Inet4Address && !addr.isLoopbackAddress()) {
return addr.getHostAddress();
}
}
}
} catch (SocketException e) {
log.error("獲取 IP 失敗", e);
}
return "";
}
/** 獲取對應物理網卡的 MAC 地址 (格式: XX-XX-XX-XX-XX-XX) */
public static String getMacAddress() {
try {
Enumeration interfaces = NetworkInterface.getNetworkInterfaces();
while (interfaces.hasMoreElements()) {
NetworkInterface ni = interfaces.nextElement();
if (ni.isLoopback() || ni.isVirtual() || !ni.isUp()) continue;
byte[] mac = ni.getHardwareAddress();
if (mac != null && mac.length == 6) {
StringBuilder sb = new StringBuilder(17);
for (int i = 0; i < mac.length; i++) {
sb.append(String.format("%02X", mac[i]));
if (i < mac.length - 1) sb.append('-');
}
return sb.toString();
}
}
} catch (SocketException e) {
log.error("獲取 MAC 失敗", e);
}
return "";
}
/** 綁定 IP 與 MAC(確保來自同一張網卡) */
public static Pair getIpAndMac() {
try {
Enumeration interfaces = NetworkInterface.getNetworkInterfaces();
while (interfaces.hasMoreElements()) {
NetworkInterface ni = interfaces.nextElement();
if (ni.isLoopback() || ni.isVirtual() || !ni.isUp()) continue;
byte[] mac = ni.getHardwareAddress();
if (mac == null || mac.length != 6) continue;
Enumeration addresses = ni.getInetAddresses();
while (addresses.hasMoreElements()) {
InetAddress addr = addresses.nextElement();
if (addr instanceof Inet4Address && !addr.isLoopbackAddress()) {
StringBuilder sb = new StringBuilder(17);
for (int i = 0; i < mac.length; i++) {
sb.append(String.format("%02X", mac[i]));
if (i < mac.length - 1) sb.append('-');
}
return new Pair<>(addr.getHostAddress(), sb.toString());
}
}
}
} catch (SocketException e) {
log.error("獲取 IP+MAC 失敗", e);
}
return new Pair<>("", "");
}
}
| 過濾條件 | 方法 | 作用 |
|---|---|---|
| 回環網卡 | ni.isLoopback() | 排除 127.0.0.1 / ::1 等本地回環介面 |
| 虛擬網卡 | ni.isVirtual() | 排除 Docker(veth)、VPN(tun)、Hyper-V 等虛擬介面 |
| 未啟用 | !ni.isUp() | 排除禁用、未連接、拔掉網線的介面 |
| IPv4 優先 | addr instanceof Inet4Address | 業務多用 IPv4,IPv6 可作備選 |
| 非回環地址 | !addr.isLoopbackAddress() | 雙重保險,排除 127.x.x.x |
public enum OsType { WINDOWS, MAC, LINUX, OTHER }
public static OsType getOsType() {
String os = System.getProperty("os.name").toLowerCase();
if (os.contains("win")) return OsType.WINDOWS;
if (os.contains("mac") || os.contains("darwin")) return OsType.MAC;
if (os.contains("nix") || os.contains("nux") || os.contains("aix")) return OsType.LINUX;
return OsType.OTHER;
}
public static void setHttpProxy(String host, int port, String nonProxyHosts) {
System.setProperty("http.proxyHost", host);
System.setProperty("http.proxyPort", String.valueOf(port));
System.setProperty("https.proxyHost", host);
System.setProperty("https.proxyPort", String.valueOf(port));
if (nonProxyHosts != null) {
System.setProperty("http.nonProxyHosts", nonProxyHosts);
System.setProperty("https.nonProxyHosts", nonProxyHosts);
}
}
public static void clearHttpProxy() {
System.clearProperty("http.proxyHost");
System.clearProperty("http.proxyPort");
System.clearProperty("https.proxyHost");
System.clearProperty("https.proxyPort");
System.clearProperty("http.nonProxyHosts");
System.clearProperty("https.nonProxyHosts");
}
注意:系統屬性代理對 `HttpURLConnection`、`HttpClient` (舊版)、`WebService` 等生效,**不影響** OkHttp、Netty 等自帶連接池的客戶端,需單獨配置。
public static void printAllInterfaces() {
try {
Enumeration nis = NetworkInterface.getNetworkInterfaces();
while (nis.hasMoreElements()) {
NetworkInterface ni = nis.nextElement();
System.out.printf("Name: %s, Display: %s, Up: %b, Loopback: %b, Virtual: %b, MAC: %s%n",
ni.getName(), ni.getDisplayName(), ni.isUp(), ni.isLoopback(), ni.isVirtual(),
formatMac(ni.getHardwareAddress()));
Enumeration addrs = ni.getInetAddresses();
while (addrs.hasMoreElements()) {
InetAddress addr = addrs.nextElement();
System.out.printf(" IP: %s (%s)%n", addr.getHostAddress(),
addr instanceof Inet4Address ? "IPv4" : "IPv6");
}
}
} catch (SocketException e) { log.error("列舉網卡失敗", e); }
}
| 現象 | 可能原因 | 排查步驟 |
|---|---|---|
| 返回空字串 | 無符合條件的網卡 | 調用 `printAllInterfaces()` 查看實際網卡狀態 |
| 獲取到 Docker 虛擬 IP | 未過濾 isVirtual() | 確認 JDK 版本支援 isVirtual() (Java 8u151+) |
| MAC 為 null | 某些虛擬/隧道介面無 MAC | 增加 `mac != null && mac.length == 6` 校驗 |
| Windows 多網卡順序不定 | Enumeration 順序不保證 | 按介面名稱排序 (如優先 Ethernet、Wi-Fi) |