个人网站上有个功能,记录访问者的IP及其归属地。最初我偷懒通过一个WebService来查询IP归属地,后来觉得通过这种方法响应时间长,资源耗费大,而且对那个WebSerice的依赖度太高,如果它挂了或者网络原因,经常要到超时才返回。所以,我打算直接从本地的纯真IP库里查询。

纯真库的数据结构在http://lumaqq.linuxsir.org/article/qqwry_format_detail.html上讲的很详细了。简单地讲数据文件分成三个区域:
    1、文件头(8个字节,前4字节是指向索引区第一条记录,后4字节指向索引区最后一条记录)
    2、记录区(一个记录包含IP地址,国家记录,地区记录,后两个记录有可能是字符串,也可能是重定向,有多种重定向模式)
    3、索引区(一个索引定长7个字节,前4字节是IP地址(little-endian),后3字节指向对应记录区的位置,这里的位置指从文件头开始计算的偏移字节)

虽然这个库结构工作的很好,效率也没有问题,但是我觉得设计的有点小复杂了。而且,如果记录区中有条记录A,是重定向到记录B中的,假如我删除了记录B,查询记录A的时候就会有问题。当然,可以在删除记录B的时候进行相应处理,只是有些麻烦。如果把文件结构改成如下,应该处理起来会更方便一些:
   1、文件头(与原库一样)
   2、字符串区
   3、索引区(4字节的IP地址,4字节的偏移值,4字节的偏移值)
所有字符串放在字符串区中,统一管理。索引区中放IP地址,国家记录的“指针”和区域记录的“指针”,所谓的“指针”是对应到字符串区中某条的字符串偏移值。

不过既然纯真IP库是这么设计的,我只好根据它的结构来进行相应的查询。

