如何在C#List中存储IP地址列表,使其也可以搜索到子网?
我应该如何正确地存储IP地址列表和地址,使其成为可搜索的子网?
有两个例子:
-
我有IP地址1.2.3.4,在我的C#List中有1.2.3.4条目,所以这里我们没有问题。
-
我有IP地址3.4.5.6,在我的C#列表中我有子网3.4.0.0/24。 这是我的问题。
如何在List中存储IP子网以涵盖第二个示例?
谢谢
在本答案的最后,您将找到一个表示IPV4地址的结构的完整实现。
这是一个非常简单的用法示例: –
List list = new List (); 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.GetAddressBytes()
的第一个PrefixLength
位IPAddress.GetAddressBytes()
当然,还考虑IPAddress类型)。
然后,您可以使用此类将子网前缀存储在List
或将它们用作Dictionary
:
var subnets = new List { 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将通过在树中将00000001000000100000001100000100
(该地址的二进制表示)置为true
来存储 – 并且在根和此之间的所有节点处为false 。 相反,3.4.0.0/16将以true
值存储在0000001100000100
(二进制表示forms的前16位为3.4.0.0)。
当您获得要测试的地址时,只需根据该地址的位向下移动树:如果您到达节点为true
,则该地址在列表中。 如果到达分支的末尾,则该地址不在列表中。
例如,如果查找3.4.123.48,则在达到true之前,您将在树中下降16个级别,这意味着此地址位于列表中。 但是查看129.199.195.13,你会从该地址的前1位知道它不是列表的一部分。
我不确定使用List
类型存储这些地址对你有多重要,所以这可能没有用; OTOH,一旦你用标签实现了一个基本的二叉树,这应该具有比.Net List
更好的渐近性能特征。
不要将其存储在列表中 – 将其存储在诸如Dictionary之类的结构中,其中键是IP地址,值是子网地址。