C#编程实践(一):深入System.Net.NetworkInformation获取系统网络信息

在工业软件、设备控制系统、网络诊断工具开发中,经常需要获取当前计算机的网络状态,例如:

  • 当前有哪些网卡?
  • 网卡是否连接?
  • IP地址是多少?
  • MAC地址是多少?
  • 网络连接速度是多少?
  • DNS、网关、DHCP信息是什么?

.NET提供了一个非常方便的命名空间System.Net.NetworkInformation,其中核心类:

作用
NetworkInterface 获取网卡信息
IPInterfaceProperties 获取 IP 配置
IPAddressInformation 获取 IP 地址状态
PhysicalAddress 获取 MAC 地址
NetworkChange 监听网络变化

本文将系统介绍如何使用这些API构建一个网络信息采集工具。

获取所有网络接口

最基础的方式:

1
2
3
4
5
var interfaces = NetworkInterface.GetAllNetworkInterfaces();
foreach (var ni in interfaces)
{
    Console.WriteLine(ni.Name);
}

输出如下:

图1 系统中所有网络适配器
图1 系统中所有网络适配器

每一个NetworkInterface对应系统中的一个网络适配器。

NetworkInterface核心属性

1
2
3
4
5
6
7
8
9
var interfaces = NetworkInterface.GetAllNetworkInterfaces();
foreach (var ni in interfaces)
{
    Console.WriteLine($"名称: {ni.Name}");
    Console.WriteLine($"描述: {ni.Description}");
    Console.WriteLine($"类型: {ni.NetworkInterfaceType}");
    Console.WriteLine($"状态: {ni.OperationalStatus}");
    Console.WriteLine(string.Concat(Enumerable.Repeat("#", 80)));
}

图2 适配器详细信息
图2 适配器详细信息

NetworkInterfaceType表示网卡类型,常见有:

类型 说明
Ethernet 有线网卡
Wireless80211 WiFi
Loopback 本地回环
Tunnel VPN 隧道

工业设备通常重点关注Ethernet,PLC、工业相机等通常都是通过以太网连接,可通过该属性进行筛选。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
var interfaces = NetworkInterface.GetAllNetworkInterfaces();
foreach (var ni in interfaces)
{
    if (ni.NetworkInterfaceType != NetworkInterfaceType.Ethernet) continue;
    Console.WriteLine($"名称: {ni.Name}");
    Console.WriteLine($"描述: {ni.Description}");
    Console.WriteLine($"类型: {ni.NetworkInterfaceType}");
    Console.WriteLine($"状态: {ni.OperationalStatus}");
    Console.WriteLine(string.Concat(Enumerable.Repeat("#", 80)));
}

判断网卡是否可用

我们可通过OperationalStatus判断网卡是否可用。

图3 OperationalStatus的不同枚举值
图3 OperationalStatus的不同枚举值

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
var interfaces = NetworkInterface.GetAllNetworkInterfaces();
var activeInterfaces = interfaces.Where(x => x.OperationalStatus == OperationalStatus.Up);
foreach (var ni in activeInterfaces)
{
    if (ni.NetworkInterfaceType != NetworkInterfaceType.Ethernet) continue;
    Console.WriteLine($"名称: {ni.Name}");
    Console.WriteLine($"描述: {ni.Description}");
    Console.WriteLine($"类型: {ni.NetworkInterfaceType}");
    Console.WriteLine($"状态: {ni.OperationalStatus}");
    Console.WriteLine(string.Concat(Enumerable.Repeat("#", 80)));
}

获取MAC地址

NetworkInterface有一个GetPhysicalAddress方法可用来获取MAC地址。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
var interfaces = NetworkInterface.GetAllNetworkInterfaces();
var activeInterfaces = interfaces.Where(x => x.OperationalStatus == OperationalStatus.Up);
foreach (var ni in activeInterfaces)
{
    if (ni.NetworkInterfaceType != NetworkInterfaceType.Ethernet) continue;
    var mac = ni.GetPhysicalAddress();
    if (mac == null) continue;
    string macAddress = string.Join(":", mac.GetAddressBytes().Select(x => x.ToString("X2")));
    Console.WriteLine(macAddress);
}

