在C#开发中,获取本机IPv4地址是一个常见需求,比如做本地服务绑定、局域网通信或设备标识时都要用到。如果不加筛选地直接读取系统网络信息,很容易拿到回环地址、虚拟机虚拟网卡地址或者IPv6地址,导致业务逻辑出错。

为什么不能直接用Dns类获取
很多初学者会优先想到使用Dns.GetHostAddresses方法,通过本机主机名解析出所有绑定的IP。这种做法代码简短,但隐患明显:它返回的是所有地址族的结果,而且会包含127.0.0.1这类回环地址,也可能包含IPv6地址,甚至在某些配置了多个网络服务的机器上返回顺序并不稳定。
下面的代码演示了这种简单但不够严谨的写法:
using System;
using System.Net;
class Program
{
static void Main()
{
string hostName = Dns.GetHostName();
IPAddress[] addresses = Dns.GetHostAddresses(hostName);
foreach (IPAddress addr in addresses)
{
Console.WriteLine(addr.ToString());
}
}
}
运行后你可能会看到类似::1、fe80::开头的IPv6地址,以及127.0.0.1。若业务只支持IPv4且需要真实网卡地址,就必须额外过滤,而Dns类本身并不提供网卡状态等信息,过滤起来并不方便。
使用NetworkInterface精准筛选IPv4
更可靠的方案是基于System.Net.NetworkInformation.NetworkInterface来枚举网络接口。其核心原理是:先判断网卡是否处于Up状态、不是回环接口,再读取该接口的单播地址集合,挑出地址族为InterNetwork的项。这样能自然排除禁用网卡、虚拟回环和IPv6。
以下方法展示了完整的获取逻辑,并增加了对虚拟网卡名称的常见排除策略:
using System;
using System.Net;
using System.Net.NetworkInformation;
using System.Linq;
class IpHelper
{
public static string GetLocalIPv4()
{
// 获取所有网络接口
var interfaces = NetworkInterface.GetAllNetworkInterfaces();
foreach (var ni in interfaces)
{
// 只处理已启用且非回环的网卡
if (ni.OperationalStatus != OperationalStatus.Up)
continue;
if (ni.NetworkInterfaceType == NetworkInterfaceType.Loopback)
continue;
// 遍历单播地址
var props = ni.GetIPProperties();
foreach (var ua in props.UnicastAddresses)
{
if (ua.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
{
// 排除常见虚拟网卡(如VMware、VirtualBox)
if (ni.Description.IndexOf("Virtual", StringComparison.OrdinalIgnoreCase) >= 0)
continue;
return ua.Address.ToString();
}
}
}
return "127.0.0.1";
}
}
上面代码通过OperationalStatus.Up确保网卡在用,用NetworkInterfaceType.Loopback排除回环,再用AddressFamily.InterNetwork锁定IPv4。对于装有虚拟机的开发机,增加Description关键字排除可以进一步提升准确率。
这种方式的优点是信息维度丰富,不仅能拿到地址,还能知道子网掩码、网关等;缺点是实现比Dns略长,且在多网卡环境下可能需按优先级(如有线优先于无线)进一步排序。
多网卡环境下的选取策略
当设备同时连着有线、无线和VPN时,简单返回第一个匹配项可能拿到不期望的网段。可以通过给接口类型加权来优化,例如优先选Ethernet,其次Wireless,最后其他。
示例改进如下:
using System;
using System.Net;
using System.Net.NetworkInformation;
class IpHelper
{
public static string GetBestLocalIPv4()
{
string fallback = "127.0.0.1";
var list = NetworkInterface.GetAllNetworkInterfaces()
.Where(n => n.OperationalStatus == OperationalStatus.Up &&
n.NetworkInterfaceType != NetworkInterfaceType.Loopback)
.ToList();
// 按类型优先级排序
list.Sort((a, b) => GetWeight(a).CompareTo(GetWeight(b)));
foreach (var ni in list)
{
foreach (var ua in ni.GetIPProperties().UnicastAddresses)
{
if (ua.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
return ua.Address.ToString();
}
}
return fallback;
}
static int GetWeight(NetworkInterface ni)
{
if (ni.NetworkInterfaceType == NetworkInterfaceType.Ethernet) return 1;
if (ni.NetworkInterfaceType == NetworkInterfaceType.Wireless80211) return 2;
return 3;
}
}
通过权重排序,程序会优先返回有线网络的IPv4,更符合大多数服务端和桌面程序的预期。若你的场景需要特定网段,也可以结合IPAddress的网段比对来做二次过滤。
总体来看,使用NetworkInterface并配合状态、类型与地址族过滤,是C#中获取本机IPv4最稳妥的做法。相比Dns解析,它逻辑清晰、可控性强,也便于应对复杂的网络环境。