這不是什么新鮮事情了,很早之前就已經(jīng)有人做出來了。 就是使用PHP操作純真IP庫或珊瑚蟲IP庫,根據(jù)來訪者的IP得到所在的物理位置。
我先帖出代碼。然后再慢慢一步步淺析出來。希望對想了解這一塊的朋友們有幫助。
Only For PHP5的代碼。會繼續(xù)優(yōu)化代碼的。
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地址打包成二進制數(shù)據(jù),以big endian(高位在前)格式打包 * 數(shù)據(jù)存儲格式為 little endian(低位在前) 如: * 00 28 C6 DA 218.198.40.0 little endian * 3F 28 C6 DA 218.198.40.0 little endian * 這樣的數(shù)據(jù)無法作二分搜索查找的比較,所以必須先把獲得的IP數(shù)據(jù)使用strrev轉(zhuǎn)換為big endian * @param $ip * @return big endian格式的二進制數(shù)據(jù) */ 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數(shù)據(jù)庫格式詳解
CoralWry.dat文件結(jié)構(gòu)上分為3個區(qū)域:
- 文件頭[固定8個字節(jié)]
- 數(shù)據(jù)區(qū)[不固定長度,記錄IP的地址信息]
- 索引區(qū)[大小由文件頭決定]
該文件數(shù)據(jù)的存儲方式是:little endian。 在這里引用了談?wù)刄nicode編碼里的關(guān)于little endian 與 big endian的區(qū)別
引用:
big endian和little endian是CPU處理多字節(jié)數(shù)的不同方式。例如“漢”字的Unicode編碼是6C49。那么寫到文件里時,究竟是將6C寫在前面,還是將49寫在前面?如果將6C寫在前面,就是big endian。還是將49寫在前面,就是little endian。
“endian”這個詞出自《格列佛游記》。小人國的內(nèi)戰(zhàn)就源于吃雞蛋時是究竟從大頭(Big-Endian)敲開還是從小頭(Little-Endian)敲開,由此曾發(fā)生過六次叛亂,其中一個皇帝送了命,另一個丟了王位。
我們一般將endian翻譯成“字節(jié)序”,將big endian和little endian稱作“大尾”和“小尾”。
文件頭: 紅色框框里的就是文件頭,前4個字節(jié)是索引區(qū)的開始地址,后4個字節(jié)是索引區(qū)的結(jié)束地址。
如下圖所示:
點擊放大
由于數(shù)據(jù)庫是使用了little endian的字節(jié)庫,所以我們需要把它倒過來。 把文件頭的0-3的字節(jié)讀取出來,再使用 unpack 函數(shù)把二進制數(shù)據(jù)轉(zhuǎn)換為big endian格式的無符號整型。 處理后,索引區(qū)的開始地址位置是:00077450 ;索引區(qū)的結(jié)束地址位置是:000CE17C。 如果你手頭上有UltraEdit的軟件,可以打開CoralWry.dat文件,查找地址為:00077450 的位置,那就是IP地址索引區(qū)的開始。 如下圖所示:
點擊放大
紅色框框住那就是索引區(qū)的開始位置。
經(jīng)典論壇討論: http://bbs.blueidea.com/thread-2703212-1-1.html
出處:藍色理想
責(zé)任編輯:moby
◎進入論壇網(wǎng)絡(luò)編程版塊參加討論
|