基于本地数据库的IP地址查询[php源码]

(゜-゜)つロ 干杯~

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

模块代码

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

使用方法

将上面的模块代码保存为 IPQuery.class.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 数据库到本地,因此第一次进行查询时会有点慢。如果你的服务器因为某些原因,无法连接到纯真网获取数据库,可以直接下载离线版,并将 IPQuery.class.php 第 25 行的 $dbExpires 值改为“0”(即永不自动更新数据库)。

   

如果资源下载失效请到 留言板 留言或者 QQ 联系我。

本站默认解压密码:www.hibbba.com

(゜-゜)つロ 干杯~

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: