jmDNS在Windows 11的Eclipse中正常运行但MacOS Monterey下失效
问题背景
我目前在维护SourceForge上已停止更新的Java应用kmttg,这款应用可以让Tivo设备用户通过电脑编辑修改Tivo上的OnePass条目。该应用的RPC证书下月即将过期,我正在尝试修复这个问题,已经从SourceForge拉取了代码,当前在多台设备上调试运行。
- 调试设备1:Windows 11系统笔记本,安装Java版Eclipse 2021-09(4.2.1.0)
- 调试设备2:搭载M1 Pro芯片的14寸MacBook Pro,运行MacOS Monterey系统,安装同版本Java版Eclipse
- 两台设备Java JRE版本均为10.0.2,代码合规级别设为1.8,依赖Jar包完全一致,均来自SourceForge原项目
问题现象
应用获取本地IP地址的逻辑在Windows 11上运行正常,但在MacBook Pro上运行失败,执行获取IP的代码后Mac设备返回IP为0.0.0.0。
问题代码段
package com.tivo.kmttg.main; import java.net.DatagramSocket; import java.net.InetAddress; //import java.util.Date; import java.util.Enumeration; import java.util.Hashtable; import java.util.Stack; import javax.jmdns.JmDNS; import javax.jmdns.ServiceInfo; import com.tivo.kmttg.util.log; public class mdns { private JmDNS jmdns = null; private Hashtable<String,ServiceInfo> SERVICE = new Hashtable<String,ServiceInfo>(); //private int timeout = 5; // ~mins after which mdns listening disabled //private long start_time; public mdns() { try { DatagramSocket socket = new DatagramSocket(); socket.connect(InetAddress.getByName("8.8.8.8"), 10002); InetAddress IP = socket.getLocalAddress(); // 该逻辑在Windows 11上正常,Mac上失败 if (! IP.isReachable(3000)) IP = InetAddress.getLocalHost(); if (! IP.isReachable(3000)) log.error("mdns error: Can't determine valid IP for host running kmttg"); //log.print("IP=" + socket.getLocalAddress().getHostAddress()); jmdns = JmDNS.create(IP); socket.close(); //start_time = new Date().getTime(); } catch (Exception e) { log.error("mdns error: " + e.getMessage()); } } }
运行截图


解决方案
这个问题是MacOS(尤其是Apple Silicon架构设备)的网络栈实现和Windows存在差异导致的:
- 你当前通过连接公网地址8.8.8.8获取本地出口IP的逻辑,在Mac上如果系统没有开启IPv4优先、或者当前网络没有公网连通性时,
socket.getLocalAddress()会返回通配地址0.0.0.0,这是BSD系网络栈的常见行为,和Windows的实现逻辑不同。 - 后续的
InetAddress.getLocalHost()在Mac上默认会返回loopback地址127.0.0.1或者::1,而不是物理网卡的实际IP,这是MacOS的默认hosts配置导致的。
你可以替换原有的IP获取逻辑,改为遍历所有物理网卡,筛选出非回环、非虚拟、已启动的IPv4地址,示例实现如下:
import java.net.Inet6Address; import java.net.NetworkInterface; private InetAddress getLocalHostLANAddress() throws Exception { // 优先返回可连通公网的出口地址 try { DatagramSocket socket = new DatagramSocket(); socket.connect(InetAddress.getByName("8.8.8.8"), 10002); InetAddress address = socket.getLocalAddress(); if (!address.isLoopbackAddress() && !address.isAnyLocalAddress()) { socket.close(); return address; } socket.close(); } catch (Exception e) { // 忽略异常,走 fallback 逻辑 } // Fallback:遍历所有网卡找有效IP InetAddress candidateAddress = null; for (Enumeration<NetworkInterface> ifaces = NetworkInterface.getNetworkInterfaces(); ifaces.hasMoreElements(); ) { NetworkInterface iface = ifaces.nextElement(); // 跳过回环、虚拟、未启用的网卡 if (iface.isLoopback() || iface.isVirtual() || !iface.isUp()) { continue; } for (Enumeration<InetAddress> inetAddrs = iface.getInetAddresses(); inetAddrs.hasMoreElements(); ) { InetAddress inetAddr = inetAddrs.nextElement(); // 跳过回环地址和IPv6地址 if (inetAddr.isLoopbackAddress() || inetAddr instanceof Inet6Address) { continue; } if (inetAddr.isSiteLocalAddress()) { // 找到内网地址直接返回 return inetAddr; } else if (candidateAddress == null) { // 暂存非内网的公网地址备选 candidateAddress = inetAddr; } } } if (candidateAddress != null) { return candidateAddress; } // 最后兜底用localhost地址 return InetAddress.getLocalHost(); }
把原来的InetAddress IP = socket.getLocalAddress();替换为调用这个方法即可,兼容Windows、MacOS、Linux多平台。
内容的提问来源于stack exchange,提问作者rpetruzz
相关产品推荐
相关产品推荐

