我应该如何正确存储带有子网地址的 IP 地址列表以使其可搜索?
有两个例子:
我有 IP 地址 1.2.3.4 并且在我的 C# 列表中有 1.2.3.4 条目,所以这里我们没有问题。
我有 IP 地址 3.4.5.6,在我的 C# 列表中我有子网 3.4.0.0/24。这是我的问题。
如何将 IP 子网存储在列表中以涵盖第二个示例?
我应该如何正确存储带有子网地址的 IP 地址列表以使其可搜索?
有两个例子:
我有 IP 地址 1.2.3.4 并且在我的 C# 列表中有 1.2.3.4 条目,所以这里我们没有问题。
我有 IP 地址 3.4.5.6,在我的 C# 列表中我有子网 3.4.0.0/24。这是我的问题。
如何将 IP 子网存储在列表中以涵盖第二个示例?
在此答案的最后,您将找到表示 IPV4 地址的结构的完整实现。
这是非常简单的用法示例:-
List<IPV4Address> list = new List<IPV4Address>();
list.Add(IPV4Address.FromString("3.4.0.0", 24));
var x = IPV4Address.FromString("3.4.0.6");
foreach (var addr in list.Where(a => a.Contains(x)))
Console.WriteLine(addr);
值“3.4.0.0/255.255.255.0”显示在控制台中,因为在 3.4.0.0/24 子网中找到了 3.4.0.6。假设list
充满了各种子网并且x
可以包含任何地址,那么这个: -
var result = list.Where(a => a.Contains(x))
.OrderByDescending(a => a.Mask)
.FirstOrDefault();
将为包含x
.
public struct IPV4Address
{
private UInt32 _Value;
private UInt32 _Mask;
public UInt32 Value
{
get { return _Value; }
private set { _Value = value; }
}
public UInt32 Mask
{
get { return _Mask; }
private set { _Mask = value; }
}
public static IPV4Address FromString(string address)
{
return FromString(address, 32);
}
public static IPV4Address FromString(string address, int maskLength)
{
string[] parts = address.Split('.');
UInt32 value = ((UInt32.Parse(parts[0]) << 24) +
((UInt32.Parse(parts[1])) << 16) +
((UInt32.Parse(parts[2])) << 8) +
UInt32.Parse(parts[3]));
return new IPV4Address(value, maskLength);
}
public IPV4Address(UInt32 value)
{
_Value = value;
_Mask = int.MaxValue;
}
public IPV4Address(UInt32 value, int maskLength)
{
if (maskLength < 0 || maskLength > 32)
throw new ArgumentOutOfRangeException("maskLength", "Must be 0 to 32");
_Value = value;
if (maskLength == 32)
_Mask = UInt32.MaxValue;
else
_Mask = ~(UInt32)((1 << (32 - maskLength))-1);
if ((_Value & _Mask) != _Value)
throw new ArgumentException("Address value must be contained in mask");
}
public bool Contains(IPV4Address address)
{
if ((Mask & address.Mask) == Mask)
{
return (address.Value & Mask) == Value;
}
return false;
}
public override string ToString()
{
string result = String.Format("{0}.{1}.{2}.{3}", (_Value >> 24),
(_Value >> 16) & 0xFF,
(_Value >> 8) & 0xFF,
_Value & 0xFF);
if (_Mask != UInt32.MaxValue)
result += "/" + String.Format("{0}.{1}.{2}.{3}", (_Mask >> 24),
(_Mask >> 16) & 0xFF,
(_Mask >> 8) & 0xFF,
_Mask & 0xFF);
return result;
}
}
定义一个存储一个IPAddress
和前缀长度的类:
public class IPAddressWithPrefixLength
{
public IPAddress IPAddress { get; }
public int PrefixLength { get; }
}
然后覆盖Equals
并且GetHashCode
只考虑 的第一位(当然还有 IPAddress 类型)PrefixLength
。IPAddress.GetAddressBytes()
然后,您可以使用此类将子网前缀存储在 a 中List<T>
或将它们用作 a 的键Dictionary<K,V>
:
var subnets = new List<IPAddressWithPrefixLength>
{
new IPAddressWithPrefixLength(IPAddress.Parse("1.2.3.4"), 32),
new IPAddressWithPrefixLength(IPAddress.Parse("3.4.0.0"), 16),
};
var ipawpl = new IPAddressWithPrefixLength(IPAddress.Parse("3.4.5.6"), 16);
Console.WriteLine(subnets.Contains(ipawpl)); // prints "True"
这也适用于 IPv6 地址。
我更愿意创建一个专门的结构(类)来将所有这些信息存储在一起。可能在不久的将来,您希望将其扩展为在 ipv4 旁边存储 ipv6,也许还有更多数据(指标、网关等)。
我可以在节点上使用带有布尔标签的二叉树。使用标准表示法,其中 0 是左孩子,1 是右孩子,1.2.3.4 将通过将true
at 00000001000000100000001100000100
(该地址的二进制表示)放入树中来存储 - 并且在根和 this 之间的所有节点处为 false。相反,3.4.0.0/16 将与true
at 0000001100000100
(3.4.0.0 的二进制表示的前 16 位)一起存储。
当你得到一个要测试的地址时,只需根据该地址的位沿着树向下走:如果你到达一个节点 a true
,该地址就在列表中。如果您到达分支的末尾,则该地址不在列表中。
例如,如果查找 3.4.123.48,您将在树中向下 16 层才能达到 true,这意味着该地址在列表中。但是查找 129.199.195.13,您会从该地址的前 1 位知道它不是列表的一部分。
我不确定使用该List
类型存储这些地址对您有多重要,所以这可能无济于事;OTOH,一旦您实现了带有标签的基本二叉树,它应该比 .Net 具有更好的渐近性能特征List
。
不要将其存储在列表中 - 将其存储在诸如字典之类的结构中,其中键是 IP 地址,值是子网地址。