基于纯真本地数据库的 IP 地址查询 PHP 源码

mengkun 46.8K 44

之前介绍过很多第三方的 IP 地址查询 API 接口,详见: 分享几个IP获取地理位置的API接口,直接调用第三方的接口很方便,但也容易失效导致无法使用。因此今天来分享一个基于本地数据库的 IP 地址查询源码!

模块代码

PHP
  1. <?php
  2. /**
  3. * 纯真 IP 数据库查询
  4. *
  5. * 参考资料:
  6. * - 纯真 IP 数据库 http://www.cz88.net/ip/
  7. * - PHP 读取纯真IP地址数据库 http://ju.outofmemory.cn/entry/42500
  8. * - 纯真 IP 数据库自动更新文件教程 https://www.22vd.com/40035.html
  9. * - IpLocation https://github.com/nauxliu/IpLocation/
  10. * - 基于本地数据库的 IP 地址查询 PHP 源码 https://mkblog.cn/?p=1951
  11. *
  12. * 使用示例:
  13. * $ip = new IPQuery();
  14. * $addr = $ip->query('IP地址');
  15. * print_r($addr);
  16. */
  17.  
  18. class IPQuery {
  19. private $fh; // IP数据库文件句柄
  20. private $first; // 第一条索引
  21. private $last; // 最后一条索引
  22. private $total; // 索引总数
  23. private $dbFile = __DIR__ . DIRECTORY_SEPARATOR . 'qqwry.dat'; // 纯真 IP 数据库文件存放路径
  24. private $dbExpires = 86400 * 10; // 数据库文件有效期(10天)如无需自动更新 IP 数据库,请将此值改为 0
  25. // 构造函数
  26. function __construct() {
  27. // IP 数据库文件不存在或已过期,则自动获取
  28. if(!file_exists($this->dbFile) || ($this->dbExpires && ((time() - filemtime($this->dbFile)) > $this->dbExpires))) {
  29. $this->update();
  30. }
  31. }
  32. // 忽略超时
  33. private function ignore_timeout() {
  34. @ignore_user_abort(true);
  35. @ini_set('max_execution_time', 48 * 60 * 60);
  36. @set_time_limit(48 * 60 * 60); // set_time_limit(0) 2day
  37. @ini_set('memory_limit', '4000M');// 4G;
  38. }
  39. // 读取little-endian编码的4个字节转化为长整型数
  40. private function getLong4() {
  41. $result = unpack('Vlong', fread($this->fh, 4));
  42. return $result['long'];
  43. }
  44. // 读取little-endian编码的3个字节转化为长整型数
  45. private function getLong3() {
  46. $result = unpack('Vlong', fread($this->fh, 3).chr(0));
  47. return $result['long'];
  48. }
  49. // 查询位置信息
  50. private function getPos($data = '') {
  51. $char = fread($this->fh, 1);
  52. while (ord($char) != 0) { // 地区信息以 0 结束
  53. $data .= $char;
  54. $char = fread($this->fh, 1);
  55. }
  56. return $data;
  57. }
  58. // 查询运营商
  59. private function getISP() {
  60. $byte = fread($this->fh, 1); // 标志字节
  61. switch (ord($byte)) {
  62. case 0: $area = ''; break; // 没有相关信息
  63. case 1: // 被重定向
  64. fseek($this->fh, $this->getLong3());
  65. $area = $this->getPos(); break;
  66. case 2: // 被重定向
  67. fseek($this->fh, $this->getLong3());
  68. $area = $this->getPos(); break;
  69. default: $area = $this->getPos($byte); break; // 没有被重定向
  70. }
  71. return $area;
  72. }
  73. // 检查 IP 格式是否正确
  74. public function checkIp($ip) {
  75. $arr = explode('.', $ip);
  76. if(count($arr) != 4) return false;
  77. for ($i = 0; $i < 4; $i++) {
  78. if ($arr[$i] < '0' || $arr[$i] > '255') {
  79. return false;
  80. }
  81. }
  82. return true;
  83. }
  84. // 查询 IP 地址
  85. public function query($ip) {
  86. if(!$this->checkIp($ip)) {
  87. return false;
  88. }
  89. $this->fh = fopen($this->dbFile, 'rb');
  90. $this->first = $this->getLong4();
  91. $this->last = $this->getLong4();
  92. $this->total = ($this->last - $this->first) / 7; // 每条索引7字节
  93. $ip = pack('N', intval(ip2long($ip)));
  94. // 二分查找 IP 位置
  95. $l = 0;
  96. $r = $this->total;
  97. while($l <= $r) {
  98. $m = floor(($l + $r) / 2); // 计算中间索引
  99. fseek($this->fh, $this->first + $m * 7);
  100. $beginip = strrev(fread($this->fh, 4)); // 中间索引的开始IP地址
  101. fseek($this->fh, $this->getLong3());
  102. $endip = strrev(fread($this->fh, 4)); // 中间索引的结束IP地址
  103. if ($ip < $beginip) { // 用户的IP小于中间索引的开始IP地址时
  104. $r = $m - 1;
  105. } else {
  106. if ($ip > $endip) { // 用户的IP大于中间索引的结束IP地址时
  107. $l = $m + 1;
  108. } else { // 用户IP在中间索引的IP范围内时
  109. $findip = $this->first + $m * 7;
  110. break;
  111. }
  112. }
  113. }
  114. // 查找 IP 地址段
  115. fseek($this->fh, $findip);
  116. $location['beginip'] = long2ip($this->getLong4()); // 用户IP所在范围的开始地址
  117. $offset = $this->getlong3();
  118. fseek($this->fh, $offset);
  119. $location['endip'] = long2ip($this->getLong4()); // 用户IP所在范围的结束地址
  120. // 查找 IP 信息
  121. $byte = fread($this->fh, 1); // 标志字节
  122. switch (ord($byte)) {
  123. case 1: // 都被重定向
  124. $countryOffset = $this->getLong3(); // 重定向地址
  125. fseek($this->fh, $countryOffset);
  126. $byte = fread($this->fh, 1); // 标志字节
  127. switch (ord($byte)) {
  128. case 2: // 信息被二次重定向
  129. fseek($this->fh, $this->getLong3());
  130. $location['pos'] = $this->getPos();
  131. fseek($this->fh, $countryOffset + 4);
  132. $location['isp'] = $this->getISP();
  133. break;
  134. default: // 信息没有被二次重定向
  135. $location['pos'] = $this->getPos($byte);
  136. $location['isp'] = $this->getISP();
  137. break;
  138. }
  139. break;
  140. case 2: // 信息被重定向
  141. fseek($this->fh, $this->getLong3());
  142. $location['pos'] = $this->getPos();
  143. fseek($this->fh, $offset + 8);
  144. $location['isp'] = $this->getISP();
  145. break;
  146. default: // 信息没有被重定向
  147. $location['pos'] = $this->getPos($byte);
  148. $location['isp'] = $this->getISP();
  149. break;
  150. }
  151. // 信息转码处理
  152. foreach ($location as $k => $v) {
  153. $location[$k] = iconv('gb2312', 'utf-8', $v);
  154. $location[$k] = preg_replace(array('/^.*CZ88\.NET.*$/isU', '/^.*纯真.*$/isU', '/^.*日IP数据/'), '', $location[$k]);
  155. $location[$k] = htmlspecialchars($location[$k]);
  156. }
  157. return $location;
  158. }
  159. // 更新数据库 https://www.22vd.com/40035.html
  160. public function update() {
  161. $this->ignore_timeout();
  162. $copywrite = file_get_contents('http://update.cz88.net/ip/copywrite.rar');
  163. $qqwry = file_get_contents('http://update.cz88.net/ip/qqwry.rar');
  164. $key = unpack('V6', $copywrite)[6];
  165. for($i = 0; $i < 0x200; $i++) {
  166. $key *= 0x805;
  167. $key ++;
  168. $key = $key & 0xFF;
  169. $qqwry[$i] = chr(ord($qqwry[$i]) ^ $key);
  170. }
  171. $qqwry = gzuncompress($qqwry);
  172. file_put_contents($this->dbFile, $qqwry);
  173. }
  174. // 析构函数
  175. function __destruct() {
  176. if($this->fh) {
  177. fclose($this->fh);
  178. }
  179. $this->fp = null;
  180. }
  181. }
