珊瑚虫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入门速成(2)
Oct 09 PHP
十天学会php(1)
Oct 09 PHP
将数字格式的计算结果转为汉字格式
Oct 09 PHP
PHP中cookies使用指南
Mar 16 PHP
PHP4和PHP5性能测试和对比 测试代码与环境
Aug 17 PHP
php 需要掌握的东西 不做浮躁的人
Dec 28 PHP
apache+php+mysql安装配置方法小结
Aug 01 PHP
PHP命名空间(Namespace)的使用详解
May 04 PHP
php生成随机字符串可指定纯数字、纯字母或者混合的
Apr 18 PHP
yii2中添加验证码的实现方法
Jan 09 PHP
详解php框架Yaf路由重写
Jun 20 PHP
基于PHP+Mysql简单实现了图书购物车系统的实例详解
Aug 06 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 checkbox复选框值的获取与checkbox默认值输出方法
2010/05/15 PHP
在PHP中使用反射技术的架构插件使用说明
2010/05/18 PHP
PHP执行Curl时报错提示CURL ERROR: Recv failure: Connection reset by peer的解决方法
2014/06/26 PHP
PHP多文件上传类实例
2015/03/07 PHP
一个javascript参数的小问题
2008/03/02 Javascript
JavaScript 函数式编程的原理
2009/10/16 Javascript
深入理解JavaScript 闭包究竟是什么
2013/04/12 Javascript
JavaScript通过字符串调用函数的实现方法
2015/03/18 Javascript
javascript原型模式用法实例详解
2015/06/04 Javascript
20分钟打造属于你的Bootstrap站点
2016/07/27 Javascript
AngularJs  Using $location详解及示例代码
2016/09/02 Javascript
jstree创建无限分级树的方法【基于ajax动态创建子节点】
2016/10/25 Javascript
JavaScript中常见的八个陷阱总结
2017/06/28 Javascript
AngularJS 实现购物车全选反选功能
2017/10/24 Javascript
React-intl 实现多语言的示例代码
2017/11/03 Javascript
vue.js 打包时出现空白页和路径错误问题及解决方法
2019/06/26 Javascript
layui关闭弹窗后刷新主页面和当前更改项的例子
2019/09/06 Javascript
如何实现一个简易版的vuex持久化工具
2019/09/11 Javascript
使用scrapy实现爬网站例子和实现网络爬虫(蜘蛛)的步骤
2014/01/23 Python
使用url_helper简化Python中Django框架的url配置教程
2015/05/30 Python
python 爬取微信文章
2016/01/30 Python
Python3.6实现连接mysql或mariadb的方法分析
2018/05/18 Python
python3 拼接字符串的7种方法
2018/09/12 Python
10分钟教你用Python实现微信自动回复功能
2018/11/28 Python
不到40行代码用Python实现一个简单的推荐系统
2019/05/10 Python
解决pycharm不能自动保存在远程linux中的问题
2021/02/06 Python
AmazeUI 图标的示例代码
2020/08/13 HTML / CSS
法国设计制造的扫帚和刷子:Andrée Jardin
2018/12/06 全球购物
Allen Edmonds官方网站:一家美国优质男士鞋类及配饰制造商
2019/03/12 全球购物
DBA数据库管理员JAVA程序员架构师必看
2016/02/07 面试题
天游软件面试
2013/11/23 面试题
售后服务承诺书怎么写
2014/05/21 职场文书
医学专业自荐信
2014/06/14 职场文书
关于读书的演讲稿300字
2014/08/27 职场文书
python pygame入门教程
2021/06/01 Python
分享几个简单MySQL优化小妙招
2022/03/31 MySQL