正确做法是过滤出非回环、IPv4类型、且非0.0.0.0的地址:Dns.GetHostAddresses(Dns.GetHostName()).Where(ip => ip.AddressFamily == AddressFamily.InterNetwork && !IPAddress.IsLoopback(ip) && ip.ToString() != "0.0.0.0"),再结合NetworkInterface筛选启用状态为Up、支持IPv4且非虚拟网卡(如排除vEthernet、WSL)的接口,并优先选取配置了默认网关的DHCP或手动分配的IPv4地址。

获取本机 IPv4 地址(排除 127.0.0.1 和虚拟网卡)
直接调用 Dns.GetHostAddresses(Dns.GetHostName()) 得到的是所有地址,包括 IPv6、回环、Docker/virtualbox 虚拟网卡地址,多数场景下你只想要「真实可用的局域网 IPv4」。
正确做法是过滤出非回环、IPv4 类型、且不是 0.0.0.0 的地址:
var addresses = Dns.GetHostAddresses(Dns.GetHostName())
.Where(ip => ip.AddressFamily == AddressFamily.InterNetwork &&
!IPAddress.IsLoopback(ip) &&
ip.ToString() != "0.0.0.0")
.ToArray();
string localIp = addresses.Length > 0 ? addresses[0].ToString() : null;
- 不建议用
Dns.GetHostEntry,它在某些 DNS 配置异常时会抛SocketException - 如果机器有多个物理网卡(如 WiFi + 以太网),
addresses[0]不一定是最常用的——需结合路由表或网卡状态进一步判断 - Windows 上可能拿到 Hyper-V 或 WSL2 的 vEthernet 地址,它们也满足上述条件,但不属于“物理网络”,需额外排除名称含
vEthernet或WSL的网卡(见下节)
排除虚拟网卡:通过 NetworkInterface 获取真实网卡 IP
靠 IP 地址本身很难区分虚拟与物理网卡,更可靠的方式是遍历 NetworkInterface.GetAllNetworkInterfaces(),筛选「已启用、非环回、支持 IPv4、且操作状态为 Up」的接口。
关键过滤条件:
var ip = NetworkInterface.GetAllNetworkInterfaces()
.Where(nic => nic.OperationalStatus == OperationalStatus.Up &&
nic.NetworkInterfaceType != NetworkInterfaceType.Loopback &&
nic.Supports(NetworkInterfaceComponent.IPv4))
.Select(nic => nic.GetIPProperties().UnicastAddresses
.FirstOrDefault(ua => ua.Address.AddressFamily == AddressFamily.InterNetwork &&
!IPAddress.IsLoopback(ua.Address)))
.FirstOrDefault()?.Address?.ToString();
-
nic.NetworkInterfaceType可排除Loopback、Tunnel、PointToPoint等类型,但VirtualAdapter和SoftwareLoopback在旧版 .NET 中可能未被识别,需配合名称判断 - 部分虚拟网卡(如 VMware NAT)类型仍是
Ethernet,此时应检查nic.Name或nic.Description是否含VMware、VirtualBox、vEthernet、Hyper-V - .NET 5+ 支持
nic.IsReceiveOnly和nic.IsSendOnly,可辅助识别异常接口
处理多网卡优先级:按默认网关选最可能的出口 IP
当本机有多个活跃网卡(比如同时连了公司内网和手机热点),仅取第一个 IPv4 地址容易出错。更稳妥的做法是找出「拥有默认网关」的网卡对应 IP——这通常就是系统实际用于外发流量的出口地址。
示例逻辑:
var gateway = NetworkInterface.GetAllNetworkInterfaces()
.Where(nic => nic.OperationalStatus == OperationalStatus.Up)
.SelectMany(nic => nic.GetIPProperties().GatewayAddresses)
.FirstOrDefault(ga => ga.Address.AddressFamily == AddressFamily.InterNetwork);
if (gateway != null)
{
var ip = NetworkInterface.GetAllNetworkInterfaces()
.SelectMany(nic => nic.GetIPProperties().UnicastAddresses)
.FirstOrDefault(ua => ua.Address.AddressFamily == AddressFamily.InterNetwork &&
!IPAddress.IsLoopback(ua.Address) &&
ua.PrefixOrigin == PrefixOrigin.Dhcp || ua.PrefixOrigin == PrefixOrigin.Manual);
if (ip != null) localIp = ip.Address.ToString();
}
-
PrefixOrigin是关键:优先选Dhcp或Manual分配的地址,避开WellKnown(如 169.254.x.x)或RouterAdvertisement(IPv6 RA) - 注意:有些网卡(如 USB 网络共享)虽有网关,但实际不可用;若需高可靠性,应尝试 ping 网关再确认
- 没有默认网关的网卡(如纯内网隔离段)会被跳过,符合预期
跨平台兼容性:.NET Core/.NET 5+ 与 .NET Framework 差异
在 Linux/macOS 上,Dns.GetHostName() 可能返回 localhost,导致 Dns.GetHostAddresses("localhost") 只返回 127.0.0.1;而 NetworkInterface 在容器中可能只暴露 bridge 接口。
推荐统一使用以下兜底策略:
- 先尝试
NetworkInterface方式(全平台支持) - 若结果为空,再 fallback 到读取
/etc/hosts(Linux/macOS)或GetAdaptersAddressesP/Invoke(Windows) - .NET 6+ 可用
IPGlobalProperties.GetIPGlobalProperties().UnicastAddresses,但它不区分网卡,仍需手动过滤 - 避免依赖
Environment.MachineName,它在 Docker 容器里常是随机 ID,且不保证 DNS 可解析
真正麻烦的不是代码怎么写,而是「本机 IP」这个概念本身在现代网络环境下越来越模糊:NAT、多宿主、容器桥接、IPv6 隐私扩展……每次你以为拿到了“正确”的地址,都可能漏掉某个边缘场景。


















