应通过临时 TCP 连接(如连 8.8.8.8)获取操作系统路由选择的实际出口 IPv4 地址,而非依赖 Dns.GetHostAddresses 返回的未过滤地址列表,因其可能包含回环、虚拟网卡或无效地址。

直接获取本机所有 IP 地址不难,但真正能用的 IPv4 地址往往只有一个——就是你连外网时实际走的那个网卡出口 IP。多数人用 Dns.GetHostAddresses 或 Dns.GetHostEntry 拿到一堆地址,结果挑错了:选到虚拟网卡、IPv6 回环、或 127.0.0.1,程序一上线就连不上对端。
用 Dns.GetHostAddresses 获取全部地址,但别直接用 [0]
这是最常见写法,但隐患最大:
-
Dns.GetHostAddresses(Dns.GetHostName())返回的是主机名解析出的所有地址,包括127.0.0.1、::1、VMware 虚拟网卡、Docker 网桥地址等,顺序不保证 - 硬取
AddressList[0]极大概率拿到回环地址或 IPv6,尤其在 Windows 启用了 IPv6 的默认配置下 - 没做空值检查:如果 DNS 解析失败(比如 hosts 文件干扰、网络未就绪),会抛
SocketException
正确做法是过滤 + 排序:
var addresses = Dns.GetHostAddresses(Dns.GetHostName());
var ipv4s = addresses
.Where(ip => ip.AddressFamily == AddressFamily.InterNetwork)
.Where(ip => !IPAddress.IsLoopback(ip))
.ToArray();
if (ipv4s.Length > 0) return ipv4s[0].ToString(); // 至少拿一个可用 IPv4
判断是否为有效 IPv4 地址,不能只看 AddressFamily
IPAddress.AddressFamily == AddressFamily.InterNetwork 只说明字节长度是 4,不代表它能用于通信:
-
IPAddress.Any(即0.0.0.0)家族是InterNetwork,但它表示“任意接口”,不能作为客户端源地址绑定 -
IPAddress.Loopback(127.0.0.1)也是合法 IPv4,但仅限本机通信 - 某些老旧网卡驱动可能返回无效或未激活的 IPv4 地址(比如 DHCP 失败后残留的
169.254.x.x)
所以业务中应组合判断:
bool IsValidPublicIpv4(IPAddress ip) =>
ip != null &&
ip.AddressFamily == AddressFamily.InterNetwork &&
!IPAddress.IsLoopback(ip) &&
!ip.Equals(IPAddress.Any) &&
!IsLinkLocalIpv4(ip); // 169.254.0.0/16
bool IsLinkLocalIpv4(IPAddress ip) =>
ip.GetAddressBytes() is byte[] b && b[0] == 169 && b[1] == 254;
要拿“真实出口 IPv4”,得模拟一次外网连接
最可靠的方式,是让系统自己选路由表里默认网关对应的那块网卡:
- 创建一个临时
TcpClient,连一个稳定外网地址(如"8.8.8.8",不用 DNS 解析,避免依赖本地 DNS) - 连接成功后,读取
LocalEndPoint.Address,这就是操作系统选出的实际出口 IP - 即使没连通外网,只要路由表有默认网关,也能拿到对应网卡的 IPv4(TCP 连接会触发路由查找)
示例代码:
public static string GetActiveIpv4()
{
try
{
using var client = new TcpClient();
client.Connect("8.8.8.8", 53); // UDP 端口也行,但 TcpClient 更通用
var ep = (IPEndPoint)client.Client.LocalEndPoint;
return ep.Address.ToString();
}
catch
{
return null;
}
}
注意:这个方法不依赖 DNS、不读注册表、不调外部命令,纯 .NET 基础类库,跨平台兼容性好(.NET 5+)。Windows/macOS/Linux 都适用。
别用 route print 或 ipconfig 解析文本
虽然网上很多用 Process.Start("route", "print") 然后正则匹配的方法,问题很实在:
- 输出格式随系统语言变化(中文 Win 下 “0.0.0.0” 前面是“网络目标”,英文是 “Destination”)
- 不同 Windows 版本字段对齐方式不同,正则极易失效
- Linux/macOS 下命令完全不同(
ip route/netstat -rn),维护成本高 - 需要额外权限(某些策略下禁止执行 cmd)
除非你明确要求“不发任何网络包”,否则优先用 TCP 连接法;真要零网络 IO,再考虑用 NetworkInterface.GetAllNetworkInterfaces() 配合路由表 API(需 System.Net.NetworkInformation),但复杂度陡增。
真正容易被忽略的点:同一个物理网卡可能配了多个 IPv4,而操作系统选哪个作为默认出口,取决于路由度量值(metric),不是靠“谁先出现”或“谁数值小”。所以别手动排序 IP 列表,让内核决定。


















