【问题标题】:What is the most efficient way to find out which subnet an IP belongs to找出IP属于哪个子网的最有效方法是什么
【发布时间】:2015-05-02 11:56:11
【问题描述】:

假设我有数十亿条记录,每条记录都包含 IPv4 字段。我想知道:

  1. 如果每条记录都属于我关注的子网之一
  2. 如果满足要求 1,则属于哪个子网。

每个相关子网都定义为几个掩码(61.232.85.0/25、61.232.86.0/27),可能还有一些离散的 IP,我总共有 1000 个子网定义要处理。

那么,处理这项工作最有效的算法和/或数据结构是什么?

另外,我找到了Trie 一个可能的解决方案,有什么建议吗?

【问题讨论】:

  • 您可以将子网表示为 int 值,将它们全部放在一个数组中,对其进行排序,然后对其使用二进制搜索。最多需要 10 个步骤才能找到子网。尝试可能需要例如/25 子网的 25 个步骤。
  • 还要记住 1000 个整数只有 4 kb。这将很容易适应 CPU 缓存,使其非常快。当然,您可以使用第二个数组来存储每个掩码所属的子网。
  • @SpiderPig,感谢您的建议,我发现它比 Trie 更有效,因为子网本身是不相交的。您介意将其发布为答案,因此我可以投票,同时更多人可以稍后看到。

标签: algorithm scala data-structures ip-address trie


【解决方案1】:

您可以将子网表示为 int 值,将它们全部放在一个数组中,对其进行排序,然后对其使用二进制搜索。 尽管从那时起 long 可能会更好,但您不必在二进制搜索中处理负数。 最多需要 10 个步骤才能找到子网。尝试可能需要例如/25 子网的 25 个步骤。 还要记住,1000 长只有 8 kb。这将很容易适应 CPU 缓存,使其非常快。 当然,您可以使用第二个数组来存储每个掩码所属的子网。

这是 Scala 中的示例 findMaskIdx 使用二分搜索查找给定掩码的索引(子网定义的 ip 部分)。 如果它找不到任何东西,它会返回大于它搜索的第一个掩码的索引。 findIpIdx 接受一个 IP 地址并返回它所属的子网定义的索引,如果没有找到则返回 -1。

findIpIdx 每秒可以运行大约 100 到 2 亿次。 所以它似乎相当快。 这种方法只有一个问题。如果两个不同大小的子网重叠,则代码可能会找到错误的子网。 但我希望这应该不会太难解决。

def ipStringToInt(s: String): Int = {
  var ip = 0
  for(num <- s.split("\\.")) {
    ip = ip * 256 + num.toInt
  }
  ip
}

def parseSubnet(s: String): (Long, Int) = {
  val mask_length = s.split("/")
  val length = if(mask_length.size > 1) mask_length(1).toInt else 32
  var mask = ipStringToInt(mask_length(0)) & 0xFFFFFFFFL
  (mask, length)
}

val subnetGroups = Vector(
  Vector("61.232.85.0/25", "61.232.86.0/27"),
  Vector("123.234.12.24/16", "1.2.3.4"),
  Vector("61.232.87.5", "253.2.0.0/16")
)

val subnetData = (for {
  (group, idx) <- subnetGroups.zipWithIndex
  maskString <- group
  (mask, length) = parseSubnet(maskString)
} yield (mask, length, idx)).sortBy(_._1)

val masks: Array[Long] = subnetData.map(_._1).toArray
val maskLengths: Array[Int] = subnetData.map(_._2).toArray
val groupNr: Array[Int] = subnetData.map(_._3).toArray

def findMaskIdx(ip: Long): Int = {
  var low = 0
  var high = masks.size
  while(high > low) {
    val mid = (low + high)/2
    if(masks(mid) > ip) high = mid
    else if(masks(mid) < ip) low = mid + 1
    else return mid
  }
  low
}

def findIpIdx(ip: Int): Int = {
  val ipLong = ip & 0xFFFFFFFFL
  var idx = findMaskIdx(ipLong)
  if(idx < masks.size && masks(idx) == ipLong) return idx
  idx -= 1
  if(idx < 0) return -1
  val m = (0xFFFFFFFF00000000L >>> maskLengths(idx)) & 0xFFFFFFFFL
  if((m & masks(idx)) == (m & ipLong)) return idx
  return -1
}


