珊瑚虫IP库浅析


Posted in PHP onFebruary 15, 2007

这不是什么新鲜事情了,很早之前就已经有人做出来了。
就是使用PHP操作纯真IP库或珊瑚虫IP库,根据来访者的IP得到所在的物理位置。

我先帖出代码。然后再慢慢一步步浅析出来。希望对想了解这一块的朋友们有帮助。

Only For PHP5的代码。会继续优化代码的。

class IpLocation{
    private $fp;
    private $wrydat;
    private $wrydat_version;
    private $ipnumber;
    private $firstip;
    private $lastip;
    private $ip_range_begin;
    private $ip_range_end;
    private $country;
    private $area;
    const REDIRECT_MODE_0 = 0;
    const REDIRECT_MODE_1 = 1;
    const REDIRECT_MODE_2 = 2;
    function __construct(){
        $args = func_get_args();
        $this->wrydat = func_num_args()>0?$args[0]:'CoralWry.dat';
        $this->initialize();
    }
    function __destruct(){
        fclose($this->fp);
    }
    private function initialize(){
        if(file_exists($this->wrydat))
            $this->fp = fopen($this->wrydat,'rb');
        $this->getipnumber();
        $this->getwryversion();
    }
    public function get($str){
        return $this->$str;
    }
    public function set($str,$val){
        $this->$str = $val;
    }
    private function getbyte($length,$offset=null){
        if(!is_null($offset)){
            fseek($this->fp,$offset,SEEK_SET);
        }
        $b = fread($this->fp,$length);
        return $b;
    }
/**
* 把IP地址打包成二进制数据,以big endian(高位在前)格式打包
* 数据存储格式为 little endian(低位在前) 如:
* 00 28 C6 DA    218.198.40.0    little endian
* 3F 28 C6 DA    218.198.40.0    little endian
* 这样的数据无法作二分搜索查找的比较,所以必须先把获得的IP数据使用strrev转换为big endian
* @param $ip
* @return big endian格式的二进制数据
*/
    private function packip($ip){
        return pack( "N", intval( ip2long( $ip)));
    }

    private function getlong($length=4, $offset=null){
        $chr=null;
        for($c=0;$length%4!=0&&$c<(4-$length%4);$c++){
            $chr .= chr(0);
        }
        $var = unpack( "Vlong", $this->getbyte($length, $offset).$chr);
        return $var['long'];
    }

    private function getwryversion(){
        $length = preg_match("/coral/i",$this->wrydat)?26:30;
        $this->wrydat_version = $this->getbyte($length, $this->firstip-$length);
    }

    private function getipnumber(){
        $this->firstip = $this->getlong();
        $this->lastip = $this->getlong();
        $this->ipnumber = ($this->lastip-$this->firstip)/7+1;
    }

    private function getstring($data="",$offset=null){
        $char = $this->getbyte(1,$offset);
        while(ord($char) > 0){
            $data .= $char;
            $char = $this->getbyte(1);
        }
        return $data;
    }

