当前位置: 首页 > 图文教程 > 网络编程 > PHP > 珊瑚虫IP库浅析

PHP
利用discuz实现PHP大文件上传应用实例代码
php 遍历显示文件夹下所有目录、所有文件的函数,没有分页的代码
php在字符串中查找另一个字符串
php下HTTP Response中的Chunked编码实现方法
PHP实现定时生成HTML网站首页实例代码
PHP下一个非常全面获取图象信息的函数
PHP strtr() 函数使用说明
php 删除无限级目录与文件代码共享
php AJAX实例根据邮编自动完成地址信息
php miniBB中文乱码问题解决方法
cmd下运行php脚本
PHP 遍历XP文件夹下所有文件
php 移除数组重复元素的一点说明
discuz论坛 用户登录 后台程序代码
Optimizer与Debugger兼容性问题的解决方法
Zend Studio 无法启动的问题解决方法
php 页面执行时间计算代码
php smarty的预保留变量总结
php获取网页内容方法总结
php读取msn上的用户信息类

PHP 中的 珊瑚虫IP库浅析


出处:互联网   整理: 软晨网(RuanChen.com)   发布: 2009-10-12   浏览: 268 ::
收藏到网摘: n/a

这不是什么新鲜事情了,很早之前就已经有人做出来了。
就是使用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地址索引区的开始。
如下图所示:


点击放大

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