个人博客2.0新版正式上线了,欢迎各位博主、个人网站、IT类等交换友情链接smilesmile

  • 文章详情

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

# 使用方法

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

  1. <?php
  2. require_once('IPQuery.class.php');

  3. $ip = new IPQuery();
  4. $addr = $ip -> query('123.233.233.233');
  5. echo "<pre>
  6. IP起始段:{$addr['beginip']}
  7. IP结束段:{$addr['endip']}
  8. 实际地址:{$addr['pos']}
  9. 运 营 商:{$addr['isp']}
  10. </pre>";

# 注意事项

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

# 离线版下载

https://mkblog.cn/go/?url=https://www.lanzous.com/i4vhiza

转载自:https://mkblog.cn/1951/

  • 评论列表

    当前文章没有评论

  • 发表评论
请登录后才能发表评论,点击 登录