常规获取ip方法
import java.net.*;
import java.util.List;
import java.util.logging.Logger;
public class NetworkIpUtils {
// 示例:指定要查询的网络接口名称(如 "eth0"、"wlan0",需根据实际环境修改)
private static final String INTERFACE_NAME = "wlan0";
public static String getIpv4() {
try {
NetworkInterface ni = getInterface(INTERFACE_NAME);
if (ni != null) {
List<InterfaceAddress> addresses = ni.getInterfaceAddresses();
for (InterfaceAddress add : addresses) {
InetAddress iad = add.getAddress();
// 过滤 IPv4 且非回环地址
if (iad instanceof Inet4Address && !iad.isLoopbackAddress()) {
log.info("ip4: " + iad.getHostAddress());
}
}
} else {
log.warning("未找到名称为 " + INTERFACE_NAME + " 的网络接口");
}
} catch (SocketException e) {
log.severe("获取网络接口信息失败:" + e.getMessage());
}
}
// 补充实现 getInterface 方法:根据名称获取 NetworkInterface
private static NetworkInterface getInterface(String interfaceName) throws SocketException {
return NetworkInterface.getByName(interfaceName);
}
}
实际结果是,可能有多个ip4: xx打印,也就是说我们在同一张网卡上有多个ip
网卡能有多个 IP 的常见原因
几个最常见的场景,解释为什么一个网卡会有多个 IP:
- 同时存在 IPv4 和 IPv6 地址(最常见)
现代操作系统默认会给网卡同时分配IPv4 地址和IPv6 地址,你的代码里通过iad instanceof Inet4Address过滤了 IPv6,但如果不过滤的话,会看到明显的两个 IP(比如 192.168.1.100 和 fe80::xxxx:xxxx:xxxx:xxxx)。
你的代码只打印 IPv4,所以如果网卡只有一个 IPv4 + 一个 IPv6,最终只输出 1 个 IPv4,看起来是 “1 个 IP”;
但如果代码去掉Inet4Address过滤,就能看到多个 IP 了。 - 手动配置的 “IP 别名”(多 IP 绑定)
在服务器 / 开发机场景中,运维或开发者会给网卡绑定多个 IPv4 别名,比如:
主 IP:192.168.1.100(用于日常通信);
别名 IP:192.168.1.101(用于特定服务 / 测试)。
这种配置下,你的代码会直接读取到多个 IPv4 地址。 - 临时 / 辅助 IP(系统自动分配)
链路本地地址:IPv4 有一个特殊的 “链路本地地址”(169.254.x.x),当网卡无法获取 DHCP 地址时,系统会自动分配这个 IP,导致网卡同时有 “正常 IP + 链路本地 IP”;
VPN / 虚拟网卡叠加:如果开启了 VPN、容器网络(Docker)、虚拟机网络,物理网卡会关联虚拟 IP,也会出现多 IP。
但是实际与其它端通信的IP应该只有一个,那么究竟如何获取到这个“出口ip”呢
进阶获取ip方法
// 核心方法:获取出口IP
private static String getExitIp() {
// 选择一个公网IP(无需连通,仅用于获取本地出口),比如阿里云公共DNS、谷歌DNS
String testHost = "223.5.5.5"; // 阿里云DNS(推荐),也可以用 8.8.8.8(谷歌DNS)
int testPort = 53; // DNS端口,几乎不会被屏蔽
try (Socket socket = new Socket()) {
// 连接到公网地址(仅建立连接,不发送数据)
SocketAddress remoteAddr = new InetSocketAddress(testHost, testPort);
socket.connect(remoteAddr, 3000); // 超时3秒
// 获取本地绑定的IP(即出口IP)
InetAddress localAddr = ((InetSocketAddress) socket.getLocalSocketAddress()).getAddress();
return localAddr.getHostAddress();
} catch (Exception e) {
log.severe("获取出口IP异常:" + e.getMessage());
return null;
}
}
核心思路
获取出口 IP 的关键不是直接读取本地网卡 IP,而是通过主动发起一个对外的网络连接(不需要实际发送数据),从连接的本地端点中获取真正用于通信的 IP。因为本地可能有多个网卡、多个 IP,但操作系统会根据路由表选择最优的出口 IP,这个方式能精准拿到这个IP
关键逻辑解释
为什么要连接公网地址?
本地网卡的 IP 只是 "备选",操作系统会根据路由表决定用哪个 IP 访问公网。通过连接一个公网 IP(比如阿里云 DNS 223.5.5.5),操作系统会自动选择最优的出口 IP 绑定到这个 socket,我们只需读取这个本地绑定的 IP 即可。测试地址选择原则:
选公网、高可用的地址(如公共 DNS),避免选可能不通的地址;
端口选通用端口(53 是 DNS 端口,80 是 HTTP 端口),几乎不会被防火墙屏蔽;
代码中用 try-with-resources 自动关闭 socket,避免资源泄漏。特殊情况处理:
如果是内网环境(比如公司局域网),获取到的 "出口 IP" 是内网出口(如 192.168.1.100),而非公网 IP;
如果要获取公网 IP(如 112.xx.xx.xx),需要调用公网 API(比如 http://icanhazip.com),补充代码如下:
// 补充:获取公网IP(需联网)
private static String getPublicIp() {
try {
// 调用公网API获取公网IP
URL url = new URL("http://icanhazip.com");
try (BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream()))) {
return reader.readLine().trim();
}
} catch (Exception e) {
log.severe("获取公网IP异常:" + e.getMessage());
return null;
}
}