http://blog.csdn.net/clin003/archive/2007/08/14/1743157.aspx

利用 QQWry.Dat 实现 IP 地址高效检索(PHP)
根据 LumaQQ 开发者文档中的纯真 IP 数据库格式详解,我编写了一个 PHP 的查询
IP 所在地区信息的类。在编写过程中发现纯真 IP 数据库格式详解中关于记录区的描述不是很全面,不过出入也不是很大,所以我没必要再写一份纯真 IP
数据库的格式说明了,大家感兴趣的话,读一读下面的代码应该就能看出来了。代码中加了很详细的注释,应该很容易读懂的。

在创建这个类的一个实例后,实例中就保存了打开的文件指针和一些查询需要的信息,每次查询时不需要重新打开文件,直到页面执行结束后,打开的文件才会自动关闭。这样。在一个页面内进行多次查询时,效率是很高的。并且此类不仅可以直接查询
IP,还可以自动将域名解析为 IP 进行查询。

下面是程序代码:

  1. <?php
  2. /**
  3. * IP 地理位置查询类
  4. *
  5. * @author 马秉尧
  6. * @version 1.5
  7. * @copyright 2005 CoolCode.CN
  8. */
  9. class IpLocation {
  10. /**
  11. * QQWry.Dat文件指针
  12. *
  13. * @var resource
  14. */
  15. var $fp;
  16. /**
  17. * 第一条IP记录的偏移地址
  18. *
  19. * @var int
  20. */
  21. var $firstip;
  22. /**
  23. * 最后一条IP记录的偏移地址
  24. *
  25. * @var int
  26. */
  27. var $lastip;
  28. /**
  29. * IP记录的总条数(不包含版本信息记录)
  30. *
  31. * @var int
  32. */
  33. var $totalip;
  34. /**
  35. * 返回读取的长整型数
  36. *
  37. * @access private
  38. * @return int
  39. */
  40. function getlong() {
  41. //将读取的little-endian编码的4个字节转化为长整型数
  42. $result = unpack('Vlong', fread($this->fp, 4));
  43. return $result['long'];
  44. }
  45. /**
  46. * 返回读取的3个字节的长整型数
  47. *
  48. * @access private
  49. * @return int
  50. */
  51. function getlong3() {
  52. //将读取的little-endian编码的3个字节转化为长整型数
  53. $result = unpack('Vlong', fread($this->fp, 3).chr(0));
  54. return $result['long'];
  55. }
  56. /**
  57. * 返回压缩后可进行比较的IP地址
  58. *
  59. * @access private
  60. * @param string $ip
  61. * @return string
  62. */
  63. function packip($ip) {
  64. // 将IP地址转化为长整型数,如果在PHP5中,IP地址错误,则返回False,
  65. // 这时intval将Flase转化为整数-1,之后压缩成big-endian编码的字符串
  66. return pack('N', intval(ip2long($ip)));
  67. }
  68. /**
  69. * 返回读取的字符串
  70. *
  71. * @access private
  72. * @param string $data
  73. * @return string
  74. */
  75. function getstring($data = "") {
  76. $char = fread($this->fp, 1);
  77. while (ord($char) > 0) {        // 字符串按照C格式保存,以�结束
  78. $data .= $char;             // 将读取的字符连接到给定字符串之后
  79. $char = fread($this->fp, 1);
  80. }
  81. return $data;
  82. }
  83. /**
  84. * 返回地区信息
  85. *
  86. * @access private
  87. * @return string
  88. */
  89. function getarea() {
  90. $byte = fread($this->fp, 1);    // 标志字节
  91. switch (ord($byte)) {
  92. case 0:                     // 没有区域信息
  93. $area = "";
  94. break;
  95. case 1:
  96. case 2:                     // 标志字节为1或2,表示区域信息被重定向
  97. fseek($this->fp, $this->getlong3());
  98. $area = $this->getstring();
  99. break;
  100. default:                    // 否则,表示区域信息没有被重定向
  101. $area = $this->getstring($byte);
  102. break;
  103. }
  104. return $area;
  105. }
  106. /**
  107. * 根据所给 IP 地址或域名返回所在地区信息
  108. *
  109. * @access public
  110. * @param string $ip
  111. * @return array
  112. */
  113. function getlocation($ip) {
  114. if (!$this->fp) return null;            // 如果数据文件没有被正确打开,则直接返回空
  115. $location['ip'] = gethostbyname($ip);   // 将输入的域名转化为IP地址
  116. $ip = $this->packip($location['ip']);   // 将输入的IP地址转化为可比较的IP地址
  117. // 不合法的IP地址会被转化为255.255.255.255
  118. // 对分搜索
  119. $l = 0;                         // 搜索的下边界
  120. $u = $this->totalip;            // 搜索的上边界
  121. $findip = $this->lastip;        // 如果没有找到就返回最后一条IP记录(QQWry.Dat的版本信息)
  122. while ($l <= $u) {              // 当上边界小于下边界时,查找失败
  123. $i = floor(($l + $u) / 2);  // 计算近似中间记录
  124. fseek($this->fp, $this->firstip + $i * 7);
  125. $beginip = strrev(fread($this->fp, 4));     // 获取中间记录的开始IP地址
  126. // strrev函数在这里的作用是将little-endian的压缩IP地址转化为big-endian的格式
  127. // 以便用于比较,后面相同。
  128. if ($ip < $beginip) {       // 用户的IP小于中间记录的开始IP地址时
  129. $u = $i - 1;            // 将搜索的上边界修改为中间记录减一
  130. }
  131. else {
  132. fseek($this->fp, $this->getlong3());
  133. $endip = strrev(fread($this->fp, 4));   // 获取中间记录的结束IP地址
  134. if ($ip > $endip) {     // 用户的IP大于中间记录的结束IP地址时
  135. $l = $i + 1;        // 将搜索的下边界修改为中间记录加一
  136. }
  137. else {                  // 用户的IP在中间记录的IP范围内时
  138. $findip = $this->firstip + $i * 7;
  139. break;              // 则表示找到结果,退出循环
  140. }
  141. }
  142. }
  143. //获取查找到的IP地理位置信息
  144. fseek($this->fp, $findip);
  145. $location['beginip'] = long2ip($this->getlong());   // 用户IP所在范围的开始地址
  146. $offset = $this->getlong3();
  147. fseek($this->fp, $offset);
  148. $location['endip'] = long2ip($this->getlong());     // 用户IP所在范围的结束地址
  149. $byte = fread($this->fp, 1);    // 标志字节
  150. switch (ord($byte)) {
  151. case 1:                     // 标志字节为1,表示国家和区域信息都被同时重定向
  152. $countryOffset = $this->getlong3();         // 重定向地址
  153. fseek($this->fp, $countryOffset);
  154. $byte = fread($this->fp, 1);    // 标志字节
  155. switch (ord($byte)) {
  156. case 2:             // 标志字节为2,表示国家信息又被重定向
  157. fseek($this->fp, $this->getlong3());
  158. $location['country'] = $this->getstring();
  159. fseek($this->fp, $countryOffset + 4);
  160. $location['area'] = $this->getarea();
  161. break;
  162. default:            // 否则,表示国家信息没有被重定向
  163. $location['country'] = $this->getstring($byte);
  164. $location['area'] = $this->getarea();
  165. break;
  166. }
  167. break;
  168. case 2:                     // 标志字节为2,表示国家信息被重定向
  169. fseek($this->fp, $this->getlong3());
  170. $location['country'] = $this->getstring();
  171. fseek($this->fp, $offset + 8);
  172. $location['area'] = $this->getarea();
  173. break;
  174. default:                    // 否则,表示国家信息没有被重定向
  175. $location['country'] = $this->getstring($byte);
  176. $location['area'] = $this->getarea();
  177. break;
  178. }
  179. if ($location['country'] == " CZ88.NET") {  // CZ88.NET表示没有有效信息
  180. $location['country'] = "未知";
  181. }
  182. if ($location['area'] == " CZ88.NET") {
  183. $location['area'] = "";
  184. }
  185. return $location;
  186. }
  187. /**
  188. * 构造函数,打开 QQWry.Dat 文件并初始化类中的信息
  189. *
  190. * @param string $filename
  191. * @return IpLocation
  192. */
  193. function IpLocation($filename = "QQWry.Dat") {
  194. $this->fp = 0;
  195. if (($this->fp = @fopen($filename, 'rb')) !== false) {
  196. $this->firstip = $this->getlong();
  197. $this->lastip = $this->getlong();
  198. $this->totalip = ($this->lastip - $this->firstip) / 7;
  199. //注册析构函数,使其在程序执行结束时执行
  200. register_shutdown_function(array(&$this, '_IpLocation'));
  201. }
  202. }
  203. /**
  204. * 析构函数,用于在页面执行结束后自动关闭打开的文件。
  205. *
  206. */
  207. function _IpLocation() {
  208. if ($this->fp) {
  209. fclose($this->fp);
  210. }
  211. $this->fp = 0;
  212. }
  213. }
  214. ?>
  1. Discuz  5.0 不在使用自己的IP数据,而是使用纯真IP的数据格式, 存取纯真IP数据库稍微有点麻烦,它的存储格式比较特殊也很有趣,具体的格式分析参考下面两个链接,其他语言实现参考文章末的链接。
  2. 《纯真IP数据库格式详解》
  3. 链接一:http://blog.csdn.Net/heiyeshuwu/archive/2006/05/12/725675.aspx
  4. 链接二:http://lumaqq.Linuxsir.org/article/qqwry_format_detail.html
  5. 纯真IP数据库官网:http://www.cz88.Net/ip/
  6. 纯真IP数据库下载:http://update.cz88.Net/soft/qqwry.rar
  7. 以下函数conrvertip()位于 Discuz!5_GBK/upload/include/misc.func.Php 路径中,有兴趣可以具体去阅读分析。(下面代码我做了简单的修改,更便于阅读,核心没有修改)
  8. <?
  9. //===================================
  10. //
  11. // 功能:IP地址获取真实地址函数
  12. // 参数:$ip - IP地址
  13. // 作者:[Discuz!] (C) Comsenz Inc.
  14. //
  15. //===================================
  16. function convertip($ip) {
  17. //IP数据文件路径
  18. $dat_path = 'QQWry.Dat';
  19. //检查IP地址
  20. if(!preg_match("/^d{1,3}.d{1,3}.d{1,3}.d{1,3}$/", $ip)) {
  21. return 'IP Address Error';
  22. }
  23. //打开IP数据文件
  24. if(!$fd = @fopen($dat_path, 'rb')){
  25. return 'IP date file not exists or access denied';
  26. }
  27. //分解IP进行运算,得出整形数
  28. $ip = explode('.', $ip);
  29. $ipNum = $ip[0] * 16777216 + $ip[1] * 65536 + $ip[2] * 256 + $ip[3];
  30. //获取IP数据索引开始和结束位置
  31. $DataBegin = fread($fd, 4);
  32. $DataEnd = fread($fd, 4);
  33. $ipbegin = implode('', unpack('L', $DataBegin));
  34. if($ipbegin < 0) $ipbegin += pow(2, 32);
  35. $ipend = implode('', unpack('L', $DataEnd));
  36. if($ipend < 0) $ipend += pow(2, 32);
  37. $ipAllNum = ($ipend - $ipbegin) / 7 + 1;
  38. $BeginNum = 0;
  39. $EndNum = $ipAllNum;
  40. //使用二分查找法从索引记录中搜索匹配的IP记录
  41. while($ip1num>$ipNum || $ip2num<$ipNum) {
  42. $Middle= intval(($EndNum + $BeginNum) / 2);
  43. //偏移指针到索引位置读取4个字节
  44. fseek($fd, $ipbegin + 7 * $Middle);
  45. $ipData1 = fread($fd, 4);
  46. if(strlen($ipData1) < 4) {
  47. fclose($fd);
  48. return 'System Error';
  49. }
  50. //提取出来的数据转换成长整形,如果数据是负数则加上2的32次幂
  51. $ip1num = implode('', unpack('L', $ipData1));
  52. if($ip1num < 0) $ip1num += pow(2, 32);
  53. //提取的长整型数大于我们IP地址则修改结束位置进行下一次循环
  54. if($ip1num > $ipNum) {
  55. $EndNum = $Middle;
  56. continue;
  57. }
  58. //取完上一个索引后取下一个索引
  59. $DataSeek = fread($fd, 3);
  60. if(strlen($DataSeek) < 3) {
  61. fclose($fd);
  62. return 'System Error';
  63. }
  64. $DataSeek = implode('', unpack('L', $DataSeek.chr(0)));
  65. fseek($fd, $DataSeek);
  66. $ipData2 = fread($fd, 4);
  67. if(strlen($ipData2) < 4) {
  68. fclose($fd);
  69. return 'System Error';
  70. }
  71. $ip2num = implode('', unpack('L', $ipData2));
  72. if($ip2num < 0) $ip2num += pow(2, 32);
  73. //没找到提示未知
  74. if($ip2num < $ipNum) {
  75. if($Middle == $BeginNum) {
  76. fclose($fd);
  77. return 'Unknown';
  78. }
  79. $BeginNum = $Middle;
  80. }
  81. }
  82. //下面的代码读晕了,没读明白,有兴趣的慢慢读
  83. $ipFlag = fread($fd, 1);
  84. if($ipFlag == chr(1)) {
  85. $ipSeek = fread($fd, 3);
  86. if(strlen($ipSeek) < 3) {
  87. fclose($fd);
  88. return 'System Error';
  89. }
  90. $ipSeek = implode('', unpack('L', $ipSeek.chr(0)));
  91. fseek($fd, $ipSeek);
  92. $ipFlag = fread($fd, 1);
  93. }
  94. if($ipFlag == chr(2)) {
  95. $AddrSeek = fread($fd, 3);
  96. if(strlen($AddrSeek) < 3) {
  97. fclose($fd);
  98. return 'System Error';
  99. }
  100. $ipFlag = fread($fd, 1);
  101. if($ipFlag == chr(2)) {
  102. $AddrSeek2 = fread($fd, 3);
  103. if(strlen($AddrSeek2) < 3) {
  104. fclose($fd);
  105. return 'System Error';
  106. }
  107. $AddrSeek2 = implode('', unpack('L', $AddrSeek2.chr(0)));
  108. fseek($fd, $AddrSeek2);
  109. } else {
  110. fseek($fd, -1, SEEK_CUR);
  111. }
  112. while(($char = fread($fd, 1)) != chr(0))
  113. $ipAddr2 .= $char;
  114. $AddrSeek = implode('', unpack('L', $AddrSeek.chr(0)));
  115. fseek($fd, $AddrSeek);
  116. while(($char = fread($fd, 1)) != chr(0))
  117. $ipAddr1 .= $char;
  118. } else {
  119. fseek($fd, -1, SEEK_CUR);
  120. while(($char = fread($fd, 1)) != chr(0))
  121. $ipAddr1 .= $char;
  122. $ipFlag = fread($fd, 1);
  123. if($ipFlag == chr(2)) {
  124. $AddrSeek2 = fread($fd, 3);
  125. if(strlen($AddrSeek2) < 3) {
  126. fclose($fd);
  127. return 'System Error';
  128. }
  129. $AddrSeek2 = implode('', unpack('L', $AddrSeek2.chr(0)));
  130. fseek($fd, $AddrSeek2);
  131. } else {
  132. fseek($fd, -1, SEEK_CUR);
  133. }
  134. while(($char = fread($fd, 1)) != chr(0)){
  135. $ipAddr2 .= $char;
  136. }
  137. }
  138. fclose($fd);
  139. //最后做相应的替换操作后返回结果
  140. if(preg_match('/http/i', $ipAddr2)) {
  141. $ipAddr2 = '';
  142. }
  143. $ipaddr = "$ipAddr1 $ipAddr2";
  144. $ipaddr = preg_replace('/CZ88.Net/is', '', $ipaddr);
  145. $ipaddr = preg_replace('/^s*/is', '', $ipaddr);
  146. $ipaddr = preg_replace('/s*$/is', '', $ipaddr);
  147. if(preg_match('/http/i', $ipaddr) || $ipaddr == '') {
  148. $ipaddr = 'Unknown';
  149. }
  150. return $ipaddr;
  151. }
  152. //========================
  153. //
  154. //  调用举例(速度很快)
  155. //
  156. //========================
  157. echo convertip('219.238.235.10');
  158. //输出: 北京市 电信通
  159. echo convertip('23.56.82.12');
  160. //输出:IANA
  161. echo convertip('250.69.52.0');
  162. //输出:IANA保留地址
  163. echo convertip('238.69.52.0');
  164. //输出:IANA保留地址 用于多点传送
  165. echo convertip('192.168.0.1');
  166. //输出:局域网 对方和您在同一内部网
  167. echo convertip('255.255.255.255');
  168. //输出:纯真网络 2006年11月20日IP数据
  169. ?>

