如何在 C# List 中存储 IP 地址列表以使其也可搜索子网?

Posted

技术标签:

【中文标题】如何在 C# List 中存储 IP 地址列表以使其也可搜索子网?【英文标题】:How to store IP address list in C# List to make it searchable for subnets too? 【发布时间】:2010-11-26 01:45:45 【问题描述】:

我应该如何正确存储带有子网地址的 IP 地址列表以使其可搜索?

有两个例子:

    我的 IP 地址是 1.2.3.4,在我的 C# 列表中有 1.2.3.4 条目,所以这里没有问题。

    我有 IP 地址 3.4.5.6,在我的 C# 列表中我有子网 3.4.0.0/24。这是我的问题。

如何在列表中存储 IP 子网以覆盖第二个示例?

【问题讨论】:

【参考方案1】:

在这个答案的最后,您将找到一个完整的结构实现来表示 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;
  

【讨论】:

【参考方案2】:

定义一个存储IPAddress 和前缀长度的类:

public class IPAddressWithPrefixLength

    public IPAddress IPAddress  get; 
    public int PrefixLength  get; 

然后覆盖EqualsGetHashCode,这样就只考虑IPAddress.GetAddressBytes() 的前PrefixLength 位(当然还有IPAddress 类型)。

然后您可以使用此类将子网前缀存储在 List&lt;T&gt; 中或将它们用作 Dictionary&lt;K,V&gt; 的键:

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 地址。

【讨论】:

+1,但是ipawpl的构造函数不应该使用32的前缀长度吗?理想情况下,也应该找到一个用 IPAddressWithPrefixLength(IPAddress.Parse("3.4.5.6"), 32) 构造的实例,对吧? @Vinay:我不这么认为,因为 3.4.5.6 中前缀的长度仍然是 16 位,不是吗? (或 24 个?) @dtb:我的意思是,网络地址(带有 16 位前缀)应该匹配子网中的任意地址(可以是完整的 32 位地址,或者说是 24 -bit 原 3.4.0.0 网络的子网)。 @Vinay:对。 3.4.0.0/16 应该匹配子网中的任何地址。【参考方案3】:

我更愿意创建一个专门的结构(类)来将所有这些信息存储在一起。 可能在不久的将来,您希望将其扩展为在 ipv4 旁边存储 ipv6,也许还有更多数据(指标、网关等)。

【讨论】:

【参考方案4】:

我可以在节点上使用带有布尔标签的二叉树。使用标准表示法,其中 0 是左孩子,1 是右孩子,1.2.3.4 将通过将true 放在树中的00000001000000100000001100000100(该地址的二进制表示)来存储 - 并且在之间的所有节点处为 false根和这个。相反,3.4.0.0/16 将与 true 一起存储在 0000001100000100(3.4.0.0 二进制表示的前 16 位)。

当你得到一个地址进行测试时,只需根据该地址的位沿着树向下走:如果你到达一个节点true,则该地址在列表中。如果到达分支的末尾,则地址不在列表中。

例如,如果查找 3.4.123.48,您将在树中向下 16 层才能达到 true,这意味着该地址在列表中。但是查找 129.199.195.13,您会从该地址的前 1 位知道它不是列表的一部分。

我不确定使用List 类型来存储这些地址对您有多重要,所以这可能无济于事; OTOH,一旦您实现了带有标签的基本二叉树,它应该比.Net List 具有更好的渐近性能特征。

【讨论】:

【参考方案5】:

不要将其存储在列表中 - 将其存储在诸如字典之类的结构中,其中键是 IP 地址,值是子网地址。

【讨论】:

以上是关于如何在 C# List 中存储 IP 地址列表以使其也可搜索子网?的主要内容,如果未能解决你的问题,请参考以下文章

如何将 List<> 数组中的数据保存到文本文件(C#)?

如何在应用程序设置中存储对象列表

如何在 C# 代码中获取 IP 地址 [重复]

如何在C#中获取机器的IP地址

C#如何在页面中获取本机的外网IP地址

如何在C#中获取用户的公共IP地址