获取IP地址

通过:

1
var properties = ni.GetIPProperties();

其中:

1
properties.UnicastAddresses

表示本机IP。通常只需要IPv4:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
var interfaces = NetworkInterface.GetAllNetworkInterfaces();
var activeInterfaces = interfaces.Where(x => x.OperationalStatus == OperationalStatus.Up);
foreach (var ni in activeInterfaces)
{
    var properties = ni.GetIPProperties();
    foreach (var ip in properties.UnicastAddresses)
    {
        if (ip.Address.AddressFamily != System.Net.Sockets.AddressFamily.InterNetwork) continue;
        Console.WriteLine(ip.Address);
    }
}

获取子网掩码

通过UnicastIPAddressInformation类的IPv4Mask属性可获取到子网掩码。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
var interfaces = NetworkInterface.GetAllNetworkInterfaces();
var activeInterfaces = interfaces.Where(x => x.OperationalStatus == OperationalStatus.Up);
foreach (var ni in activeInterfaces)
{
    var properties = ni.GetIPProperties();
    foreach (var ip in properties.UnicastAddresses)
    {
        if (ip.Address.AddressFamily != System.Net.Sockets.AddressFamily.InterNetwork) continue;
        Console.WriteLine($"IP: {ip.Address} Mask: {ip.IPv4Mask}");
    }
}

获取默认网关

通过IPInterfaceProperties类的GatewayAddresses属性可获取到网关信息(GatewayIPAddressInformationCollection类)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
var interfaces = NetworkInterface.GetAllNetworkInterfaces();
var activeInterfaces = interfaces.Where(x => x.OperationalStatus == OperationalStatus.Up);
foreach (var ni in activeInterfaces)
{
    Console.WriteLine($"Speed: {ni.Speed} Name: {ni.Name}");
    var properties = ni.GetIPProperties();
    foreach (var ip in properties.UnicastAddresses)
    {
        if (ip.Address.AddressFamily != System.Net.Sockets.AddressFamily.InterNetwork) continue;
        Console.WriteLine($"IP: {ip.Address} Mask: {ip.IPv4Mask}");
    }

    var gateways = properties.GatewayAddresses;

    foreach(var gateway in  gateways)
    {
        Console.WriteLine($"Gateway: {gateway.Address}");
    }
}

获取DNS信息

1
2
3
4
5
6

var dnsAddresses = properties.DnsAddresses;
foreach(var dns in dnsAddresses)
{
    Console.WriteLine($"DNS: {dns}");
}

获取网络速度

NetworkInterface.Speed表示网络接口的速度,单位是比特/秒。

1
double Mbps = ni.Speed / 1000_000.0;

获取网络统计信息

1
2
3
var stats = ni.GetIPv4Statistics();

Console.WriteLine($"Sent: {stats.BytesSent} Received: {stats.BytesReceived}");

网络配置文件(Network Profile)

Windows会根据当前网络环境,将网络划分为3种Profile:

Profile 含义 默认安全级别
DomainAuthenticated 域网络 最低限制
Private 家庭/可信网络 中等
Public 公共网络 最高限制

Windows防火墙、文件共享、网络发现等功能会根据Profile调整。System.Net.NetworkInformation无法直接获取Profile类型,需要调用Windows API。我们可以使用PowerShell获取Network Profile,执行Get-NetConnectionProfile即可:

图4 PowerShell获取Network Profile信息
图4 PowerShell获取Network Profile信息

关键字段:

字段 含义
InterfaceAlias 网卡名称
NetworkCategory Profile
IPv4Connectivity IPv4状态
IPv6Connectivity IPv6状态

我们可通过C#调用PowerShell获取Profile,NuGet添加Microsoft.PowerShell.SDK,引入System.Management.Automation

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// Query network profiles (Domain/Private/Public) for all connected interfaces via PowerShell.
// Keyed by InterfaceAlias which matches networkInterface.Name in the .NET API.
var profileByAlias = new Dictionary<string, NetworkProfile>(StringComparer.OrdinalIgnoreCase);

