总目录
前言
在网络编程中,IP地址的处理是基础且不可或缺的环节。C#的System.Net.IPAddress
类提供了对IP地址(IPv4和IPv6)的封装和操作功能,支持解析、转换、比较等操作。本文将从基础用法到高级技巧,全面解析IPAddress
的使用方法。
一、IPAddress 是什么?
1. IPAddress 类概述
System.Net.IPAddress
是.NET中处理IP地址的核心类型,支持IPv4和IPv6地址的各种操作。
2. 作用
- 地址解析:从字符串、字节数组构造 IPv4/IPv6 地址
- 格式转换:地址与整数、二进制格式互转
- 网络计算:子网掩码处理、CIDR 表示法支持
- 属性验证:判断地址类型(环回地址、私有地址等)
二、基础用法
1. 创建IPAddress
实例
1)从字符串解析 IP 地址
最常见的方式是从字符串解析 IP 地址,使用 IPAddress.Parse
或 IPAddress.TryParse
方法。
// IPv4 解析
IPAddress ipv4 = IPAddress.Parse("192.168.1.1");
// IPv6 解析
IPAddress ipv6 = IPAddress.Parse("2001:db8::8a2e:370:7334");
// IPv6 解析(支持压缩格式)
IPAddress ipv6 = IPAddress.Parse("2001:db8::1");
2)从字节数组构造 IP 地址
// IPv4:4字节数组
byte[] bytesv4 = { 192, 168, 1, 1 };
IPAddress ipFromBytesV4 = new IPAddress(bytesv4); // IPv6:16字节数组
byte[] bytesv6 = new byte[16];
new Random().NextBytes(bytesv6);
IPAddress ipFromBytesV6 = new IPAddress(bytesv6); // IPv6带范围ID(Windows专用)
var ipv6WithScope = new IPAddress(new byte[] { 0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0x8a, 0x2e, 0x03, 0x70, 0x73, 0x34 },15); // 接口索引
3)安全解析 IP 地址
if (IPAddress.TryParse("203.0.113.5", out IPAddress? address))
{Console.WriteLine($"有效地址: {address}");
}
else
{Console.WriteLine("无效IP地址格式");
}
注意:Parse
方法在格式错误时会抛出 FormatException
,推荐优先使用 TryParse
。
2. 特殊IP地址的静态字段
IPAddress
类提供了几个静态字段,用于表示特殊IP地址:
// 0.0.0.0:表示所有接口
Console.WriteLine(IPAddress.Any); // 输出:0.0.0.0// 255.255.255.255:表示不使用任何接口(等同于虚四地址)
Console.WriteLine(IPAddress.None); // 输出:255.255.255.255// IPv6的特殊地址
Console.WriteLine(IPAddress.IPv6Any); // 输出:::// 回环地址
Console.WriteLine(IPAddress.Loopback); //输出 127.0.0.1
绑定所有接口:使用IPAddress.Any
作为Socket
的绑定地址:
var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.Bind(new IPEndPoint(IPAddress.Any, 8080));
虚四地址(广播):使用IPAddress.None
表示不绑定任何接口:
// 通常用于特定协议的广播场景
回环地址判断:使用IPAddress.Loopback
表示回环地址,使用IPAddress.IsLoopback
判断当前IP地址是否是回环地址 :
// 回环地址
bool isLoopback = IPAddress.IsLoopback(IPAddress.Parse("192.168.1.1"));
Console.WriteLine(isLoopback); //输出 FalseisLoopback = IPAddress.IsLoopback(IPAddress.Loopback);
Console.WriteLine(isLoopback); //输出 True
3. 字节数组与IP地址转换
通过构造函数或GetAddressBytes
方法实现:
// 从字节数组创建IPv4地址
IPAddress iPAddress = new IPAddress(new byte[] { 192, 168, 1, 1 });// 获取IP地址的字节数组
byte[] bytes= iPAddress.GetAddressBytes();
Console.WriteLine(string.Join(".",bytes)); // 输出:192.168.1.1
4. IP地址与字符串转换
// IPv4 解析
IPAddress ipv4 = IPAddress.Parse("192.168.1.1");
// IPv6 解析
IPAddress ipv6 = IPAddress.Parse("2001:db8::8a2e:370:7334");Console.WriteLine(ipv4.ToString()); // 输出:192.168.1.1
Console.WriteLine(ipv6.ToString()); // 输出:2001:db8::8a2e:370:7334// 无压缩格式(使用 Replace 去掉压缩格式)
// string fullFormat = ipv6.ToString().Replace("::", ":0:");
Console.WriteLine(ipv6.ToString().Replace("::", ":0:")); // 输出:2001:db8:0:8a2e:370:7334
IPv6 转字符串
// 标准格式化
string ipv6String = ipv6.ToString(); // 无压缩格式
string fullFormat = ipv6.ToString().Replace("::", ":0:");
5. IP地址与整数转换
在C#中,IP地址(尤其是IPv4)可以与整数进行相互转换。这种转换在某些场景下非常有用,比如将IP地址存储到数据库中以节省空间、进行快速比较或排序等。
1)IPv4 地址与整数的转换原理
IPv4地址本质上是一个32位的无符号整数,通常以点分十进制形式表示(如192.168.1.10
)。每个部分(称为八位组或字节)占用一个字节(8位),范围是0-255
。
例如:
192.168.1.10
对应的二进制表示为:
11000000 10101000 00000001 00001010
将其视为一个32位整数时:
11000000101010000000000100001010 (十六进制:C0A8010A)
对应的十进制值为:
3232235786
2)从 IP 地址转换为整数
▶ 方法 1:使用 IPAddress.GetAddressBytes
通过获取IP地址的字节数组,逐字节计算其整数值:
using System;
using System.Net;class Program
{static void Main(){// 示例IP地址string ipAddress = "192.168.1.10";IPAddress ip = IPAddress.Parse(ipAddress);// 转换为整数byte[] bytes = ip.GetAddressBytes();uint ipAsInt = (uint)((bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]);Console.WriteLine($"IP地址 {ipAddress} 转换为整数: {ipAsInt}");// 输出:IP地址 192.168.1.10 转换为整数: 3232235786}
}
▶ 方法 2:使用 BitConverter
和位运算
BitConverter
可以直接将字节数组转换为整数,但需要注意字节顺序(大端 vs 小端):
using System;
using System.Net;class Program
{static void Main(){// 示例IP地址string ipAddress = "192.168.1.10";IPAddress ip = IPAddress.Parse(ipAddress);// 获取字节数组并反转(确保大端顺序)byte[] bytes = ip.GetAddressBytes();if (BitConverter.IsLittleEndian)Array.Reverse(bytes);// 转换为整数uint ipAsInt = BitConverter.ToUInt32(bytes, 0);Console.WriteLine($"IP地址 {ipAddress} 转换为整数: {ipAsInt}");// 输出:IP地址 192.168.1.10 转换为整数: 3232235786}
}
3)从整数转换为 IP 地址
▶ 方法 1:使用位运算拆分字节
将整数按位拆分为四个字节,并重新组合为IP地址:
using System;
using System.Net;class Program
{static void Main(){// 示例整数uint ipAsInt = 3232235786;// 拆分字节byte[] bytes = new byte[4];bytes[0] = (byte)((ipAsInt >> 24) & 0xFF); // 高字节bytes[1] = (byte)((ipAsInt >> 16) & 0xFF);bytes[2] = (byte)((ipAsInt >> 8) & 0xFF);bytes[3] = (byte)(ipAsInt & 0xFF); // 低字节// 组合为IP地址IPAddress ip = new IPAddress(bytes);Console.WriteLine($"整数 {ipAsInt} 转换为IP地址: {ip}");// 输出:整数 3232235786 转换为IP地址: 192.168.1.10}
}
▶ 方法 2:使用 BitConverter
通过BitConverter
将整数转换为字节数组,并注意字节顺序:
using System;
using System.Net;class Program
{static void Main(){// 示例整数uint ipAsInt = 3232235786;// 转换为字节数组byte[] bytes = BitConverter.GetBytes(ipAsInt);if (BitConverter.IsLittleEndian)Array.Reverse(bytes);// 组合为IP地址IPAddress ip = new IPAddress(bytes);Console.WriteLine($"整数 {ipAsInt} 转换为IP地址: {ip}");// 输出:整数 3232235786 转换为IP地址: 192.168.1.10}
}
4)转换须知
-
IPv6 不支持直接转换: IPv6地址是128位的,无法直接用
uint
表示。如果需要处理IPv6,建议使用字符串存储或第三方库(如BigInteger
)。 -
字节顺序: C#默认是小端模式(低位在前),而IP地址通常是以大端模式存储的。因此,在转换时可能需要手动调整字节顺序。
-
IPv4地址转换为整数的适用场景:
- 数据库存储优化:将IP地址存储为整数,减少存储空间。
- 快速比较:整数比较比字符串比较更高效。
- 排序:整数排序天然支持升序/降序排列。
6. 获取 IP 地址的二进制形式
通过 GetAddressBytes
方法可以获取 IP 地址的字节数组表示。
IPAddress ipv4 = IPAddress.Parse("192.168.1.1");
byte[] bytes = ipv4.GetAddressBytes();
Console.WriteLine(BitConverter.ToString(bytes)); // 输出:C0-A8-01-01
7. 判断 IP 地址类型
可以使用 AddressFamily
属性来判断 IP 地址是 IPv4 还是 IPv6。
// IPv4 解析
IPAddress ipv4 = IPAddress.Parse("192.168.1.1");
Console.WriteLine(ipv4.AddressFamily); // 输出:InterNetwork// IPv6 解析
IPAddress ipv6 = IPAddress.Parse("2001:db8::8a2e:370:7334");
Console.WriteLine(ipv6.AddressFamily); // 输出:InterNetworkV6IPAddress ip = IPAddress.Parse("192.168.1.10");
Console.WriteLine($"是否为IPv4: {ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork}");
三、高级用法
1. 与 Dns
类 配合使用
1)获取本地IP地址
通过Dns.GetHostEntry
或IPAddress
的静态方法获取本机IP地址:
// 获取本机所有IP地址
var host = Dns.GetHostEntry(Dns.GetHostName());
foreach (var ip in host.AddressList)
{Console.WriteLine($"本机IP地址: {ip}");
}// 直接获取IPv4地址(排除IPv6)
var ipv4Addresses = host.AddressList.Where(ip => ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork).ToList();
2)解析域名到IP地址
使用Dns.GetHostAddresses
:
var googleIps = Dns.GetHostAddresses("google.com");
foreach (var ip in googleIps)
{Console.WriteLine($"Google IP地址: {ip}");
}
2. 私有地址检测
1)代码示例
public static bool IsPrivateAddress(IPAddress ip)
{ if (ip.AddressFamily == AddressFamily.InterNetwork) { byte[] bytes = ip.GetAddressBytes(); return bytes[0] == 10 || (bytes[0] == 172 && bytes[1] >= 16 && bytes[1] <= 31) || (bytes[0] == 192 && bytes[1] == 168); } // IPv6 私有地址检测(ULA:fc00::/7) else if (ip.AddressFamily == AddressFamily.InterNetworkV6) { byte[] bytes = ip.GetAddressBytes(); return bytes[0] >= 0xFC && bytes[0] <= 0xFD; } return false;
}
IPAddress ipv4 = IPAddress.Parse("192.168.1.10");
IPAddress ipv6 = IPAddress.Parse("fc00::1");Console.WriteLine(IsPrivateAddress(ipv4)); // 输出:True
Console.WriteLine(IsPrivateAddress(ipv6)); // 输出:TrueIPAddress publicIp = IPAddress.Parse("8.8.8.8");
Console.WriteLine(IsPrivateAddress(publicIp)); // 输出:False
2)代码分析
这段代码实现了一个方法 IsPrivateAddress
,用于判断给定的 IP 地址是否属于私有地址范围。私有地址是指在局域网(LAN)中使用的地址,不会直接暴露在公共互联网上。以下是对代码的详细解析:
▶ IPv4 私有地址检测
return bytes[0] == 10 ||(bytes[0] == 172 && bytes[1] >= 16 && bytes[1] <= 31) ||(bytes[0] == 192 && bytes[1] == 168);
- 根据 RFC 1918,IPv4 私有地址范围包括以下三段:
10.0.0.0/8
:从10.0.0.0
到10.255.255.255
- 判断条件:
bytes[0] == 10
- 判断条件:
172.16.0.0/12
:从172.16.0.0
到172.31.255.255
- 判断条件:
bytes[0] == 172 && bytes[1] >= 16 && bytes[1] <= 31
- 判断条件:
192.168.0.0/16
:从192.168.0.0
到192.168.255.255
- 判断条件:
bytes[0] == 192 && bytes[1] == 168
- 判断条件:
- 如果满足上述任意一个条件,则返回
true
,表示该 IP 地址是私有地址。
▶ IPv6 私有地址检测
return bytes[0] >= 0xFC && bytes[0] <= 0xFD;
- 根据 RFC 4193,IPv6 私有地址(称为唯一本地地址,ULA)的范围是
fc00::/7
。- 前 7 位固定为
1111110
,对应十六进制为FC
或FD
。 - 判断条件:
bytes[0] >= 0xFC && bytes[0] <= 0xFD
- 前 7 位固定为
- 如果满足条件,则返回
true
,表示该 IPv6 地址是私有地址。
▶ 使用场景
- 网络编程中,判断某个 IP 地址是否属于局域网范围。
- 防火墙规则或网络策略配置中,过滤私有地址流量。
3. 检查 IP 地址是否在子网内
1)代码示例
可以通过计算子网掩码和网络地址来判断 IP 地址是否在某个子网内。
public static bool IsInSubnet(IPAddress address, IPAddress subnet, int cidr)
{// 参数校验if (address.AddressFamily != subnet.AddressFamily)throw new ArgumentException("IP 地址和子网地址必须为同一协议版本");if (cidr < 0 || (address.AddressFamily == AddressFamily.InterNetwork && cidr > 32) || (address.AddressFamily == AddressFamily.InterNetworkV6 && cidr > 128))throw new ArgumentOutOfRangeException(nameof(cidr), "CIDR 范围无效");byte[] ipBytes = address.GetAddressBytes();byte[] subnetBytes = subnet.GetAddressBytes();byte[] maskBytes = new byte[ipBytes.Length];// 生成掩码int remainingCidr = cidr;for (int i = 0; i < maskBytes.Length && remainingCidr > 0; i++){int bitsToSet = Math.Min(8, remainingCidr);maskBytes[i] = (byte)(0xFF << (8 - bitsToSet));remainingCidr -= bitsToSet;}// 比较掩码后的结果for (int i = 0; i < ipBytes.Length; i++){if ((ipBytes[i] & maskBytes[i]) != (subnetBytes[i] & maskBytes[i]))return false;}return true;
}
// 示例
IPAddress ipv4 = IPAddress.Parse("192.168.1.10");
IPAddress subnetv4 = IPAddress.Parse("192.168.1.0");
int cidr = 24;Console.WriteLine(IsInSubnet(ipv4, subnetv4, cidr)); // 输出:TrueIPAddress ipv6 = IPAddress.Parse("2001:db8::1");
IPAddress subnetv6 = IPAddress.Parse("2001:db8::");
cidr = 64;Console.WriteLine(IsInSubnet(ipv6, subnetv6, cidr)); // 输出:True
2)代码分析
该方法 IsInSubnet
用于判断一个 IP 地址(address
)是否属于由子网地址(subnet
)和 CIDR 掩码(cidr
)定义的子网。核心逻辑是通过计算子网掩码,然后比较 IP 地址与子网地址在掩码部分是否一致。
▶ 子网掩码生成
// 生成掩码int remainingCidr = cidr;for (int i = 0; i < maskBytes.Length && remainingCidr > 0; i++){int bitsToSet = Math.Min(8, remainingCidr);maskBytes[i] = (byte)(0xFF << (8 - bitsToSet));remainingCidr -= bitsToSet;}
▶ IP 地址与子网地址的掩码比较
// 比较掩码后的结果for (int i = 0; i < ipBytes.Length; i++){if ((ipBytes[i] & maskBytes[i]) != (subnetBytes[i] & maskBytes[i]))return false;}return true;
- 逻辑:
- 对每个字节,将 IP 地址和子网地址与掩码进行按位与操作。
- 如果结果不一致,则说明该 IP 不在子网内,返回
false
。 - 否则,所有字节均匹配,返回
true
。
▶ 适用场景
网络编程中判断 IP 地址是否属于特定子网,如防火墙规则、路由配置等。
▶ 重载方法
public static bool IsInSubnet(IPAddress address, IPAddress subnet, int cidr)
仅展示核心逻辑(缺少参数校验)如下:
public static bool IsInSubnet(IPAddress address, IPAddress subnet, IPAddress mask)
{ byte[] addressBytes = address.GetAddressBytes(); byte[] subnetBytes = subnet.GetAddressBytes(); byte[] maskBytes = mask.GetAddressBytes(); for (int i = 0; i < addressBytes.Length; i++) if ((addressBytes[i] & maskBytes[i]) != subnetBytes[i]) return false; return true;
}
4. CIDR 表示法解析
1)解析IP 和 掩码
public static (IPAddress Address, int Prefix) ParseCidr(string cidr)
{ string[] parts = cidr.Split('/'); IPAddress address = IPAddress.Parse(parts[0]); int prefix = int.Parse(parts[1]); return (address, prefix);
} // 使用示例
var (baseIp, prefix) = ParseCidr("192.168.1.0/24");
仅展示核心逻辑
2) CIDR格式转换(掩码转为CIDR表示)
// 将掩码转换为CIDR表示
public static int GetCidr(IPAddress subnetMask)
{byte[] bytes = subnetMask.GetAddressBytes();int cidr = 0;foreach (byte b in bytes){if (b == 0xFF){cidr += 8;continue;}byte mask = 0x80;while (mask > 0){if ((b & mask) == mask) cidr++;else break;mask >>= 1;}break;}return cidr;
}
仅展示核心逻辑
3)子网掩码计算(CIDR掩码转为IP地址格式)
// 生成 CIDR 掩码
int cidr = 24;
IPAddress mask = IPAddress.Parse("255.255.255.0");
// 或自动生成
byte[] cidrBytes = new byte[4];
for (int i = 0; i < cidr; i++) cidrBytes[i / 8] |= (byte)(0x80 >> (i % 8));
IPAddress cidrMask = new IPAddress(cidrBytes);
// 生成CIDR掩码
public static IPAddress CreateSubnetMask(int cidr, bool isIPv6 = false)
{if (isIPv6) cidr += 96; // 调整IPv6 CIDR范围byte[] bytes = new byte[isIPv6 ? 16 : 4];for (int i = 0; i < bytes.Length; i++){if (cidr >= 8){bytes[i] = 0xFF;cidr -= 8;}else{bytes[i] = (byte)(0xFF << (8 - cidr));break;}}return new IPAddress(bytes);
}
仅展示核心逻辑
5. 网络接口处理
// 获取本机所有IP地址
foreach (NetworkInterface nic in NetworkInterface.GetAllNetworkInterfaces())
{foreach (UnicastIPAddressInformation ip in nic.GetIPProperties().UnicastAddresses){Console.WriteLine($"{nic.Name}: {ip.Address}");}
}
6. IPv4与IPv6映射
// IPv4转IPv6映射地址
IPAddress v4MappedV6 = ipv4.MapToIPv6();// IPv6转IPv4(仅当是映射地址时)
if (ipv6.IsIPv4MappedToIPv6)
{IPAddress v4 = ipv6.MapToIPv4();
}
// 强制转换为 IPv4
if (ip.AddressFamily == AddressFamily.InterNetworkV6 && ip.IsIPv4MappedToIPv6)
{ IPAddress ipv4 = ip.MapToIPv4();
}
四、性能优化技巧
1. 缓存频繁解析的地址
private static readonly ConcurrentDictionary<string, IPAddress> addressCache = new();public static IPAddress ParseCached(string ipString)
{return addressCache.GetOrAdd(ipString, s => {if (IPAddress.TryParse(s, out var ip))return ip;throw new FormatException("无效IP地址格式");});
}
2. 使用 Span 优化字节操作
public static bool IsIPv4MappedToIPv6(IPAddress ip)
{ ReadOnlySpan<byte> bytes = ip.GetAddressBytes().AsSpan(); return ip.IsIPv4MappedToIPv6 && bytes.Slice(0, 12).SequenceEqual(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF });
}
结语
回到目录页:C#/.NET 知识汇总
希望以上内容可以帮助到大家,如文中有不对之处,还请批评指正。
参考资料:
- .NET Runtime IPAddress 源码
- Microsoft Learn: IPAddress.None
- IPAddress.Parse 解析规则
- IPAddress 构造函数详解