查看: 6726|回复: 1
打印 上一主题 下一主题

利用 QQWry.Dat 实现 IP 地址高效检索

[复制链接]
跳转到指定楼层
1#
发表于 2007-9-27 21:48:02 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
台州网址导航
根据 LumaQQ 开发者文档中的纯真 IP 数据库格式详解,我编写了一个 PHP 的查询 IP 所在地区信息的类。 在编写过程中发现纯真 IP 数据库格式详解中关于记录区的描述不是很全面,不过出入也不是很大,所以我没必要再写一份纯真 IP 数据库的格式说明了,大家感兴趣的话,读一读下面的代码应该就能看出来了。代码中加了很详细的注释,应该很容易读懂的。

  在创建这个类的一个实例后,实例中就保存了打开的文件指针和一些查询需要的信息,每次查询时不需要重新打开文件,直到页面执行结束后,打开的文件才会自动关闭。这样。在一个页面内进行多次查询时,效率是很高的。并且此类不仅可以直接查询 IP,还可以自动将域名解析为 IP 进行查询。

  下面是程序代码:



<?php
/**
* @author 马秉尧
*/

class IpLocation {

/**
* QQWry.Dat文件指针
* @var resource
*/

var $fp;

/**
* 第一条IP记录的偏移地址
*
* @var int
*/

var $firstip;

/**
* 最后一条IP记录的偏移地址
*
* @var int
*/

var $lastip;

/**
* IP记录的总条数(不包含版本信息记录)
*
* @var int
*/

var $totalip;

/**
* 返回读取的长整型数
*
* @access private
* @return int
*/

function getlong() {

//将读取的little-endian编码的4个字节转化为长整型数

$result = unpack('Vlong', fread($this->fp, 4));

return $result['long'];

}

/**
* 返回读取的3个字节的长整型数
*
* @access private
* @return int
*/

function getlong3() {

//将读取的little-endian编码的3个字节转化为长整型数

$result = unpack('Vlong', fread($this->fp, 3).chr(0));

return $result['long'];

}

/**
* 返回压缩后可进行比较的IP地址
*
* @access private
* @param string $ip
* @return string
*/

function packip($ip) {

// 将IP地址转化为长整型数,如果在PHP5中,IP地址错误,则返回False,

// 这时intval将Flase转化为整数-1,之后压缩成big-endian编码的字符串

return pack('N', intval(ip2long($ip)));

}

/**
* 返回读取的字符串
*
* @access private
* @param string $data
* @return string
*/

function getstring($data = "") {

$char = fread($this->fp, 1);

while (ord($char) > 0) { // 字符串按照C格式保存,以\0结束

$data .= $char; // 将读取的字符连接到给定字符串之后

$char = fread($this->fp, 1);

}

return $data;

}

/**
* 返回地区信息
*
* @access private
* @return string
*/

function getarea() {

$byte = fread($this->fp, 1); // 标志字节

switch (ord($byte)) {

case 0: // 没有区域信息

$area = "";

break;

case 1:

case 2: // 标志字节为1或2,表示区域信息被重定向

fseek($this->fp, $this->getlong3());

$area = $this->getstring();

break;

default: // 否则,表示区域信息没有被重定向

$area = $this->getstring($byte);

break;

}

return $area;

}

/**
* 根据所给 IP 地址或域名返回所在地区信息
*
* @access public
* @param string $ip
* @return array
*/

function getlocation($ip) {

if (!$this->fp) return null; // 如果数据文件没有被正确打开,则直接返回空

$location['ip'] = gethostbyname($ip); // 将输入的域名转化为IP地址

$ip = $this->packip($location['ip']); // 将输入的IP地址转化为可比较的IP地址

// 不合法的IP地址会被转化为255.255.255.255

// 对分搜索

$l = 0; // 搜索的下边界

$u = $this->totalip; // 搜索的上边界

$findip = $this->lastip; // 如果没有找到就返回最后一条IP记录(QQWry.Dat的版本信息)

while ($l <= $u) { // 当上边界小于下边界时,查找失败

$i = floor(($l + $u) / 2); // 计算近似中间记录

fseek($this->fp, $this->firstip + $i * 7);

$beginip = strrev(fread($this->fp, 4)); // 获取中间记录的开始IP地址

// strrev函数在这里的作用是将little-endian的压缩IP地址转化为big-endian的格式

// 以便用于比较,后面相同。

if ($ip < $beginip) { // 用户的IP小于中间记录的开始IP地址时

$u = $i - 1; // 将搜索的上边界修改为中间记录减一

}

else {

fseek($this->fp, $this->getlong3());

$endip = strrev(fread($this->fp, 4)); // 获取中间记录的结束IP地址

if ($ip > $endip) { // 用户的IP大于中间记录的结束IP地址时

$l = $i + 1; // 将搜索的下边界修改为中间记录加一

}

else { // 用户的IP在中间记录的IP范围内时

$findip = $this->firstip + $i * 7;

break; // 则表示找到结果,退出循环

}

}

}



//获取查找到的IP地理位置信息

fseek($this->fp, $findip);

$location['beginip'] = long2ip($this->getlong()); // 用户IP所在范围的开始地址

$offset = $this->getlong3();

fseek($this->fp, $offset);

$location['endip'] = long2ip($this->getlong()); // 用户IP所在范围的结束地址

$byte = fread($this->fp, 1); // 标志字节

switch (ord($byte)) {

case 1: // 标志字节为1,表示国家和区域信息都被同时重定向

$countryOffset = $this->getlong3(); // 重定向地址

fseek($this->fp, $countryOffset);

$byte = fread($this->fp, 1); // 标志字节

switch (ord($byte)) {

case 2: // 标志字节为2,表示国家信息又被重定向

fseek($this->fp, $this->getlong3());

$location['country'] = $this->getstring();

fseek($this->fp, $countryOffset + 4);

$location['area'] = $this->getarea();

break;

default: // 否则,表示国家信息没有被重定向

$location['country'] = $this->getstring($byte);

$location['area'] = $this->getarea();

break;

}

break;

case 2: // 标志字节为2,表示国家信息被重定向

fseek($this->fp, $this->getlong3());

$location['country'] = $this->getstring();

fseek($this->fp, $offset + 8);

$location['area'] = $this->getarea();

break;

default: // 否则,表示国家信息没有被重定向

$location['country'] = $this->getstring($byte);

$location['area'] = $this->getarea();

break;

}

if ($location['country'] == " CZ88.NET") { // CZ88.NET表示没有有效信息

$location['country'] = "未知";

}

if ($location['area'] == " CZ88.NET") {

$location['area'] = "";

}

return $location;

}

/**
* 构造函数,打开 QQWry.Dat 文件并初始化类中的信息
*
* @param string $filename
* @return IpLocation
*/

function IpLocation($filename = "QQWry.Dat") {

if (($this->fp = @fopen($filename, 'rb')) !== false) {

$this->firstip = $this->getlong();

$this->lastip = $this->getlong();

$this->totalip = ($this->lastip - $this->firstip) / 7;

//注册析构函数,使其在程序执行结束时执行

register_shutdown_function(array(&$this, '_IpLocation'));

}

}

/**
* 析构函数,用于在页面执行结束后自动关闭打开的文件。
*
*/

function _IpLocation() {

fclose($this->fp);

}

}

