需要更好的实现从IP范围列表中查找给定的IP地址

时间:2014-09-09 10:31:28

标签: java

有一个表包含startIP,endIp以及与位置等相关的所有其他数据。

在应用程序启动时,缓存整个表数据以减少查询。现在当用户连接到应用程序时,我们得到了IP(即客户端IP),需要从缓存中找到Ip范围。

那么,什么是更好的缓存实现?

目前我的实施方式如下:

ConcurrentNavigableMap<String, ConcurrentNavigableMap<String, String>> cache;

外部地图键如下: startIP_endIP

即10.115.1.10_10.115.1.240

和外部地图​​值(内部地图)包含单行表格。

如上所述实现密钥有一个好处就是我可以使用 ConcurrentNavigableMap.subMap()方法来减少迭代。

查找以下方案

  • 获得客户端IP,即(10.115.1.10)
  • 按(。)拆分字符串并尝试从缓存中查找子图
  • 现在迭代submap.keySet()以找到确切的IPRange。

请在下面找到代码段:

System.out.println("==============================================");
    System.out.println("clientIP: " + clientIP);  // Client IP address
    ConcurrentNavigableMap<String, Object> subMap = null;
    String clientIPTokens[] = new String[4];
    clientIPTokens[0] = clientIP; // 10.115.1.10
    clientIPTokens[1] = clientIP.substring(0, clientIP.lastIndexOf(".")); // 10.115.1
    clientIPTokens[2] = clientIP.substring(0, clientIP.indexOf(".", clientIP.indexOf(".")+1)); // 10.115
    clientIPTokens[3] = clientIP.substring(0,clientIP.indexOf(".") ); //10

    ConcurrentNavigableMap<String, Object> packageMap = null;

    boolean isValid = false;
    for(String tokens : clientIPTokens){
        subMap = MapCache.getSubMapByPreffix(COLLECTION_NAME, tokens);  // got submap
        System.out.println("tokens: " + tokens + ", subMap: " + subMap);
        if(subMap!=null && subMap.size()>0){
            for(String key : subMap.keySet()){
                isValid = checkIPInMap((ConcurrentNavigableMap<String, String>) subMap.get(key),clientIP);
                if(isValid) // ip range found for the client ip (Got the single row)
                    break;
            }
        }
        if(isValid)
            break;
    }

    // When no match found need to iterate over whole cache
    if(!isValid && MapCache.getMap()!=null && MapCache.getMap().containsKey(COLLECTION_NAME)){  
        packageMap = MapCache.getMap().get(COLLECTION_NAME);
        isValid = checkIPInFullMap(packageMap, clientIP);
    }

我在表格中有大约650个条目,所以我怎样才能更好地实现缓存或缓存密钥,以便更容易进行搜索。

因此,底线是:

我有一个IP和IP-Range列表(start-ip,end-ip)。并且需要找到它将落在哪个IP范围内。但必须遍历整个列表或(子列表)并需要检查

public static boolean isValidRange(String ipStart, String ipEnd,
                String ipToCheck){
            try {
                long ipLo = ipToLong(InetAddress.getByName(ipStart));
                long ipHi = ipToLong(InetAddress.getByName(ipEnd));
                long ipToTest = ipToLong(InetAddress.getByName(ipToCheck));
                return (ipToTest >= ipLo && ipToTest <= ipHi);
            } catch (UnknownHostException e) {
                //throw new Exception();
                e.printStackTrace();
                return false;
            }
        }

我相信这种逻辑必须要有所改善!!!! :)

1 个答案:

答案 0 :(得分:0)

可以将IP地址作为数字进行比较(如果您的范围正确)。

尝试将ipStart和ipEnd转换为32位数字(我更喜欢它以避免签名的比较技巧)。为此数字添加一个开始和结束标记(如果使用long,则只需使用较低的数字位)并将其存储到任何类型的排序列表或集合中。

现在选择clientIP并将其转换为相同类型的数字。在列表中搜索两个最接近的数字,在客户端IP之上和之下,并检查开始/结束标志,以确保在开始和结束(范围内)之间失败,而不是结束和开始(范围之间)。

我认为这是您最快,最清晰的方法。

修改

是的,您的检查逻辑是正确的。 我为你的主要问题提出了整体解决方案 - 通过ip查找范围。

一些伪代码:

//setup:
TreeSet<Long> ranges = new TreeSet<>();
for(int i = 0; i < rangesCount; ++i) {
  ranges.add(toLong(rangeStart[i]) << 1);
  ranges.add((toLong(rangeEnd[i]) << 1) + 1);
}

//check:
long client = toLong(clientIp) << 1;
Long floorIp = ranges.floor(client);
Long ceilIp = ranges.ceiling(client);

if(floorIp == null || ceilIp == null) {
  //outside any ranges
} else {
  if(floorIp & 1 == 0 && ceilIp & 1 == 1) {
    //in range floorIp-ceilIp
  } else {
    //between ranges
  }
}

当然,范围不能以任何方式相交。

您还可以通过在ip和开始结束标记之间将其混合为长数来保持此结构中的范围编号。

long marker = (ip << 16 + (short) range_id) << 1 + startEndMark;
long floorIp = ranges.floor(clientIp << 17 + MAX_SHORT << 1);
long ceilIp = ranges.ceiling(clientIp << 17);

最后一行的小评论: 我们需要MAX_SHORT << 1来正确处理间隔的开始边缘,因为 0xIP_IP_IP_IP_IDID_0 >= 0xIP_IP_IP_IP_0000_0,但0xIP_IP_IP_IP_IDID_0 <= 0xIP_IP_IP_IP_FF_FF_0。对于结束边缘:0xIP_IP_IP_IP_0000_0 <= 0xIP_IP_IP_IP_IDID_1

或者你可以将排序范围放到数组中,并使用数组索引作为范围数。

编辑2:

还有一种真正的Java方式:为IP和范围ID创建对象(以及开始标记等)。定义Comparator(或实现Comparable)并将此对象存储在有序集合或映射中,而不是位混合long。