println("subnet data (mask, bit length of mask, index of subnet group):")
println(subnetData.map {
  case (mask, length, idx) => (mask.toHexString, length, idx)
})
println()

println("masks = " + masks.toVector.map(_.toHexString))
println()

def testIP(ipString: String) {
  println("ipString = " + ipString)
  val ip = ipStringToInt(ipString)
  val dataIdx = findIpIdx(ip)
  println("dataIdx = " + dataIdx)
  if(dataIdx >= 0) {
    val data = subnetData(dataIdx)
    println("data = " + (subnetData(dataIdx) match {
      case (mask, length, idx) => (mask.toHexString, length, idx)
    }))
  }
  println()
}

testIP("61.232.86.12")
testIP("253.2.100.253")
testIP("253.3.0.0")

【讨论】:

    【解决方案2】:

    这真的很容易。将 IP 转换为无符号 32 整数。那么如果 (IP1 & Mask) = (IP2 & Mask),其中 mask 是两个掩码中较小的一个。

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    namespace ConsoleApplication1
    {
        class Program
        {
            static void Main(string[] args)
            {
                string IP1str = "61.232.85.0/25";
                cIP IP1 = new cIP(IP1str);
                string IP2str = "61.232.86.0/27";
                cIP IP2 = new cIP(IP2str);
                string IP3str = "61.232.85.0/22";
                cIP IP3 = new cIP(IP3str);
                string IP4str = "61.232.86.0/22";
                cIP IP4 = new cIP(IP4str);
    
                bool sameNet = (new cIP()).InSubnet(IP3str, IP4str);
            }
        }
        public class cIP
        {
            public UInt32 IP { get; set; }
            public string IPHex { get; set; }
            public UInt32 mask { get; set; }
            public string maskHex = "";
    
            public cIP(){}
    
            public cIP(string IPstr)
            {
                //remove mask
                string maskStr = IPstr.Substring(IPstr.LastIndexOf("/") + 1);
                mask = (UInt32)(Math.Pow(2, 32) - Math.Pow(2, 32 - UInt32.Parse(maskStr)));
                maskHex = mask.ToString("x8");
                IPstr = IPstr.Substring(0,IPstr.LastIndexOf("/"));
                string[] IParray = IPstr.Split(new char[] { '.' });
                for (int i = 3; i >= 0; i--)
                {
                    IP = IP + (UInt32.Parse(IParray[3 - i]) << (i * 8));
                }
                IPHex = IP.ToString("x8");
            }
            public bool InSubnet(string IP1str, string IP2str)
            {
                bool results = false;
                cIP IP1 = new cIP(IP1str);
                cIP IP2 = new cIP(IP2str);
    
                if (IP1.mask <= IP2.mask)
                {
                    results = (IP1.IP & IP1.mask) == (IP2.IP & IP1.mask);
                }
                else
                {
                    results = (IP1.IP & IP2.mask) == (IP2.IP & IP2.mask);
                }
    
                return results;
            }
            public bool InSubnet(cIP IP1, cIP IP2)
            {
                bool results = false;
                if (IP1.mask <= IP2.mask)
                {
                    results = (IP1.IP & IP1.mask) == (IP2.IP & IP1.mask);
                }
                else
                {
                    results = (IP1.IP & IP2.mask) == (IP2.IP & IP2.mask);
                }
    
                return results;
            }
    
        }
    
    }
    ​

    【讨论】:

    • 您是否考虑过我在问题中提到的数据量?我认为当面对如此庞大的数据时,问题可能会改变。
    • 转换不需要那么长时间。看起来您的输入是一个字符串。如果是 IP 地址会更快。
    猜你喜欢
    • 2013-02-23
    • 2011-07-26
    • 2017-04-03
    • 1970-01-01
    • 2020-03-30
    • 1970-01-01
    • 1970-01-01
    • 2017-06-01
    • 1970-01-01
    相关资源
    最近更新 更多