之前介紹過很多第三方的 IP 地址查詢 API 接口,詳見: 分享幾個IP獲取地理位置的API接口,直接調用第三方的接口很方便,但也容易失效導致無法使用。因此今天來分享一個基於本地數據庫的 IP 地址查詢源碼!
模塊代碼
<?php /** * 純真 IP 數據庫查詢 * * 參考資料: * - 純真 IP 數據庫 http://www.cz88.net/ip/ * - PHP 讀取純真IP地址數據庫 http://ju.outofmemory.cn/entry/42500 * - 純真 IP 數據庫自動更新文件教程 https://www.22vd.com/40035.html * - IpLocation https://github.com/nauxliu/IpLocation/ * - 基於本地數據庫的 IP 地址查詢 PHP 源碼 https://mkblog.cn/?p=1951 * * 使用示例: * $ip = new IPQuery(); * $addr = $ip->query('IP地址'); * print_r($addr); */ class IPQuery { private $fh; // IP數據庫文件句柄 private $first; // 第一條索引 private $last; // 最后一條索引 private $total; // 索引總數 private $dbFile = __DIR__ . DIRECTORY_SEPARATOR . 'qqwry.dat'; // 純真 IP 數據庫文件存放路徑 private $dbExpires = 86400 * 10; // 數據庫文件有效期(10天)如無需自動更新 IP 數據庫,請將此值改為 0 // 構造函數 function __construct() { // IP 數據庫文件不存在或已過期,則自動獲取 if(!file_exists($this->dbFile) || ($this->dbExpires && ((time() - filemtime($this->dbFile)) > $this->dbExpires))) { $this->update(); } } // 忽略超時 private function ignore_timeout() { @ignore_user_abort(true); @ini_set('max_execution_time', 48 * 60 * 60); @set_time_limit(48 * 60 * 60); // set_time_limit(0) 2day @ini_set('memory_limit', '4000M');// 4G; } // 讀取little-endian編碼的4個字節轉化為長整型數 private function getLong4() { $result = unpack('Vlong', fread($this->fh, 4)); return $result['long']; } // 讀取little-endian編碼的3個字節轉化為長整型數 private function getLong3() { $result = unpack('Vlong', fread($this->fh, 3).chr(0)); return $result['long']; } // 查詢位置信息 private function getPos($data = '') { $char = fread($this->fh, 1); while (ord($char) != 0) { // 地區信息以 0 結束 $data .= $char; $char = fread($this->fh, 1); } return $data; } // 查詢運營商 private function getISP() { $byte = fread($this->fh, 1); // 標志字節 switch (ord($byte)) { case 0: $area = ''; break; // 沒有相關信息 case 1: // 被重定向 fseek($this->fh, $this->getLong3()); $area = $this->getPos(); break; case 2: // 被重定向 fseek($this->fh, $this->getLong3()); $area = $this->getPos(); break; default: $area = $this->getPos($byte); break; // 沒有被重定向 } return $area; } // 檢查 IP 格式是否正確 public function checkIp($ip) { $arr = explode('.', $ip); if(count($arr) != 4) return false; for ($i = 0; $i < 4; $i++) { if ($arr[$i] < '0' || $arr[$i] > '255') { return false; } } return true; } // 查詢 IP 地址 public function query($ip) { if(!$this->checkIp($ip)) { return false; } $this->fh = fopen($this->dbFile, 'rb'); $this->first = $this->getLong4(); $this->last = $this->getLong4(); $this->total = ($this->last - $this->first) / 7; // 每條索引7字節 $ip = pack('N', intval(ip2long($ip))); // 二分查找 IP 位置 $l = 0; $r = $this->total; while($l <= $r) { $m = floor(($l + $r) / 2); // 計算中間索引 fseek($this->fh, $this->first + $m * 7); $beginip = strrev(fread($this->fh, 4)); // 中間索引的開始IP地址 fseek($this->fh, $this->getLong3()); $endip = strrev(fread($this->fh, 4)); // 中間索引的結束IP地址 if ($ip < $beginip) { // 用戶的IP小於中間索引的開始IP地址時 $r = $m - 1; } else { if ($ip > $endip) { // 用戶的IP大於中間索引的結束IP地址時 $l = $m + 1; } else { // 用戶IP在中間索引的IP范圍內時 $findip = $this->first + $m * 7; break; } } } // 查找 IP 地址段 fseek($this->fh, $findip); $location['beginip'] = long2ip($this->getLong4()); // 用戶IP所在范圍的開始地址 $offset = $this->getlong3(); fseek($this->fh, $offset); $location['endip'] = long2ip($this->getLong4()); // 用戶IP所在范圍的結束地址 // 查找 IP 信息 $byte = fread($this->fh, 1); // 標志字節 switch (ord($byte)) { case 1: // 都被重定向 $countryOffset = $this->getLong3(); // 重定向地址 fseek($this->fh, $countryOffset); $byte = fread($this->fh, 1); // 標志字節 switch (ord($byte)) { case 2: // 信息被二次重定向 fseek($this->fh, $this->getLong3()); $location['pos'] = $this->getPos(); fseek($this->fh, $countryOffset + 4); $location['isp'] = $this->getISP(); break; default: // 信息沒有被二次重定向 $location['pos'] = $this->getPos($byte); $location['isp'] = $this->getISP(); break; } break; case 2: // 信息被重定向 fseek($this->fh, $this->getLong3()); $location['pos'] = $this->getPos(); fseek($this->fh, $offset + 8); $location['isp'] = $this->getISP(); break; default: // 信息沒有被重定向 $location['pos'] = $this->getPos($byte); $location['isp'] = $this->getISP(); break; } // 信息轉碼處理 foreach ($location as $k => $v) {
//$location[$k] = iconv('gb2312','utf-8',$v); //---這里一定要注意字符集 $location[$k] = iconv('GBK', 'utf-8', $v); $location[$k] = preg_replace(array('/^.*CZ88\.NET.*$/isU', '/^.*純真.*$/isU', '/^.*日IP數據/'), '', $location[$k]); $location[$k] = htmlspecialchars($location[$k]); } return $location; } // 更新數據庫 https://www.22vd.com/40035.html public function update() { $this->ignore_timeout(); $copywrite = file_get_contents('http://update.cz88.net/ip/copywrite.rar'); $qqwry = file_get_contents('http://update.cz88.net/ip/qqwry.rar'); $key = unpack('V6', $copywrite)[6]; for($i = 0; $i < 0x200; $i++) { $key *= 0x805; $key ++; $key = $key & 0xFF; $qqwry[$i] = chr(ord($qqwry[$i]) ^ $key); } $qqwry = gzuncompress($qqwry); file_put_contents($this->dbFile, $qqwry); } // 析構函數 function __destruct() { if($this->fh) { fclose($this->fh); } $this->fp = null; } }
使用方法
將上面的模塊代碼保存為 IPQuery.class.php
,然后按照如下方法調用即可:
<?php require_once('IPQuery.class.php'); $ip = new IPQuery(); $addr = $ip->query('123.233.233.233'); echo "<pre> IP起始段:{$addr['beginip']} IP結束段:{$addr['endip']} 實際地址:{$addr['pos']} 運 營 商:{$addr['isp']} </pre>";
輸出效果如下所示:
注意事項
本模塊會在第一次被調用時自動從純真網下載最新的 IP 數據庫到本地,因此第一次進行查詢時會有點慢。如果你的服務器因為某些原因,無法連接到純真網獲取數據庫,可以直接下載離線版,並將 IPQuery.class.php
第 25 行的 $dbExpires
值改為“0”(即永不自動更新數據庫)。
轉載:本文作者為mengkun
純真ip庫下載:https://files.cnblogs.com/files/yehuisir/qqwry.zip
https://files.cnblogs.com/files/yehuisir/qqwry_lastest.zip