索引区的记录是从小到大排列的,可以用二分法来查询。
IP库中索引的IP地址,并不是连续的,举个例子,192.168.0.0的后一条记录并不是192.168.0.1,可能是192.169.0.0,也就是说,它存储的一个是IP段。所以要做一个类似于“四舍五入”的处理。好在大部分情况下,我们都只要舍掉就可以了,比如查询192.168.1.1应该匹配192.168.0.0而不是192.169.0.0。

  1. import java.io.*;
  2. public class IPSeeker
  3. {
  4. protected RandomAccessFile ipDataFile;
  5. protected final int RECORD_LEN = 7;
  6. protected final int MODE_1 = 0x01; //重定向国家记录,地区记录
  7. protected final int MODE_2 = 0x02; //重定向国家记录,有地区记录
  8. protected final int MODE_3 = 0x03; //default
  9. protected long indexBegin;
  10. protected long indexEnd;
  11. public IPSeeker() throws Exception
  12. {
  13. //打开纯真IP库数据文件
  14. ipDataFile = new RandomAccessFile("qqwry.dat", "r");
  15. indexBegin = readLong(4, 0);
  16. indexEnd = readLong(4, 4);
  17. }
  18. public static void main(String[] args) throws Exception
  19. {
  20. IPSeeker seeker = new IPSeeker();//may throw Exception
  21. String result = seeker.search("111.2.13.4");//输入查询的IP地址
  22. System.out.println(result);
  23. seeker.close();//关闭,若不调用close,将在finalize关闭
  24. seeker = null;
  25. }
  26. @Override
  27. protected void finalize() throws Throwable
  28. {
  29. try
  30. {
  31. ipDataFile.close();
  32. }
  33. catch (IOException e)
  34. {
  35. }
  36. super.finalize();
  37. }
  38. public void close()
  39. {
  40. try
  41. {
  42. ipDataFile.close();
  43. }
  44. catch (IOException e)
  45. {
  46. }
  47. }
  48. public String search(String ipStr) throws Exception
  49. {
  50. //采用二分法查询
  51. long recordCount = (indexEnd - indexBegin) / 7 + 1;
  52. long itemStart = 0;
  53. long itemEnd = recordCount - 1;
  54. long ip = IPSeeker.stringIP2Long(ipStr);
  55. long middle = 0;
  56. long midIP = 0;
  57. while(itemStart <= itemEnd)
  58. {
  59. middle = (itemStart +  itemEnd) / 2;
  60. midIP = readLong(4, indexBegin + middle * 7);
  61. //String temp = IPSeeker.long2StringIP(midIP);
  62. if(midIP == ip)
  63. {
  64. break;
  65. }
  66. else if(midIP < ip)
  67. {
  68. itemStart = middle + 1;
  69. }
  70. else//midIP > ip
  71. {
  72. itemEnd = middle - 1;
  73. }
  74. }
  75. //若无完全匹配结果,则向前匹配
  76. if(ip < midIP && middle > 0)
  77. {
  78. middle--;
  79. }
  80. long item = readLong(3, indexBegin + middle * 7 + 4);
  81. String[] result = getInfo(item + 4);//取出信息
  82. return long2StringIP(readLong(4, indexBegin + middle * 7))+ ","//匹配到的IP地址(段)
  83. + result[0] + "," //国家
  84. + result[1];//地区
  85. }
  86. //32位整型格式的IP地址(little-endian)转化到字符串格式的IP地址
  87. public static String long2StringIP(long ip)
  88. {
  89. long ip4 = ip >> 0 & 0x000000FF;
  90. long ip3 = ip >> 8 & 0x000000FF;
  91. long ip2 = ip >> 16 & 0x000000FF;
  92. long ip1 = ip >> 24 & 0x000000FF;
  93. return String.valueOf(ip1) + "." + String.valueOf(ip2) + "." +
  94. String.valueOf(ip3) + "." + String.valueOf(ip4);
  95. }
  96. //字符串格式的IP地址转化到32位整型格式的IP地址(little-endian)
  97. public static Long stringIP2Long(String ipStr) throws Exception
  98. {
  99. String[] list = ipStr.split("\\.");
  100. if(list.length != 4)
  101. {
  102. throw new Exception("IP地址格式错误");
  103. }
  104. long ip = Long.parseLong(list[0]) << 24 & 0xFF000000;
  105. ip += Long.parseLong(list[1]) << 16 & 0x00FF0000;
  106. ip += Long.parseLong(list[2]) << 8 & 0x0000FF00;
  107. ip += Long.parseLong(list[3]) << 0 & 0x000000FF;
  108. return ip;
  109. }
  110. //读取一个n位的
  111. private long readLong(int nByte, long offset) throws Exception
  112. {
  113. ipDataFile.seek(offset);
  114. long result = 0;
  115. if(nByte > 4 || nByte < 0)
  116. throw new Exception("nBit should be 0-4");
  117. for(int i = 0; i < nByte; i++)
  118. {
  119. result |= ((long)ipDataFile.readByte() << 8 * i) & (0xFFL << 8 * i);
  120. }
  121. return result;
  122. }
  123. private String[] getInfo(long itemStartPos) throws Exception
  124. {
  125. //result[0]放国家,result[1]放地区
  126. String[] result = new String[2];
  127. ipDataFile.seek(itemStartPos);
  128. int mode = (int)ipDataFile.readByte();
  129. switch (mode)
  130. {
  131. case MODE_1:
  132. {
  133. long offset = itemStartPos + 1;
  134. long redirPos = readLong(3, offset);
  135. result = getInfo(redirPos);
  136. }
  137. break;
  138. case MODE_2:
  139. {
  140. long offset = itemStartPos + 1;
  141. long redirPos = readLong(3, offset);
  142. result = getInfo(redirPos);
  143. result[1] = getArea(offset + 3);
  144. }
  145. break;
  146. default://MODE_3
  147. {
  148. long offset = itemStartPos;
  149. int countryLen = getStrLength(offset);
  150. result[0] = getString(offset, countryLen);
  151. offset = itemStartPos + countryLen + 1;
  152. result[1] = getArea(offset);
  153. }
  154. break;
  155. }
  156. return result;
  157. }
  158. private String getArea(long offset) throws Exception
  159. {
  160. ipDataFile.seek(offset);
  161. int cityMode = (int)ipDataFile.readByte();
  162. if(cityMode ==  MODE_2 || cityMode ==  MODE_1)
  163. {
  164. offset = readLong(3, offset + 1);
  165. }
  166. int cityLen = getStrLength(offset);
  167. return getString(offset, cityLen);
  168. }
  169. private int getStrLength(long pos) throws IOException
  170. {
  171. ipDataFile.seek(pos);
  172. long strEnd = pos - 1;
  173. while(ipDataFile.readByte() != (byte)0)
  174. {
  175. strEnd++;
  176. }
  177. return (int) (strEnd - pos + 1);
  178. }
  179. private String getString(long pos, int len) throws IOException
  180. {
  181. byte buf[] = new byte[len];
  182. ipDataFile.seek(pos);
  183. ipDataFile.read(buf);
  184. String s = new String(buf, "gbk");
  185. return s;
  186. }
  187. }

转载于:https://blog.51cto.com/jianshusoft/624327

