PHP解析纯真IP数据库查询IP所在地
在Web开发中,经常需要根据用户IP判断其所在省份或城市。本文将演示如何利用PHP读取本地化的纯真IP数据库(qqwry.dat),通过高效的二分查找算法解析IP记录,获取详细的地理位置信息。
原理与准备条件
纯真IP数据库(通常命名为 qqwry.dat)采用特定的二进制结构存储IP段与地区对应关系。解析该文件需理解以下数据格式:
- 头区域:包含第一条索引偏移量和最后一条索引偏移量,各占4字节。
- 索引区:定长7字节,包含起始IP(4字节)、结束IP(3字节,实际通过重定向指针关联)和指向记录区的偏移量。
- 记录区:变长,包含结束IP(4字节)和具体的地区字符串。
准备工作:你需要下载最新的纯真IP库文件,并确保文件名符合预期(默认为 qqwry.dat)。将文件放置于脚本同级目录或指定路径。
核心实现步骤
第1步:初始化IP库与文件句柄
目的:打开二进制文件,读取头部信息以确定索引范围。
复制代码 代码如下:
<?php
class iplocation {
var $fp; // 文件资源句柄
var $firstip; // 第一条ip索引的偏移地址
var $lastip; // 最后一条ip索引的偏移地址
var $totalip; // 总ip数
function __construct($datfile = "qqwry.dat") {
$this->fp = fopen($datfile, 'rb') or die("qqwry.dat不存在");
$this->firstip = $this->get4b(); // 读取第一条索引偏移
$this->lastip = $this->get4b(); // 读取最后一条索引偏移
// 计算总IP数:索引区每条记录占7字节
$this->totalip = ($this->lastip - $this->firstip) / 7;
}
}第2步:实现二进制数据读取工具方法
目的:提供读取固定长度字节、解析重定向偏移量及字符串的方法。
复制代码 代码如下:
// 读取4个字节并转为Long整型(小端序)
function get4b() {
$str = unpack("v", fread($this->fp, 4));
return $str[1];
}
// 读取重定向了的偏移地址(3字节+填充)
function getoffset() {
$str = unpack("v", fread($this->fp, 3).chr(0));
return $str[1];
}
// 读取以0结尾的地区字符串
function getstr() {
$split = fread($this->fp, 1);
while (ord($split) != 0) {
$str .= $split;
$split = fread($this->fp, 1);
}
return $str;
}
// 将IP地址转换为网络字节序(Big-Endian)用于比较
function iptoint($ip) {
return pack("n", intval(ip2long($ip)));
}第3步:二分查找定位IP区间
目的:在索引区中快速定位目标IP所在的记录行。
复制代码 代码如下:
function searchip($ip) {
$ip = gethostbyname($ip); // 支持域名解析
$ip_offset["ip"] = $ip;
$ip = $this->iptoint($ip);
$firstip = 0;
$lastip = $this->totalip;
$ipoffset = $this->lastip;
while ($firstip <= $lastip) {
$i = floor(($firstip + $lastip) / 2);
fseek($this->fp, $this->firstip + $i * 7); // 定位到中间记录
$startip = strrev(fread($this->fp, 4)); // 读取起始IP
if ($ip < $startip) {
$lastip = $i - 1;
} else {
fseek($this->fp, $this->getoffset());
$endip = strrev(fread($this->fp, 4)); // 读取结束IP
if ($ip > $endip) {
$firstip = $i + 1;
} else {
$ip_offset["offset"] = $this->firstip + $i * 7;
break;
}
}
}
return $ip_offset;
}第4步:解析地址详情(处理重定向)
目的:根据索引找到的偏移量,读取具体的省、市等信息,并处理数据库中常见的地址重定向情况(标志位1或2)。
复制代码 代码如下:
function readaddress() {
$now_offset = ftell($this->fp);
$flag = $this->getflag();
switch (ord($flag)) {
case 0:
$address = "";
break;
case 1:
case 2:
fseek($this->fp, $this->getoffset());
$address = $this->getstr();
break;
default:
fseek($this->fp, $now_offset);
$address = $this->getstr();
break;
}
return $address;
}
function getflag() {
return fread($this->fp, 1);
}
function getaddress($ip) {
$ip_offset = $this->searchip($ip);
$ipoffset = $ip_offset["offset"];
$address["ip"] = $ip_offset["ip"];
fseek($this->fp, $ipoffset);
$address["startip"] = long2ip($this->get4b());
$address_offset = $this->getoffset();
fseek($this->fp, $address_offset);
$address["endip"] = long2ip($this->get4b());
$flag = $this->getflag();
switch (ord($flag)) {
case 1: // 地区1地区2都重定向
$address_offset = $this->getoffset();
fseek($this->fp, $address_offset);
$flag = $this->getflag();
switch (ord($flag)) {
case 2: // 地区1又一次重定向
fseek($this->fp, $this->getoffset());
$address["area1"] = $this->getstr();
fseek($this->fp, $address_offset + 4);
$address["area2"] = $this->readaddress();
break;
default:
fseek($this->fp, $address_offset);
$address["area1"] = $this->getstr();
$address["area2"] = $this->readaddress();
break;
}
break;
case 2: // 地区1重定向 地区2没有重定向
$address1_offset = $this->getoffset();
fseek($this->fp, $address1_offset);
$address["area1"] = $this->getstr();
fseek($this->fp, $address_offset + 8);
$address["area2"] = $this->readaddress();
break;
default: // 都没有重定向
fseek($this->fp, $address_offset + 4);
$address["area1"] = $this->getstr();
$address["area2"] = $this->readaddress();
break;
}
// 过滤无用数据
if (strpos($address["area1"], "cz88.net") !== false) {
$address["area1"] = "未知";
}
if (strpos($address["area2"], "cz88.net") !== false) {
$address["area2"] = " ";
}
return $address;
}
}调用示例与结果验证
创建实例后,直接调用 getaddress() 方法即可获取IP信息数组。
复制代码 代码如下:
$ip_obj = new iplocation("qqwry.dat");
$result = $ip_obj->getaddress("61.129.51.27");
echo '';
print_r($result);
输出结果通常包含:ip(查询的IP)、startip(该记录的起始IP段)、endip(该记录的结束IP段)、area1(省份/国家)和 area2(城市/ISP)。
注意事项
- 性能优化
- 编码问题:纯真IP库通常使用GB2312编码,若你的环境是UTF-8,可能需要对返回的地区字符串进行转码(如使用
iconv或mb_convert_encoding)。 - 库更新:定期更新
qqwry.dat以保证地理位置信息的准确性。
以上就是PHP利用纯真IP数据库查询IP所在地详细内容,更多关于PHP IP查询技术的资料请关注本站其它相关文章!
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