?>
分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏 转播转播 分享分享 分享淘帖
台州维博网络(www.tzweb.com)专门运用PHP+MYSQL/ASP.NET+MSSQL技术开发网站门户平台系统等。
2#
 楼主| 发表于 2007-9-27 21:53:57 | 只看该作者
台州网址导航
QQwry.dat是为QQ软件查IP的一个IP地址查询数据库.
数据更新很快.很值得利用.因为自己不了解这个文件格式.就找了这么一篇文章.

但是没有任何相关格式资料。

我分析了这个文件的格式,目前如下结论:

格式如下:

A。文件头,共8字节
B。若干条记录的结束地址+国家和区域
C。按照从小到大排列的若干条起始地址+结束地址偏移,定长,7字节
D。所有的IP都是用4字节整数记录的,并且遵照Intel次序,高位在后,低位在前。
E。所有偏移量都是绝对偏移,就是从文件最开头计算。
F。除了文件头用了两个4字节偏移,其余偏移量都用3字节。
G。所有的偏移量也是低位在前,高位在后
H。采用了一些字符串压缩技术

1。文件头,共8字节
FirstStartIpOffset:4 第一个起始IP的绝对偏移
LastStartIpOffset:4 最后一个起始IP的绝对偏移

2。起始地址+结束地址偏移记录区
每条记录7字节,按照起始地址从小到大排列

