之前介绍过很多第三方的 IP 地址查询 API 接口,详见: 分享几个IP获取地理位置的API接口,直接调用第三方的接口很方便,但也容易失效导致无法使用。因此今天来分享一个基于本地数据库的 IP 地址查询源码!
模块代码
PHP
- <?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] = 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
- <?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,转载请注明。
不错不错,谢谢大佬
这个其实还是挺实用了,先收藏着
我测试报
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
这么多错
@丷没问题的,不存在问题
感谢分享!
大神,向你求一套影视源码哦,就是那个VIP影院的(movie.lingtings.com),我在里面找来找去还是找到了你,盼回复,感谢!
@telnet该代码已绝版。不出售也不继续开发
转了哈哈~~
感谢分享
一般我查ip之类的都去 ipip.net
你好,我想问一个问题:利用这个查询ip的php代码。我怎么才能在织梦或者别的php程序里的留言功能时,通过后台留言管理里用户的ip,显示出用户所在地址。
谢谢分享
Mark
天——
Can U speak Chinese.....
Mark
谢谢分享
你博客是买哪里的主机
好歹是搞c++的,怎么没有 来点c++代码
@阿珏呐,这些都是——
https://mkblog.cn/182/
https://mkblog.cn/189/
https://mkblog.cn/192/
https://mkblog.cn/195/
陈年佳酿,哈哈哈哈……
https://www.wuzuowei.net/16078.html
QQ解封教程,不知道有用没,大佬试试
@额这个方法当天就凉了
呃呃呃,大佬能不能搞个PHP加解密教程工具
@额PHP 解密我也不会呀
正好需要,感谢
居然是3周前更新的,我是多久没上网了
厉害了
nb了
我只能说牛逼~
更新了,快来看看新文章
大佬终于等到你
可以可以
厉害竟然更新了
老哥,啥时候更新一个校园表白墙程序?
@九七博客github 上一大堆……
@mengkun太丑了
@九七博客表白墙
天哪,你总算更新了,太难得了。
大佬难得更新~
难得更新
大佬居然更了