复制 文本 高亮

使用方法

将上面的模块代码保存为 IPQuery.class.php,然后按照如下方法调用即可:

PHP
  1. <?php
  2. require_once('IPQuery.class.php');
  3.  
  4. $ip = new IPQuery();
  5. $addr = $ip->query('123.233.233.233');
  6.  
  7. echo "<pre>
  8. IP起始段:{$addr['beginip']}
  9. IP结束段:{$addr['endip']}
  10. 实际地址:{$addr['pos']}
  11. 运 营 商:{$addr['isp']}
  12. </pre>";
复制 文本 高亮

输出效果如下所示:

基于纯真本地数据库的 IP 地址查询 PHP 源码

注意事项

本模块会在第一次被调用时自动从纯真网下载最新的 IP 数据库到本地,因此第一次进行查询时会有点慢。如果你的服务器因为某些原因,无法连接到纯真网获取数据库,可以直接下载离线版,并将 IPQuery.class.php 第 25 行的 $dbExpires 值改为“0”(即永不自动更新数据库)。

离线版下载

发表评论 取消回复
表情 图片 链接 代码

  1. HelloTools
    HelloTools Lv 1

    不错不错,谢谢大佬

  2. iMin博客
    iMin博客 Lv 1

    这个其实还是挺实用了,先收藏着

  3. 丷
    Lv 1

    我测试报
    Warning: file_get_contents(http://update.cz88.net/ip/copywrite.rar): failed to open stream: HTTP request failed! in E:\WWWROOT\127.0.0.4\IPQuery.class.php on line 181

    Warning: file_get_contents(http://update.cz88.net/ip/qqwry.rar): failed to open stream: HTTP request failed! in E:\WWWROOT\127.0.0.4\IPQuery.class.php on line 182

    Warning: unpack(): Type V: not enough input, need 4, have 0 in E:\WWWROOT\127.0.0.4\IPQuery.class.php on line 183

    Warning: gzuncompress() expects parameter 1 to be string, array given in E:\WWWROOT\127.0.0.4\IPQuery.class.php on line 190

    Warning: unpack(): Type V: not enough input, need 4, have 0 in E:\WWWROOT\127.0.0.4\IPQuery.class.php on line 44

    Warning: unpack(): Type V: not enough input, need 4, have 0 in E:\WWWROOT\127.0.0.4\IPQuery.class.php on line 44

    Warning: unpack(): Type V: not enough input, need 4, have 1 in E:\WWWROOT\127.0.0.4\IPQuery.class.php on line 50

    Warning: unpack(): Type V: not enough input, need 4, have 0 in E:\WWWROOT\127.0.0.4\IPQuery.class.php on line 44

    Warning: unpack(): Type V: not enough input, need 4, have 1 in E:\WWWROOT\127.0.0.4\IPQuery.class.php on line 50

    Warning: unpack(): Type V: not enough input, need 4, have 0 in E:\WWWROOT\127.0.0.4\IPQuery.class.php on line 44

    这么多错

    • code
      code Lv 1

      @丷没问题的,不存在问题

  4. 甜力怕
    甜力怕 Lv 1

    感谢分享!

  5. telnet
    telnet Lv 1

    大神,向你求一套影视源码哦,就是那个VIP影院的(movie.lingtings.com),我在里面找来找去还是找到了你,盼回复,感谢!

    • mengkun
      mengkun 站长

      @telnet该代码已绝版。不出售也不继续开发

  6. Noah
    Noah Lv 1

    转了哈哈~~

  7. 暴躁哥
    暴躁哥 Lv 1

    感谢分享

  8. 羽忆
    羽忆 Lv 1

    一般我查ip之类的都去 ipip.net dog12

  9. 郑州泰达展柜
    郑州泰达展柜 Lv 1

    你好,我想问一个问题:利用这个查询ip的php代码。我怎么才能在织梦或者别的php程序里的留言功能时,通过后台留言管理里用户的ip,显示出用户所在地址。

  10. 李富贵
    李富贵 Lv 1

    谢谢分享
    Mark

  11. 李
    Lv 1

    天——
    Can U speak Chinese.....
    Mark

  12. 往前
    往前 Lv 1

    谢谢分享

  13. 果
    Lv 1

    你博客是买哪里的主机

  14. 阿珏
    阿珏 Lv 3

    好歹是搞c++的,怎么没有 来点c++代码

  15. 额
    Lv 1

    https://www.wuzuowei.net/16078.html
    QQ解封教程,不知道有用没,大佬试试

    • mengkun
      mengkun 站长

      @额这个方法当天就凉了

  16. 额
    Lv 1

    呃呃呃,大佬能不能搞个PHP加解密教程工具

    • mengkun
      mengkun 站长

      @额PHP 解密我也不会呀二哈

  17. 知识共享网
    知识共享网 Lv 1

    正好需要,感谢

  18. 完美者
    完美者 Lv 5

    居然是3周前更新的,我是多久没上网了

  19. pony
    pony Lv 1

    厉害了

  20. 昵称
    昵称 Lv 1

    nb了

  21. 洒洒水
    洒洒水 Lv 1

    我只能说牛逼~

  22. 韩涛博客
    韩涛博客 Lv 1

    更新了,快来看看新文章

  23. 权哥
    权哥 Lv 1

    大佬终于等到你

  24. Huelse
    Huelse Lv 1

    可以可以

  25. 杨小杰博客
    杨小杰博客 Lv 3

    厉害竟然更新了

  26. 九七博客
    九七博客 Lv 1

    老哥,啥时候更新一个校园表白墙程序?害羞

  27. ivip
    ivip Lv 1

    天哪,你总算更新了,太难得了。

  28. 轩沫博客
    轩沫博客 Lv 4

    大佬难得更新~

  29. LEELON
    LEELON Lv 1

    难得更新dog6

  30. Langlangago
    Langlangago Lv 1

    大佬居然更了