StartIp:4 起始地址,整数形式的IP
EndIpOffset:3 结束地址绝对偏移

3。结束地址+国家+区域记录区

EndIP:4
国家+区域记录:不定长

4。国家+区域记录,有几种形式
4.1。
国家字符串,以 0x0 结束
区域字符串,以 0x0 结束

4.2。
Flag:1 标识取值: 0x1,后面没有Local记录
0x2,后面还有Local记录
sCountryOffset:3 实际的字符串要去这个偏移位置去找
LocalRec:不定长,可选 根据Flag取值而定。这个记录也类似Country,可能采用压缩

4.3 LocalRec结构一
flag:1 还不是十分了解这个flag含义,取值 0x1 or 0x2
sLocalOffset:3

4.4 LocalRec结构二
sLocal:不定长 普通的C风格字符串

注意:sCountryOffset指向的位置可能依然是4.2格式的,不知道为什么这样设计。


Flag取0x1时,sCountryOffset指向的位置可能是Flag为0x2,这时,LocalRec也在这里寻找。

现在不明白当记录Local的位置遇到0x2的标志意味着什么。

在qqwry.dat中,似乎存在一些错误。
个别的记录Local会被写为:
0x2,0x0,0x0,0x0
根据规则,应该到文件最开头去寻找,可是,文件最开头显然不是记录这些的。
<?php
$qqwry_root_path ="";
define('QQWRY' , $qqwry_root_path . 'QQwry.dat' );

function IpToInt($Ip) {
    $array=explode('.',$Ip);
    $Int=($array[0] * 256*256*256) + ($array[1]*256*256) + ($array[2]*256) + $array[3];
    return $Int;
}

function IntToIp($Int) {
    $b1=($Int & 0xff000000)>>24;
    if ($b1<0) $b1+=0x100;
    $b2=($Int & 0x00ff0000)>>16;
    if ($b2<0) $b2+=0x100;
    $b3=($Int & 0x0000ff00)>>8;
    if ($b3<0) $b3+=0x100;
    $b4= $Int & 0x000000ff;
    if ($b4<0) $b4+=0x100;
    $Ip=$b1.'.'.$b2.'.'.$b3.'.'.$b4;
    return $Ip;
}


class TQQwry
{
    var $StartIP = 0;
    var $EndIP   = 0;
    var $Country = '';
    var $Local   = '';

    var $CountryFlag = 0; // 标识 Country位置
                          // 0x01,随后3字节为Country偏移,没有Local
                          // 0x02,随后3字节为Country偏移,接着是Local
                          // 其他,Country,Local,Local有类似的压缩。可能多重引用。
    var $fp;

    var $FirstStartIp = 0;
    var $LastStartIp = 0;
    var $EndIpOff = 0;

    function getStartIp ( $RecNo ) {
        $offset = $this->FirstStartIp + $RecNo * 7;
        @fseek ( $this->fp , $offset , SEEK_SET );
        $buf = fread ( $this->fp , 7 );
        $this->EndIpOff = ord($buf[4]) + (ord($buf[5])*256) + (ord($buf[6])* 256*256);
        $this->StartIp = ord($buf[0]) + (ord($buf[1])*256) + (ord($buf[2])*256*256) + (ord($buf[3])*256*256*256);
        return $this->StartIp;
    }

    function getEndIp ( ) {
        @fseek ( $this->fp , $this->EndIpOff , SEEK_SET );
        $buf = fread ( $this->fp , 5 );
        $this->EndIp = ord($buf[0]) + (ord($buf[1])*256) + (ord($buf[2])*256*256) + (ord($buf[3])*256*256*256);
        $this->CountryFlag = ord ( $buf[4] );
        return $this->EndIp;
    }

    function getCountry ( ) {

        switch ( $this->CountryFlag ) {
            case 1:
            case 2:
                $this->Country = $this->getFlagStr ( $this->EndIpOff+4);
                //echo sprintf('EndIpOffset=(%x)',$this->EndIpOff );
                $this->Local = ( 1 == $this->CountryFlag )? '' : $this->getFlagStr ( $this->EndIpOff+8);
                break;
            default :
                $this->Country = $this->getFlagStr ($this->EndIpOff+4);
                $this->Local =   $this->getFlagStr ( ftell ( $this->fp ));

        }
    }


