珊瑚虫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 相关文章推荐
MySQL数据源表结构图示
Jun 05 PHP
phpmailer发送gmail邮件实例详解
Jun 24 PHP
纯php生成随机密码
Oct 30 PHP
PHP接收json 并将接收数据插入数据库的实现代码
Dec 01 PHP
PHP调用存储过程返回值不一致问题的解决方法分析
Apr 26 PHP
joomla实现注册用户添加新字段的方法
May 05 PHP
PHP上传Excel文件导入数据到MySQL数据库示例
Oct 25 PHP
php 读写json文件及修改json的方法
Mar 07 PHP
php 下 html5 XHR2 + FormData + File API 上传文件操作实例分析
Feb 28 PHP
Laravel框架源码解析之模型Model原理与用法解析
May 14 PHP
如何通过Apache在本地配置多个虚拟主机
Jul 29 PHP
PHP的重载使用魔术方法代码实例详解
Feb 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
支持php4、php5的mysql数据库操作类
2008/01/10 PHP
ajax+php打造进度条 readyState各状态
2010/03/20 PHP
php方法调用模式与函数调用模式简例
2011/09/20 PHP
php中的mongodb select常用操作代码示例
2014/09/06 PHP
php 将json格式数据转换成数组的方法
2018/08/21 PHP
Thinkphp 在api开发中异常返回依然是html的解决方式
2019/10/16 PHP
phpstudy隐藏index.php的方法
2020/09/21 PHP
网上应用的一个不错common.js脚本
2007/08/08 Javascript
JQuery 选项卡效果(JS与HTML的分离)
2010/04/01 Javascript
JS 使用for循环遍历子节点查找元素
2014/09/06 Javascript
jQuery实现div拖拽效果实例分析
2016/02/20 Javascript
AngularJS 让人爱不释手的八种功能
2016/03/23 Javascript
初识简单却不失优雅的Vue.js
2016/09/12 Javascript
JavaScript学习笔记整理_关于表达式和语句
2016/09/19 Javascript
Javascript实现倒计时(防页面刷新)实例
2016/12/13 Javascript
详解react-redux插件入门
2018/04/19 Javascript
原生js实现贪食蛇小游戏的思路详解
2019/11/26 Javascript
详解Vue中的watch和computed
2020/11/09 Javascript
vue使用require.context实现动态注册路由
2020/12/25 Vue.js
在Python中操作时间之tzset()方法的使用教程
2015/05/22 Python
Python随机数用法实例详解【基于random模块】
2017/04/18 Python
python实现壁纸批量下载代码实例
2018/01/25 Python
Python数据类型之Number数字操作实例详解
2019/05/08 Python
Python 实现还原已撤回的微信消息
2019/06/18 Python
python hough变换检测直线的实现方法
2019/07/12 Python
安装2019Pycharm最新版本的教程详解
2019/10/22 Python
python 实现矩阵按对角线打印
2019/11/29 Python
Django media static外部访问Django中的图片设置教程
2020/04/07 Python
Python第三方包PrettyTable安装及用法解析
2020/07/08 Python
Python操作Excel的学习笔记
2021/02/18 Python
HTML5+CSS3实现拖放(Drag and Drop)示例
2014/07/07 HTML / CSS
戴森比利时官方网站:Dyson BE
2020/10/03 全球购物
简述synchronized和java.util.concurrent.locks.Lock的异同
2014/12/08 面试题
招商引资工作汇报
2014/10/28 职场文书
会计试用期工作总结2015
2015/05/28 职场文书
Python实现列表拼接和去重的三种方式
2021/07/02 Python