    private function iplocaltion($ip){
        $ip = $this->packip($ip);
        $low = 0;
        $high = $this->ipnumber-1;
        $ipposition = $this->lastip;
        while($low <= $high){
            $t = floor(($low+$high)/2);
            if($ip < strrev($this->getbyte(4,$this->firstip+$t*7))){
                $high = $t - 1;
            } else {
                if($ip > strrev($this->getbyte(4,$this->getlong(3)))){
                    $low = $t + 1;
                }else{
                    $ipposition = $this->firstip+$t*7;
                    break;
                }
            }
        }
        return $ipposition;
    }
    private function getarea(){
        $b = $this->getbyte(1);
        switch(ord($b)){
            case self::REDIRECT_MODE_0 :
                return "未知";
                break;
            case self::REDIRECT_MODE_1:
            case self::REDIRECT_MODE_2:
                return $this->getstring("",$this->getlong(3));
                break;
            default:
                return $this->getstring($b);
                break;
        }
    }
    public function getiplocation($ip){
        $ippos = $this->iplocaltion($ip);
        $this->ip_range_begin = long2ip($this->getlong(4,$ippos));
        $this->ip_range_end = long2ip($this->getlong(4,$this->getlong(3)));
        $b = $this->getbyte(1);
        switch (ord($b)){
            case self::REDIRECT_MODE_1:
                $b = $this->getbyte(1,$this->getlong(3));
                if(ord($b) == REDIRECT_MODE_2){
                    $countryoffset = $this->getlong(3);
                    $this->area = $this->getarea();
                    $this->country = $this->getstring("",$countryoffset);
                }else{
                    $this->country = $this->getstring($b);
                    $this->area    = $this->getarea();
                }
                break;

            case self::REDIRECT_MODE_2:
                    $countryoffset = $this->getlong(3);
                    $this->area = $this->getarea();
                    $this->country = $this->getstring("",$countryoffset);
                break;

            default:
                $this->country = $this->getstring($b);
                $this->area    = $this->getarea();
                break;
        }
    }
}
/* */
echo microtime();
echo "\n";
$iploca = new IpLocation;
//$iploca = new IpLocation('QQWry.dat');
echo $iploca->get('wrydat_version');
echo "\n";
echo $iploca->get('ipnumber');
echo "\n";
$iploca->getiplocation('211.44.32.34');
/**/
echo $iploca->get('ip_range_begin');
echo "\n";
echo $iploca->get('ip_range_end');
echo "\n";
echo $iploca->get('country');
echo "\n";
echo $iploca->get('area');

echo "\n";
echo $iploca->get('lastip');
echo "\n";
echo microtime();
echo "\n";
unset($iploca);

参考资料:LumaQQ的 纯真IP数据库格式详解

CoralWry.dat文件结构上分为3个区域:

  • 文件头[固定8个字节]
  • 数据区[不固定长度,记录IP的地址信息]
  • 索引区[大小由文件头决定]

该文件数据的存储方式是:little endian。
在这里引用了谈谈Unicode编码里的关于little endian 与 big endian的区别

引用

big endian和little endian是CPU处理多字节数的不同方式。例如“汉”字的Unicode编码是6C49。那么写到文件里时,究竟是将6C写在前面,还是将49写在前面?如果将6C写在前面,就是big endian。还是将49写在前面,就是little endian。

“endian”这个词出自《格列佛游记》。小人国的内战就源于吃鸡蛋时是究竟从大头(Big-Endian)敲开还是从小头(Little-Endian)敲开,由此曾发生过六次叛乱,其中一个皇帝送了命,另一个丢了王位。

我们一般将endian翻译成“字节序”,将big endian和little endian称作“大尾”和“小尾”。

文件头:
红色框框里的就是文件头,前4个字节是索引区的开始地址,后4个字节是索引区的结束地址。

如下图所示:

珊瑚虫IP库浅析
点击放大

由于数据库是使用了little endian的字节库,所以我们需要把它倒过来。
把文件头的0-3的字节读取出来,再使用 unpack 函数把二进制数据转换为big endian格式的无符号整型。
处理后,索引区的开始地址位置是:00077450 ;索引区的结束地址位置是:000CE17C。
如果你手头上有UltraEdit的软件,可以打开CoralWry.dat文件,查找地址为:00077450 的位置,那就是IP地址索引区的开始。
如下图所示:

珊瑚虫IP库浅析
点击放大

红色框框住那就是索引区的开始位置。

