| 这不是什么新鲜事情了,很早之前就已经有人做出来了。就是使用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地址索引区的开始。
 如下图所示:
  点击放大
 红色框框住那就是索引区的开始位置。 经典论坛讨论:http://bbs.blueidea.com/thread-2703212-1-1.html
 出处:蓝色理想
责任编辑:moby
 ◎进入论坛网络编程版块参加讨论
	      |