纯真IP库的结构分析及一个查询类相关推荐

  1. python ipaddr库_用Python脚本查询纯真IP库QQWry.dat(Demon修改版)

    #!/usr/bin/env python # coding: utf-8 # from: http://linuxtoy.org/files/pyip.py # Blog: http://linux ...

  2. CnSeu社工库免费查询_ip代理-golang测试纯真ip库与免费版ipip.net库比较

    ip代理-golang测试纯真ip库与免费版ipip.net库比较 本篇文章主要为使用golang测试国内两款纯真ip数据库与ipip.net免费版数据库进行操作,当输入一个ip地址或域名时可以看到输 ...

  3. qq纯真IP库安装及更新

    QQ纯真IP库和命令,其主要功能就是把一些网络工具的输出的IP字符串,附加上地理位置信息(使用纯真数据库).例如218.65.137.1会变成218.65.137.1[广西南宁市电信]. qqwry. ...

  4. C# 调用IP库(QQWry.Dat)查询IP位置及自动升级IP库方法(附IP库下载地址及相关dll下载)

    前言 C# 用IP地址(123.125.114.144)查询位置(北京市百度公司)的东西,非常好用也非常方便,可手动升级刷新IP库,一次编码永久收益,可支持winform.asp.net等程序. 本文 ...

  5. C# 调用IP库(QQWry.Dat)查询IP位置及自动升级IP库方法(附IP库下载地址及相关dll下载)...

    前言 C# 用IP地址(123.125.114.144)查询位置(北京市百度公司)的东西,非常好用也非常方便,可手动升级刷新IP库,一次编码永久收益,可支持winform.asp.net等程序. 本文 ...

  6. 利用纯真IP库建立mysql ip数据库

    首先到http://www.cz88.net/  下载一个最新的ip库,安装ip库后会生成一个快捷方式,如下图所示: 点击打开后,出现下图界面: 点击解压,会生成一个20多兆的文本文件,打开或者下载一 ...

  7. C# 调用IP库(QQWry.Dat)查询IP位置及自动升级IP库方法【转】

    前言 C# 用IP地址(123.125.114.144)查询位置(北京市百度公司)的东西,非常好用也非常方便,可手动升级刷新IP库,一次编码永久收益,可支持winform.asp.net等程序. 本文 ...

  8. 根据IP地址获取真实IP所在地区 ,使用纯真IP库(纯真版20160215)

     package com.alpha.util; import java.io.ByteArrayOutputStream; import java.io.IOException; import ...

  9. Discuz!开发之替换系统IP库为纯真IP库的方法

    Discuz!默认使用的IP库只有1M,运行时占用资源少,但很多ip不准确,更新也慢,默认存放的路径在data/ipdata/tinyipdata.dat,这里给大家介绍如何将系统IP库换成纯真ip库 ...

最新文章

  1. STM8L探索套件学习笔记(转)
  2. 国内企业应如何实施ITSM
  3. httpclient 小例子编写
  4. angular接口传参
  5. windows10 搜索桌面搜索功能失效的解决
  6. 美国科技投资交易约4.1%来自中国 投资仍然很困难
  7. Struts2(一)之认识struts2
  8. Nginx源码分析 - Event事件篇 - Epoll事件模块(19)
  9. python程序员到哪里_Python程序员都知道的入门知识の五
  10. python zookeeper api_zookeeper java api介绍
  11. 推荐 干掉垃圾流氓插件得批处理文件和注册表文件
  12. word2016 脚注问题总结
  13. 正则表达式输入框验证正整数、负整数、小数点
  14. 伽罗华域(Galois Field)有限域元素生成和运算原理
  15. 在Windows电脑上修改图片内存大小的方法
  16. 2022高处安装、维护、拆除考试模拟100题及模拟考试
  17. 渗透测试sec123笔记
  18. Linux之find 命令
  19. 带符号的矩阵进行运算时,出现conj
  20. hydrus1d使用说明_HYDRUS——1D中文说明书.pdf

热门文章

  1. Docker安装和helloworld
  2. 神经网络与机器学习 笔记—多层感知器(MLP)
  3. hdu4825 字典树 + 贪心
  4. 【C 语言】二级指针作为输入 ( 自定义二级指针内存 | 二级指针排序 | 抽象业务逻辑函数 )
  5. 【Android 逆向】函数拦截实例 ( 函数拦截流程 | ① 定位动态库及函数位置 )
  6. 【错误记录】Oboe / AAudio 播放器报错 ( onEventFromServer - AAUDIO_SERVICE_EVENT_DISCONNECTED - FIFO cleared )
  7. 【MATLAB】数据类型 ( 矩阵 | 随机数函数 | 生成矩阵 )
  8. 【Flutter】Flutter 拍照示例 ( 创建应用 | 安装 image_picker 插件 )
  9. 【IOS 开发】Objective - C 语法 之 流程控制
  10. Docker批量操作容器