PHP 相关文章推荐
php smarty模版引擎中变量操作符及使用方法
Dec 11 PHP
PHP Array交叉表实现代码
Aug 05 PHP
Drupal7连接多个数据库及常见问题解决
Mar 02 PHP
PHP之uniqid()函数用法
Nov 03 PHP
php无序树实现方法
Jul 28 PHP
php处理复杂xml数据示例
Jul 11 PHP
php usort 使用用户自定义的比较函数对二维数组中的值进行排序
May 02 PHP
PHP封装的PDO数据库操作类实例
Jun 21 PHP
浅谈PHP发送HTTP请求的几种方式
Jul 25 PHP
Yii1.1框架实现PHP极光推送消息通知功能
Sep 06 PHP
yii2的restful api路由实例详解
May 14 PHP
php 根据URL下载远程图片、压缩包、pdf等文件到本地
Jul 26 PHP
PHP中HTTP方式下的Gzip压缩传输方法举偶
Feb 15 #PHP
PHP+.htaccess实现全站静态HTML文件GZIP压缩传输(一)
Feb 15 #PHP
php调用mysql存储过程
Feb 14 #PHP
mysql中存储过程、函数的一些问题
Feb 14 #PHP
让PHP支持页面回退的两种方法[转]
Feb 14 #PHP
浅析PHP水印技术
Feb 14 #PHP
解决GD中文乱码问题
Feb 14 #PHP
You might like
Notice: Trying to get property of non-object problem(PHP)解决办法
2012/03/11 PHP
采用header定义为文件然后readfile下载(隐藏下载地址)
2014/01/31 PHP
IE6下focus与blur错乱的解决方案
2011/07/31 Javascript
Prototype源码浅析 Enumerable部分之each方法
2012/01/16 Javascript
js传中文参数controller里获取参数乱码问题解决方法
2014/01/03 Javascript
原生js实现类似弹窗抖动效果
2015/04/02 Javascript
JS实现的论坛Ajax打分效果完整实例
2015/10/31 Javascript
微信小程序 限制1M的瘦身技巧与方法详解
2017/01/06 Javascript
微信小程序之拖拽排序(代码分享)
2017/01/21 Javascript
JavaScript设计模式之装饰者模式定义与应用示例
2018/07/25 Javascript
vue中使用codemirror的实例详解
2018/11/01 Javascript
详解element-ui设置下拉选择切换必填和非必填
2019/06/17 Javascript
原生js实现随机点餐效果
2019/12/10 Javascript
Python中optionParser模块的使用方法实例教程
2014/08/29 Python
举例详解Python中循环语句的嵌套使用
2015/05/14 Python
python之Character string(实例讲解)
2017/09/25 Python
深入分析python数据挖掘 Json结构分析
2018/04/21 Python
python 返回列表中某个值的索引方法
2018/11/07 Python
python爬虫刷访问量 2019 7月
2019/08/01 Python
python+selenium select下拉选择框定位处理方法
2019/08/24 Python
Python 3.8 新功能大揭秘【新手必学】
2020/02/05 Python
Python 安装 virturalenv 虚拟环境的教程详解
2020/02/21 Python
python实现测试工具(一)——命令行发送get请求
2020/10/19 Python
Python学习工具jupyter notebook安装及用法解析
2020/10/23 Python
OpenCV+Python3.5 简易手势识别的实现
2020/12/21 Python
浅谈HTML5 Web Worker的使用
2018/01/05 HTML / CSS
Hoka One One法国官网:美国专业跑鞋品牌
2018/12/29 全球购物
Ray-Ban雷朋瑞典官方网站:全球领先的太阳眼镜品牌
2019/08/22 全球购物
老教师工作总结的自我评价
2013/09/27 职场文书
优秀少先队辅导员事迹材料
2014/12/24 职场文书
公司辞职信模板
2015/05/13 职场文书
尼克胡哲观后感
2015/06/08 职场文书
运动会通讯稿300字
2015/07/20 职场文书
2016年“12.4”法制宣传日活动总结
2016/04/01 职场文书
Python并发编程实例教程之线程的玩法
2021/06/20 Python
CentOS MySql8 远程连接实战
2022/04/19 MySQL