    function getFlagStr ( $offset )
    {

        $flag = 0;
        while ( 1 ){
            @fseek ( $this->fp , $offset , SEEK_SET );
            $flag = ord ( fgetc ( $this->fp ) );
            if ( $flag == 1 || $flag == 2 ) {
                $buf = fread ($this->fp , 3 );
                if ($flag == 2 ){
                    $this->CountryFlag = 2;
                    $this->EndIpOff = $offset - 4;
                }
                $offset = ord($buf[0]) + (ord($buf[1])*256) + (ord($buf[2])* 256*256);
            }else{
                break;
            }

        }
        if ( $offset < 12 )
            return '';
        @fseek($this->fp , $offset , SEEK_SET );
        return $this->getStr();
    }
    function getStr ( )
    {
        $str = '';
        while ( 1 ) {
            $c = fgetc ( $this->fp );
            if ( ord ( $c[0] ) == 0  )
               break;
            $str .= $c;
        }
        return $str;
    }


    function qqwry ($dotip) {

        $nRet="";
        $ip = IpToInt ( $dotip );

        $this->fp= @fopen(QQWRY, "rb");
        if ($this->fp == NULL) {
              $szLocal= "OpenFileError";
            return 1;

          }
          @fseek ( $this->fp , 0 , SEEK_SET );
        $buf = fread ( $this->fp , 8 );
        $this->FirstStartIp = ord($buf[0]) + (ord($buf[1])*256) + (ord($buf[2])*256*256) + (ord($buf[3])*256*256*256);
        $this->LastStartIp  = ord($buf[4]) + (ord($buf[5])*256) + (ord($buf[6])*256*256) + (ord($buf[7])*256*256*256);

        $RecordCount= floor( ( $this->LastStartIp - $this->FirstStartIp ) / 7);
        if ($RecordCount <= 1){
            $this->Country = "FileDataError";
            fclose ( $this->fp );
            return 2;
        }

          $RangB= 0;
        $RangE= $RecordCount;
        // Match ...
        while ($RangB < $RangE-1)
        {
          $RecNo= floor(($RangB + $RangE) / 2);
          $this->getStartIp ( $RecNo );

            if ( $ip == $this->StartIp )
            {
                $RangB = $RecNo;
                break;
            }
          if ( $ip > $this->StartIp)
            $RangB= $RecNo;
          else
            $RangE= $RecNo;
        }
        $this->getStartIp ( $RangB );
        $this->getEndIp ( );

        if ( ( $this->StartIp  <= $ip ) && ( $this->EndIp >= $ip ) ){
            $nRet = 0;
            $this->getCountry ( );
            //这样不太好..............所以..........
            $this->Local = str_replace("(我们一定要解放台湾!!!)", "", $this->Local);
   $this->Local = str_replace("CZ88.NET", "", $this->Local);

        }else {
            $nRet = 3;
            $this->Country = '未知';
            $this->Local = '';
   $this->Local = str_replace("CZ88.NET", "", $this->Local);
        }
        fclose ( $this->fp );
        return $nRet;
    }
}


function ip2location ( $ip )
{
    $wry = new TQQwry;
    $nRet = $wry->qqwry ( $ip );
    //可以利用 $nRet做一些事情,我是让他自动记录未知IP到一个表,代码就不写了。
    return $wry->Country.$wry->Local;
}

echo ip2location ("127.0.0.1");
?>
台州维博网络(www.tzweb.com)专门运用PHP+MYSQL/ASP.NET+MSSQL技术开发网站门户平台系统等。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

网站推广
关于我们
  • 台州朗动科技(Tzweb.com)拥有多年开发网站平台系统门户手机客户端等业务的成功经验。主要从事:政企网站,系统平台,微信公众号,各类小程序,手机APP客户端,浙里办微应用,浙政钉微应用、主机域名、虚拟空间、后期维护等服务,满足不同企业公司的需求,是台州地区领先的网络技术服务商!

Hi,扫描关注我

Copyright © 2005-2026 站长论坛 All rights reserved

Powered by 站长论坛 with TZWEB Update Techonolgy Support

快速回复 返回顶部 返回列表