手机
当前位置:查字典教程网 >编程开发 >php教程 >珊瑚虫IP库浅析
珊瑚虫IP库浅析
摘要:这不是什么新鲜事情了,很早之前就已经有人做出来了。就是使用PHP操作纯真IP库或珊瑚虫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个字节是索引区的结束地址。

如下图所示:

珊瑚虫IP库浅析1

点击放大

由于数据库是使用了little endian的字节库,所以我们需要把它倒过来。

把文件头的0-3的字节读取出来,再使用 unpack 函数把二进制数据转换为big endian格式的无符号整型。

处理后,索引区的开始地址位置是:00077450 ;索引区的结束地址位置是:000CE17C。

如果你手头上有UltraEdit的软件,可以打开CoralWry.dat文件,查找地址为:00077450 的位置,那就是IP地址索引区的开始。

如下图所示:

珊瑚虫IP库浅析2

点击放大

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

【珊瑚虫IP库浅析】相关文章:

用PHP编程开发“虚拟域名”系统

Php部分常见问题总结第1/2页

Access数据库导入Mysql的方法之一

mod_php、FastCGI、PHP-FPM等PHP运行方式对比

提升PHP执行速度全攻略(下)

PHP递归调用数组值并用其执行指定函数的方法

PHP中的流(streams)浅析

一个简单的自动发送邮件系统(一)

PHP的Socket通信之UDP通信实例

无数据库的详细域名查询程序PHP版(2)

精品推荐
分类导航