珊瑚虫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 相关文章推荐
set_include_path在win和linux下的区别
Jan 10 PHP
PHP中怎样防止SQL注入分析
Oct 23 PHP
php修改文件上传限制方法汇总
Apr 07 PHP
PHP 绘制网站登录首页图片验证码
Apr 12 PHP
php版阿里云OSS图片上传类详解
Dec 01 PHP
Yii2实现跨mysql数据库关联查询排序功能代码
Feb 10 PHP
PHP编程快速实现数组去重的方法详解
Jul 22 PHP
yii框架redis结合php实现秒杀效果(实例代码)
Oct 26 PHP
TP5框架实现一次选择多张图片并预览的方法示例
Apr 04 PHP
PHP 8新特性简介
Aug 18 PHP
50个优秀经典PHP算法大集合 附源码
Aug 26 PHP
Jsonp劫持学习
Apr 01 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
PHP实现的折半查找算法示例
2017/12/19 PHP
jQuery 源代码显示控件 (Ajax加载方式).
2009/05/18 Javascript
jQuery 操作option的实现代码
2011/03/03 Javascript
jQuery判断对象是否存在的方法
2015/02/05 Javascript
jquery.uploadify插件在chrome浏览器频繁崩溃解决方法
2015/03/01 Javascript
javascript与jquery中的this关键字用法实例分析
2015/12/24 Javascript
AngularJS  自定义指令详解及实例代码
2016/09/14 Javascript
AngularJS 获取ng-repeat动态生成的ng-model值实例详解
2016/11/29 Javascript
jQuery Ajax File Upload实例源码
2016/12/12 Javascript
EasyUI修改DateBox和DateTimeBox的默认日期格式示例
2017/01/18 Javascript
BootStrap table删除指定行的注意事项(笔记整理)
2017/02/05 Javascript
JS组件系列之MVVM组件构建自己的Vue组件
2017/04/28 Javascript
chosen实现省市区三级联动
2018/08/16 Javascript
vue的style绑定background-image的方式和其他变量数据的区别详解
2018/09/03 Javascript
puppeteer库入门初探
2019/01/09 Javascript
vue实现购物车抛物线小球动画效果的方法详解
2019/02/13 Javascript
vue请求本地自己编写的json文件的方法
2019/04/25 Javascript
解决vue项目本地启动时无法携带cookie的问题
2021/02/06 Vue.js
python实现dict版图遍历示例
2014/02/19 Python
Python使用pylab库实现绘制直方图功能示例
2018/06/01 Python
selenium3+python3环境搭建教程图解
2018/12/07 Python
python中的反斜杠问题深入讲解
2019/08/12 Python
python GUI库图形界面开发之PyQt5单选按钮控件QRadioButton详细使用方法与实例
2020/02/28 Python
一篇文章搞懂python的转义字符及用法
2020/09/03 Python
Django模型验证器介绍与源码分析
2020/09/08 Python
手机端用rem+scss做适配的详解
2017/11/15 HTML / CSS
新加坡最受追捧的体验平台:Hapz
2018/01/01 全球购物
expedia比利时:预订航班+酒店并省钱
2018/07/13 全球购物
初中校园之声广播稿
2014/01/15 职场文书
公司端午节活动方案
2014/02/04 职场文书
三月学雷锋月活动总结
2014/04/28 职场文书
小学教育见习报告
2014/10/31 职场文书
2015年学生会个人工作总结
2015/04/09 职场文书
集结号观后感
2015/06/08 职场文书
MySQL如何构建数据表索引
2021/05/13 MySQL
深入理解python协程
2021/06/15 Python