zoukankan      html  css  js  c++  java
  • 纯真IP库,珊瑚虫IP库浅析

    使用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个字节是索引区的结束地址。

    如下图所示:



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



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

  • 相关阅读:
    Java_基础_内存管理
    Reflux中文教程——action
    Reflux中文教程——概览
    包含块、层叠上下文、BFC
    DOM编程的性能问题
    JavaScript数据存取的性能问题
    闭包
    JavaScript中的继承
    JavaScript中的静态成员
    JavaScript中的私有属性
  • 原文地址:https://www.cnblogs.com/top5/p/1594754.html
Copyright © 2011-2022 走看看