try
{
    using var ps = SMA.PowerShell.Create();
    ps.AddScript("Get-NetConnectionProfile | Select-Object InterfaceAlias, NetworkCategory");

    foreach (var result in ps.Invoke())
    {
        var alias = result.Properties["InterfaceAlias"]?.Value?.ToString();
        var category = result.Properties["NetworkCategory"]?.Value?.ToString();

        if (string.IsNullOrEmpty(alias))
            continue;

        profileByAlias[alias] = category switch
        {
            "DomainAuthenticated" => NetworkProfile.Domain,
            "Private" => NetworkProfile.Private,
            "Public" => NetworkProfile.Public,
            _ => NetworkProfile.NotConfigured
        };
    }
}
catch (Exception ex)
{
    Log.Warn("Failed to query network connection profiles via Get-NetConnectionProfile.", ex);
}

以上代码来自NETworkManager

监听网络状态变化

.NET提供NetworkChange事件:

1
NetworkChange.NetworkAddressChanged += OnNetworkChanged;

实现:

1
2
3
4
private static void OnNetworkChanged(object? sender, EventArgs e)
{
    Console.WriteLine($"网络发生变化");
}

此时,如果我们断网,会立即输出“网络发生变化”。

封装网络信息模型

实际项目建议封装:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public class NetworkInterfaceInfo
{
    public string Name { get; set; } = string.Empty;
    public string Description { get; set; } = string.Empty;
    public string MacAddress { get; set; } = string.Empty;
    public List<string> IpAddresses { get; set; } = [];
    public string Gateway { get; set; } = string.Empty;
    public long Speed { get; set; } = 0;
    public OperationalStatus Status { get; set; }
}

实现网络信息采集器

NetworkInterfaceHelper.cs
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
 public class NetworkInterfaceInfo
 {
     public string Name { get; set; } = string.Empty;
     public string Description { get; set; } = string.Empty;
     public string MacAddress { get; set; } = string.Empty;
     public List<string> IpAddresses { get; set; } = [];
     public string Gateway { get; set; } = string.Empty;
     public long Speed { get; set; } = 0;
     public OperationalStatus Status { get; set; }

     public override string ToString()
     {
         var ips = string.Join(", ", IpAddresses);
         return $@"
                 Name: {Name}
                 Description: {Description}
                 MAC: {MacAddress}
                 Gateway: {Gateway}
                 Speed: {Speed}
                 Status: {Status}
                 IP: {ips}
                 ";
     }
 }

 public static class NetworkInterfaceHelper
 {
     public static List<NetworkInterfaceInfo> GetNetworkInterfaces()
     {
         var networkInterfaces = new List<NetworkInterfaceInfo>();

         foreach (var ni in NetworkInterface.GetAllNetworkInterfaces())
         {
             if (ni.OperationalStatus != OperationalStatus.Up) continue;
             var properties = ni.GetIPProperties();
             var networkInfo = new NetworkInterfaceInfo()
             {
                 Name = ni.Name,
                 Description = ni.Description,
                 Speed = ni.Speed,
                 MacAddress = ni.GetPhysicalAddress().ToString(),
             };

             networkInfo.IpAddresses = [.. properties.UnicastAddresses.Select(x => x.Address.ToString())];
             networkInfo.Gateway = properties.GatewayAddresses.FirstOrDefault()?.Address?.ToString() ?? "";

             networkInterfaces.Add(networkInfo);
         }

         return networkInterfaces;
     }
 }

调用:

1
2
3
4
5
var networkInterfaces = NetworkInterfaceHelper.GetNetworkInterfaces();
foreach (var networkInterface in networkInterfaces)
{
    Console.WriteLine(networkInterface);
}

参考

System.Net.NetworkInformation Namespace


相关内容

请作者喝杯咖啡!
AndyFree96 支付宝支付宝
AndyFree96 微信微信