`
conkeyn
  • 浏览: 1523093 次
  • 性别: Icon_minigender_1
  • 来自: 厦门
社区版块
存档分类
最新评论

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

    博客分类:
  • PHP
阅读更多

http://blog.csdn.net/clin003/archive/2007/08/14/1743157.aspx

 

利用 QQWry.Dat 实现 IP 地址高效检索(PHP)
根据 LumaQQ 开发者文档中的纯真 IP 数据库格式详解,我编写了一个 PHP 的查询 IP 所在地区信息的类。在编写过程中发现纯真 IP 数据库格式详解中关于记录区的描述不是很全面,不过出入也不是很大,所以我没必要再写一份纯真 IP 数据库的格式说明了,大家感兴趣的话,读一读下面的代码应该就能看出来了。代码中加了很详细的注释,应该很容易读懂的。

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

下面是程序代码:

 

<?php
/**
 * IP 地理位置查询类
 *
 * @author 马秉尧
 * @version 1.5
 * @copyright 2005 CoolCode.CN
 */
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格式保存,以�结束
            $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") {
        $this->fp = 0;
        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() {
        if ($this->fp) {
            fclose($this->fp);
        }
        $this->fp = 0;
    }
}
?>

 

Discuz  5.0 不在使用自己的IP数据,而是使用纯真IP的数据格式, 存取纯真IP数据库稍微有点麻烦,它的存储格式比较特殊也很有趣,具体的格式分析参考下面两个链接,其他语言实现参考文章末的链接。

《纯真IP数据库格式详解》
链接一:http://blog.csdn.Net/heiyeshuwu/archive/2006/05/12/725675.aspx
链接二:http://lumaqq.Linuxsir.org/article/qqwry_format_detail.html

纯真IP数据库官网:http://www.cz88.Net/ip/
纯真IP数据库下载:http://update.cz88.Net/soft/qqwry.rar

 

以下函数conrvertip()位于 Discuz!5_GBK/upload/include/misc.func.Php 路径中,有兴趣可以具体去阅读分析。(下面代码我做了简单的修改,更便于阅读,核心没有修改)


<?
//===================================
//
// 功能:IP地址获取真实地址函数
// 参数:$ip - IP地址
// 作者:[Discuz!] (C) Comsenz Inc.
//
//===================================
function convertip($ip) {
    //IP数据文件路径
    $dat_path = 'QQWry.Dat';

    //检查IP地址
    if(!preg_match("/^d{1,3}.d{1,3}.d{1,3}.d{1,3}$/", $ip)) {
        return 'IP Address Error';
    }
    //打开IP数据文件
    if(!$fd = @fopen($dat_path, 'rb')){
        return 'IP date file not exists or access denied';
    }

    //分解IP进行运算,得出整形数
    $ip = explode('.', $ip);
    $ipNum = $ip[0] * 16777216 + $ip[1] * 65536 + $ip[2] * 256 + $ip[3];

    //获取IP数据索引开始和结束位置
    $DataBegin = fread($fd, 4);
    $DataEnd = fread($fd, 4);
    $ipbegin = implode('', unpack('L', $DataBegin));
    if($ipbegin < 0) $ipbegin += pow(2, 32);
    $ipend = implode('', unpack('L', $DataEnd));
    if($ipend < 0) $ipend += pow(2, 32);
    $ipAllNum = ($ipend - $ipbegin) / 7 + 1;
   
    $BeginNum = 0;
    $EndNum = $ipAllNum;

    //使用二分查找法从索引记录中搜索匹配的IP记录
    while($ip1num>$ipNum || $ip2num<$ipNum) {
        $Middle= intval(($EndNum + $BeginNum) / 2);

        //偏移指针到索引位置读取4个字节
        fseek($fd, $ipbegin + 7 * $Middle);
        $ipData1 = fread($fd, 4);
        if(strlen($ipData1) < 4) {
            fclose($fd);
            return 'System Error';
        }
        //提取出来的数据转换成长整形,如果数据是负数则加上2的32次幂
        $ip1num = implode('', unpack('L', $ipData1));
        if($ip1num < 0) $ip1num += pow(2, 32);
       
        //提取的长整型数大于我们IP地址则修改结束位置进行下一次循环
        if($ip1num > $ipNum) {
            $EndNum = $Middle;
            continue;
        }
       
        //取完上一个索引后取下一个索引
        $DataSeek = fread($fd, 3);
        if(strlen($DataSeek) < 3) {
            fclose($fd);
            return 'System Error';
        }
        $DataSeek = implode('', unpack('L', $DataSeek.chr(0)));
        fseek($fd, $DataSeek);
        $ipData2 = fread($fd, 4);
        if(strlen($ipData2) < 4) {
            fclose($fd);
            return 'System Error';
        }
        $ip2num = implode('', unpack('L', $ipData2));
        if($ip2num < 0) $ip2num += pow(2, 32);

        //没找到提示未知
        if($ip2num < $ipNum) {
            if($Middle == $BeginNum) {
                fclose($fd);
                return 'Unknown';
            }
            $BeginNum = $Middle;
        }
    }

    //下面的代码读晕了,没读明白,有兴趣的慢慢读
    $ipFlag = fread($fd, 1);
    if($ipFlag == chr(1)) {
        $ipSeek = fread($fd, 3);
        if(strlen($ipSeek) < 3) {
            fclose($fd);
            return 'System Error';
        }
        $ipSeek = implode('', unpack('L', $ipSeek.chr(0)));
        fseek($fd, $ipSeek);
        $ipFlag = fread($fd, 1);
    }

    if($ipFlag == chr(2)) {
        $AddrSeek = fread($fd, 3);
        if(strlen($AddrSeek) < 3) {
            fclose($fd);
            return 'System Error';
        }
        $ipFlag = fread($fd, 1);
        if($ipFlag == chr(2)) {
            $AddrSeek2 = fread($fd, 3);
            if(strlen($AddrSeek2) < 3) {
                fclose($fd);
                return 'System Error';
            }
            $AddrSeek2 = implode('', unpack('L', $AddrSeek2.chr(0)));
            fseek($fd, $AddrSeek2);
        } else {
            fseek($fd, -1, SEEK_CUR);
        }

        while(($char = fread($fd, 1)) != chr(0))
            $ipAddr2 .= $char;

        $AddrSeek = implode('', unpack('L', $AddrSeek.chr(0)));
        fseek($fd, $AddrSeek);

        while(($char = fread($fd, 1)) != chr(0))
            $ipAddr1 .= $char;
    } else {
        fseek($fd, -1, SEEK_CUR);
        while(($char = fread($fd, 1)) != chr(0))
            $ipAddr1 .= $char;

        $ipFlag = fread($fd, 1);
        if($ipFlag == chr(2)) {
            $AddrSeek2 = fread($fd, 3);
            if(strlen($AddrSeek2) < 3) {
                fclose($fd);
                return 'System Error';
            }
            $AddrSeek2 = implode('', unpack('L', $AddrSeek2.chr(0)));
            fseek($fd, $AddrSeek2);
        } else {
            fseek($fd, -1, SEEK_CUR);
        }
        while(($char = fread($fd, 1)) != chr(0)){
            $ipAddr2 .= $char;
        }
    }
    fclose($fd);

    //最后做相应的替换操作后返回结果
    if(preg_match('/http/i', $ipAddr2)) {
        $ipAddr2 = '';
    }
    $ipaddr = "$ipAddr1 $ipAddr2";
    $ipaddr = preg_replace('/CZ88.Net/is', '', $ipaddr);
    $ipaddr = preg_replace('/^s*/is', '', $ipaddr);
    $ipaddr = preg_replace('/s*$/is', '', $ipaddr);
    if(preg_match('/http/i', $ipaddr) || $ipaddr == '') {
        $ipaddr = 'Unknown';
    }

    return $ipaddr;
}


//========================
//
//  调用举例(速度很快)
//
//========================

echo convertip('219.238.235.10');
//输出: 北京市 电信通

echo convertip('23.56.82.12');
//输出:IANA

echo convertip('250.69.52.0');
//输出:IANA保留地址

echo convertip('238.69.52.0');
//输出:IANA保留地址 用于多点传送

echo convertip('192.168.0.1');
//输出:局域网 对方和您在同一内部网

echo convertip('255.255.255.255');
//输出:纯真网络 2006年11月20日IP数据

?>
 

附:(相应其他实现程序)

Php)
"  href= " http : // www.coolcode.cn/?p=16" rel=bookmark>利用 QQWry.Dat 实现 IP 地址高效检索(Php)(作者: andot)

数据库(QQWry
. Dat)查询 C源码 "  href= " http : // www.douzi.org/wp/index.Php/articles/71" rel=bookmark>纯真IP数据库(QQWry.Dat)查询 C源码 (作者:Windix)

 

分享到:
评论

相关推荐

    PHP调用QQWry.Dat 实现IP归属地查询

    这可以通过二分查找算法(Binary Search)来实现,因为QQWry.Dat文件中的IP地址通常是按照升序排列的。二分查找可以显著提高查询效率,尤其在处理大量数据时。 ```php function binarySearch($data, $ip) { $low =...

    通过qqwry.dat解析IP地址的动态库源代码

    qqwry.dat是由QQ公司提供的一种IP地址数据库,包含了全球大部分IP地址与其对应国家、地区的映射关系。这个数据库文件小巧高效,是快速查找IP地址地理信息的常用工具。其工作原理是通过二进制格式存储IP段和地理位置...

    qqwry.dat纯真ip数据库和自动更新脚本

    qqwry.dat qqwry 纯真IP数据库文件,dat数据非exe安装包,每天会扫描两次(17:55和23:55),当发现更新时也可以选择手动运行来获得文件。 目录说明 1、qqwry.dat 为最新数据直链(即下即用),每次更新会覆盖之前的...

    java基于QQWry.Dat实现IP位置定位,项目可直接运行

    本项目基于Java编程语言,利用QQWry.Dat数据库文件实现了IP到地理位置的映射,使得开发者可以轻松地在自己的应用中集成这一功能。QQWry.Dat是一个包含全球IP地址和对应地理位置信息的数据库,它被广泛用于快速查询IP...

    qqwry.dat, 纯真IP地址数据库镜像,mirror of qqwry.dat.zip

    qqwry.dat, 纯真IP地址数据库镜像,mirror of qqwry.dat.zip

    qqwry.dat 纯真ip数据库

    qqwry.dat 纯真ip数据库 附在线升级工具 下载下来直接安装即可,ip数据库(qqwry.dat)在安装所在目录下

    qqwry.dat 纯真20230920最新稳定版

    qqwry.dat纯真IP 2023年9月更新 2023年最新版 相对于上个版本最新更新超过了100000个IP信息

    PHP 根据IP地址获取所在城市代码以及纯真ip库qqwry.dat下载

    纯真IP库(qqwry.dat)是中国流行的IP地址数据库,包含了大量的IP段和对应的地理位置信息。这个数据库文件以特定的格式存储数据,便于快速查找。使用这种库可以高效地将IP地址映射到城市或其他地理位置,避免了每次...

    GeoIP.dat,qqhostinfo.pm、qqwry.pl、QQWry.dat3 打包下载

    4. **QQWry.dat3**:这是QingWry数据库的一个版本,包含了最新的IP地址和地理位置信息。与GeoIP.dat类似,它用于快速查找IP地址对应的地理位置,但可能更侧重于中国及亚洲地区的IP数据,提供更精确的国内地区信息。 ...

    QQWry.dat.zip

    QQWry.dat是一个知名的IP地址库,用于快速查询和定位全球IP地址的地理位置信息。这个压缩包文件"QQWry.dat.zip"包含了QQWry.dat这个数据库文件,它被广泛应用于各种软件和网络服务中,以帮助用户识别和追踪互联网上...

    java读取纯真IP数据库QQwry.dat的源代码--获取ip所在地区

    在Java编程中,读取纯真IP数据库QQwry.dat是为了实现快速地查询IP地址对应的城市和地区信息。QQwry.dat是一种广泛使用的IP地址库,它包含了大量的IP地址及其地理位置信息,通常用于网络日志分析、网站访问统计、地理...

    通过QQwry.dat来获的IP地址所在地

    总结,通过QQwry.dat获取IP地址所在地是一种常见且实用的方法,尤其在处理大量IP查询时,由于其高效的二进制查找机制,能显著提升性能。理解QQwry.dat的结构和查询流程,对于开发相关功能或者优化现有服务具有重要的...

    IP定位QQwry.dat文件

    QQwry.dat文件是与IP定位相关的数据文件,主要用于快速查询和识别IP地址对应的地理位置信息。这个文件在技术社区中被广泛分享,因为其便捷性和实用性。本文将深入探讨QQwry.dat的背景、工作原理以及如何利用它进行IP...

    java读取qqwry.dat实现IP地域查询

    Java读取qqwry.dat实现IP地域查询是网络编程中的一种常见需求,特别是在开发服务器端应用、游戏插件或者网络安全工具时。qqwry.dat是一个包含全球IP地址及其对应地理位置信息的数据文件,由著名的IP查询库——...

    NET.QQwry.dat.IP.design.code.rar_QQwry.dat_ip

    在实现.NET QQwry.dat IP地址设计的经典代码时,开发者可能会利用这些工具读取文件,逐个处理条目,并建立一个高效的查询结构,比如平衡二叉搜索树(AVL树)或者B树,以便于快速查找特定IP对应的国家/地区信息。...

    qqwry.zip 2019年IP地址库 7月更新qqwry.dat 纯真IP库 dat文件 QQ IP数据库 纯真版 20190705

    qqwry.dat 20190705更新的地址库 全球地址库 很全很新,纯真IP库 QQ IP数据库 纯真版收集了包括中国电信、中国移动、中国联通、长城宽带、聚友宽带等 ISP 的最新准确 IP 地址数据。包括 最全的网吧数据。希望能够通过...

    纯真ip库QQWry.dat

    QQWry.dat是由“纯真IP数据库”提供的IP地址解析文件,主要用于将IPv4地址转换为地理位置信息。这个数据库由热心网友维护,定期更新,包含了大量全球IP地址的对应信息,尤其是中国地区的IP数据。由于其准确性和实用...

    IP转换数据库QQWry.Dat

    QQWry.Dat是一个常用的IP地址数据库,它主要用于将IP地址转换为对应的地理位置信息,比如国家、地区等。这个数据库通常被各种网络应用和软件用来显示访客的来源地,或者进行网络管理与分析。在提供的压缩包中,...

    最新QQWRY.DAT文件 以及源码

    4. 定位IP地址的算法会遍历QQWRY.DAT文件,通过二分查找或其他高效的搜索策略来快速定位到指定IP地址所在的记录,然后返回对应的国家和地区信息。 5. 源码还会包含异常处理机制,以应对如文件不存在、读取错误或IP...

    如何借助qqwry.dat文件实现查询指定IP所属地的共能

    在IT领域,有时候我们...总之,借助qqwry.dat文件查询IP地址所属地是一种高效且实用的方法,但要正确实现这一功能,必须熟悉二进制文件操作、编码转换以及搜索算法。对于开发人员来说,这是一个提高技术能力的好实践。

Global site tag (gtag.js) - Google Analytics