附:(相应其他实现程序)

Php)
"
 href=
"
http
:
//
www.coolcode.cn/?p=16" rel=bookmark>利用 QQWry.Dat 实现 IP 地址高效检索(Php)(作者: andot)

数据库(QQWry
.
Dat)查询 C源码
"
 href=
"
http
:
//
www.douzi.org/wp/index.Php/articles/71" rel=bookmark>纯真IP数据库(QQWry.Dat)查询 C源码 (作者:Windix)

转载纯真ip库的更多相关文章

  1. lib-qqwry v1.0 发布 nodejs解析纯真IP库(qqwry.dat)

    lib-qqwry是当初学习node时用来练手的一个模块,用来解析纯真IP库的 现在发一个v1.0版本弥补我当时稚嫩的代码. 意外收获是,整理代码后发现,相比v0.x版本 急速模式下的效率提升大概20 ...

  2. 使用纯真IP库获取用户端地理位置信息

    引言 在一些电商类或者引流类的网站中经常会有获取用户地理位置信息的需求,下面我分享一个用纯真IP库获取用户地理位置信息的方案. 正文 第一步:本文的方案是基于纯真IP库的,所以首先要去下载最新的纯真I ...

  3. PHP Swoole 基于纯真IP库根据IP匹配城市

    把纯真IP库读到内存,纯真IP库本来就是有序的,然后每次请求二分查找就行,44WIP查找十几次就搞定了 dispatch_mode最好写3,不然做服务的时候,会导致进程任务分配不均匀. max_req ...

  4. Java使用纯真IP库获取IP对应省份和城市

    原文:http://blog.csdn.net/chwshuang/article/details/78027873?locationNum=10&fps=1 Java使用纯真IP库获取IP对 ...

  5. qqwry - 纯真ip库的golang服务

    qqwry 纯真 IP 库的一个服务.通过http提供一个ip地址归属地查询支持 软件介绍 我们大家做网站的时候,都会需要将用户的IP地址转换为归属地址功能,而之前的作法大都是从硬盘的数据文件中读取, ...

  6. 解析纯真IP地址库

    一周以来,一直在做 IP地址库的解析.从调研到编码到优化,大概花了有七八天的时间.感觉很好玩.总结一下整个做的过程. 1.关于IP 地址库的解析方式 目前主要的解析方式有两种:通过API,或通过IP数 ...

  7. 纯真IP根据IP地址获得地址

    <?php /** * 纯真IP根据IP地址获得地址 */ class ipLocation { public $fp; public $firstip; //第一条ip索引的偏移地址 publ ...

  8. PHP获取IP及地区信息(纯真IP数据库)

    昨天在写程序的时候,发现在用户的时候记录IP和地区信息也许以后用得上,去网上找了找,发现实现的方式有好多好多,因为我用的ThinkPHP,后来又去TP官网找了找,最后采用了下面这种方法. <?p ...

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

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

随机推荐

  1. Android TextWatcher应用实例

    (1)使用TextWathcer限制输入字符个数布局中EditText在android布局中经常用到,对EditText中输入的内容也经常需要进行限制,我们可以通过TextWatcher去观察输入框中 ...

  2. HDU 5820 Lights(扫描线+zkw线段树)

    [题目链接] http://acm.hdu.edu.cn/showproblem.php?pid=5820 [题目大意] 在一个大小为50000*50000的矩形中,有n个路灯. 询问是否每一对路灯之 ...

  3. javascript - Show mouse cursor in phantom.js - Stack Overflow

    javascript - Show mouse cursor in phantom.js - Stack Overflow Show mouse cursor in phantom.js

  4. MVC5.0 中如何提高Controller 的优先级

    //在area下建立的Home namespace WebApplication8.Areas.Weather.Controllers { public class HomeController : ...

  5. (转)openURL的使用方法

    view plaincopy to clipboardprint? [[UIApplication sharedApplication] openURL:[NSURL URLWithString:ap ...

  6. jQuery相关面试题

    1 你在公司是怎么用jquery的? 答:在项目中是怎么用的是看看你有没有项目经验(根据自己的实际情况来回答) 你用过的选择器啊,复选框啊,表单啊,ajax啊,事件等 配置Jquery环境 下载jqu ...

  7. Android应用开发提高篇(1)-----获取本地IP

    链接地址:http://www.cnblogs.com/lknlfy/archive/2012/02/21/2361802.html 一.概述 习惯了Linux下的网络编程,在还没用智能机之前就一直想 ...

  8. c++ primer plus 习题答案(3)

    p296.3 #include<iostream> #include<cstdlib> #include<string> #include<cstring&g ...

  9. BZOJ 1601: [Usaco2008 Oct]灌水( MST )

    MST , kruskal 直接跑 ---------------------------------------------------------------------- #include< ...

  10. Dojo实现Tabs页报错(二)