http://blog.csdn.net/clin003/archive/2007/08/14/1743157.aspx
利用 QQWry.Dat 实现 IP 地址高效检索(PHP)
根据 LumaQQ 开发者文档中的纯真 IP 数据库格式详解,我编写了一个 PHP 的查询
IP 所在地区信息的类。在编写过程中发现纯真 IP 数据库格式详解中关于记录区的描述不是很全面,不过出入也不是很大,所以我没必要再写一份纯真 IP
数据库的格式说明了,大家感兴趣的话,读一读下面的代码应该就能看出来了。代码中加了很详细的注释,应该很容易读懂的。
在创建这个类的一个实例后,实例中就保存了打开的文件指针和一些查询需要的信息,每次查询时不需要重新打开文件,直到页面执行结束后,打开的文件才会自动关闭。这样。在一个页面内进行多次查询时,效率是很高的。并且此类不仅可以直接查询
IP,还可以自动将域名解析为 IP 进行查询。
下面是程序代码:
- <?php
- /**
- * IP 地理位置查询类
- *
- * @author 马秉尧
- * @version 1.5
- * @copyright 2005 CoolCode.CN
- */
- class IpLocation {
- /**
- * QQWry.Dat文件指针
- *
- * @var resource
- */
- var $fp;
- /**
- * 第一条IP记录的偏移地址
- *
- * @var int
- */
- var $firstip;
- /**
- * 最后一条IP记录的偏移地址
- *
- * @var int
- */
- var $lastip;
- /**
- * IP记录的总条数(不包含版本信息记录)
- *
- * @var int
- */
- var $totalip;
- /**
- * 返回读取的长整型数
- *
- * @access private
- * @return int
- */
- function getlong() {
- //将读取的little-endian编码的4个字节转化为长整型数
- $result = unpack('Vlong', fread($this->fp, 4));
- return $result['long'];
- }
- /**
- * 返回读取的3个字节的长整型数
- *
- * @access private
- * @return int
- */
- function getlong3() {
- //将读取的little-endian编码的3个字节转化为长整型数
- $result = unpack('Vlong', fread($this->fp, 3).chr(0));
- return $result['long'];
- }
- /**
- * 返回压缩后可进行比较的IP地址
- *
- * @access private
- * @param string $ip
- * @return string
- */
- function packip($ip) {
- // 将IP地址转化为长整型数,如果在PHP5中,IP地址错误,则返回False,
- // 这时intval将Flase转化为整数-1,之后压缩成big-endian编码的字符串
- return pack('N', intval(ip2long($ip)));
- }
- /**
- * 返回读取的字符串
- *
- * @access private
- * @param string $data
- * @return string
- */
- function getstring($data = "") {
- $char = fread($this->fp, 1);
- while (ord($char) > 0) { // 字符串按照C格式保存,以�结束
- $data .= $char; // 将读取的字符连接到给定字符串之后
- $char = fread($this->fp, 1);
- }
- return $data;
- }
- /**
- * 返回地区信息
- *
- * @access private
- * @return string
- */
- function getarea() {
- $byte = fread($this->fp, 1); // 标志字节
- switch (ord($byte)) {
- case 0: // 没有区域信息
- $area = "";
- break;
- case 1:
- case 2: // 标志字节为1或2,表示区域信息被重定向
- fseek($this->fp, $this->getlong3());
- $area = $this->getstring();
- break;
- default: // 否则,表示区域信息没有被重定向
- $area = $this->getstring($byte);
- break;
- }
- return $area;
- }
- /**
- * 根据所给 IP 地址或域名返回所在地区信息
- *
- * @access public
- * @param string $ip
- * @return array
- */
- function getlocation($ip) {
- if (!$this->fp) return null; // 如果数据文件没有被正确打开,则直接返回空
- $location['ip'] = gethostbyname($ip); // 将输入的域名转化为IP地址
- $ip = $this->packip($location['ip']); // 将输入的IP地址转化为可比较的IP地址
- // 不合法的IP地址会被转化为255.255.255.255
- // 对分搜索
- $l = 0; // 搜索的下边界
- $u = $this->totalip; // 搜索的上边界
- $findip = $this->lastip; // 如果没有找到就返回最后一条IP记录(QQWry.Dat的版本信息)
- while ($l <= $u) { // 当上边界小于下边界时,查找失败
- $i = floor(($l + $u) / 2); // 计算近似中间记录
- fseek($this->fp, $this->firstip + $i * 7);
- $beginip = strrev(fread($this->fp, 4)); // 获取中间记录的开始IP地址
- // strrev函数在这里的作用是将little-endian的压缩IP地址转化为big-endian的格式
- // 以便用于比较,后面相同。
- if ($ip < $beginip) { // 用户的IP小于中间记录的开始IP地址时
- $u = $i - 1; // 将搜索的上边界修改为中间记录减一
- }
- else {
- fseek($this->fp, $this->getlong3());
- $endip = strrev(fread($this->fp, 4)); // 获取中间记录的结束IP地址
- if ($ip > $endip) { // 用户的IP大于中间记录的结束IP地址时
- $l = $i + 1; // 将搜索的下边界修改为中间记录加一
- }
- else { // 用户的IP在中间记录的IP范围内时
- $findip = $this->firstip + $i * 7;
- break; // 则表示找到结果,退出循环
- }
- }
- }
- //获取查找到的IP地理位置信息
- fseek($this->fp, $findip);
- $location['beginip'] = long2ip($this->getlong()); // 用户IP所在范围的开始地址
- $offset = $this->getlong3();
- fseek($this->fp, $offset);
- $location['endip'] = long2ip($this->getlong()); // 用户IP所在范围的结束地址
- $byte = fread($this->fp, 1); // 标志字节
- switch (ord($byte)) {
- case 1: // 标志字节为1,表示国家和区域信息都被同时重定向
- $countryOffset = $this->getlong3(); // 重定向地址
- fseek($this->fp, $countryOffset);
- $byte = fread($this->fp, 1); // 标志字节
- switch (ord($byte)) {
- case 2: // 标志字节为2,表示国家信息又被重定向
- fseek($this->fp, $this->getlong3());
- $location['country'] = $this->getstring();
- fseek($this->fp, $countryOffset + 4);
- $location['area'] = $this->getarea();
- break;
- default: // 否则,表示国家信息没有被重定向
- $location['country'] = $this->getstring($byte);
- $location['area'] = $this->getarea();
- break;
- }
- break;
- case 2: // 标志字节为2,表示国家信息被重定向
- fseek($this->fp, $this->getlong3());
- $location['country'] = $this->getstring();
- fseek($this->fp, $offset + 8);
- $location['area'] = $this->getarea();
- break;
- default: // 否则,表示国家信息没有被重定向
- $location['country'] = $this->getstring($byte);
- $location['area'] = $this->getarea();
- break;
- }
- if ($location['country'] == " CZ88.NET") { // CZ88.NET表示没有有效信息
- $location['country'] = "未知";
- }
- if ($location['area'] == " CZ88.NET") {
- $location['area'] = "";
- }
- return $location;
- }
- /**
- * 构造函数,打开 QQWry.Dat 文件并初始化类中的信息
- *
- * @param string $filename
- * @return IpLocation
- */
- function IpLocation($filename = "QQWry.Dat") {
- $this->fp = 0;
- if (($this->fp = @fopen($filename, 'rb')) !== false) {
- $this->firstip = $this->getlong();
- $this->lastip = $this->getlong();
- $this->totalip = ($this->lastip - $this->firstip) / 7;
- //注册析构函数,使其在程序执行结束时执行
- register_shutdown_function(array(&$this, '_IpLocation'));
- }
- }
- /**
- * 析构函数,用于在页面执行结束后自动关闭打开的文件。
- *
- */
- function _IpLocation() {
- if ($this->fp) {
- fclose($this->fp);
- }
- $this->fp = 0;
- }
- }
- ?>
- Discuz 5.0 不在使用自己的IP数据,而是使用纯真IP的数据格式, 存取纯真IP数据库稍微有点麻烦,它的存储格式比较特殊也很有趣,具体的格式分析参考下面两个链接,其他语言实现参考文章末的链接。
- 《纯真IP数据库格式详解》
- 链接一:http://blog.csdn.Net/heiyeshuwu/archive/2006/05/12/725675.aspx
- 链接二:http://lumaqq.Linuxsir.org/article/qqwry_format_detail.html
- 纯真IP数据库官网:http://www.cz88.Net/ip/
- 纯真IP数据库下载:http://update.cz88.Net/soft/qqwry.rar
- 以下函数conrvertip()位于 Discuz!5_GBK/upload/include/misc.func.Php 路径中,有兴趣可以具体去阅读分析。(下面代码我做了简单的修改,更便于阅读,核心没有修改)
- <?
- //===================================
- //
- // 功能:IP地址获取真实地址函数
- // 参数:$ip - IP地址
- // 作者:[Discuz!] (C) Comsenz Inc.
- //
- //===================================
- function convertip($ip) {
- //IP数据文件路径
- $dat_path = 'QQWry.Dat';
- //检查IP地址
- if(!preg_match("/^d{1,3}.d{1,3}.d{1,3}.d{1,3}$/", $ip)) {
- return 'IP Address Error';
- }
- //打开IP数据文件
- if(!$fd = @fopen($dat_path, 'rb')){
- return 'IP date file not exists or access denied';
- }
- //分解IP进行运算,得出整形数
- $ip = explode('.', $ip);
- $ipNum = $ip[0] * 16777216 + $ip[1] * 65536 + $ip[2] * 256 + $ip[3];
- //获取IP数据索引开始和结束位置
- $DataBegin = fread($fd, 4);
- $DataEnd = fread($fd, 4);
- $ipbegin = implode('', unpack('L', $DataBegin));
- if($ipbegin < 0) $ipbegin += pow(2, 32);
- $ipend = implode('', unpack('L', $DataEnd));
- if($ipend < 0) $ipend += pow(2, 32);
- $ipAllNum = ($ipend - $ipbegin) / 7 + 1;
- $BeginNum = 0;
- $EndNum = $ipAllNum;
- //使用二分查找法从索引记录中搜索匹配的IP记录
- while($ip1num>$ipNum || $ip2num<$ipNum) {
- $Middle= intval(($EndNum + $BeginNum) / 2);
- //偏移指针到索引位置读取4个字节
- fseek($fd, $ipbegin + 7 * $Middle);
- $ipData1 = fread($fd, 4);
- if(strlen($ipData1) < 4) {
- fclose($fd);
- return 'System Error';
- }
- //提取出来的数据转换成长整形,如果数据是负数则加上2的32次幂
- $ip1num = implode('', unpack('L', $ipData1));
- if($ip1num < 0) $ip1num += pow(2, 32);
- //提取的长整型数大于我们IP地址则修改结束位置进行下一次循环
- if($ip1num > $ipNum) {
- $EndNum = $Middle;
- continue;
- }
- //取完上一个索引后取下一个索引
- $DataSeek = fread($fd, 3);
- if(strlen($DataSeek) < 3) {
- fclose($fd);
- return 'System Error';
- }
- $DataSeek = implode('', unpack('L', $DataSeek.chr(0)));
- fseek($fd, $DataSeek);
- $ipData2 = fread($fd, 4);
- if(strlen($ipData2) < 4) {
- fclose($fd);
- return 'System Error';
- }
- $ip2num = implode('', unpack('L', $ipData2));
- if($ip2num < 0) $ip2num += pow(2, 32);
- //没找到提示未知
- if($ip2num < $ipNum) {
- if($Middle == $BeginNum) {
- fclose($fd);
- return 'Unknown';
- }
- $BeginNum = $Middle;
- }
- }
- //下面的代码读晕了,没读明白,有兴趣的慢慢读
- $ipFlag = fread($fd, 1);
- if($ipFlag == chr(1)) {
- $ipSeek = fread($fd, 3);
- if(strlen($ipSeek) < 3) {
- fclose($fd);
- return 'System Error';
- }
- $ipSeek = implode('', unpack('L', $ipSeek.chr(0)));
- fseek($fd, $ipSeek);
- $ipFlag = fread($fd, 1);
- }
- if($ipFlag == chr(2)) {
- $AddrSeek = fread($fd, 3);
- if(strlen($AddrSeek) < 3) {
- fclose($fd);
- return 'System Error';
- }
- $ipFlag = fread($fd, 1);
- if($ipFlag == chr(2)) {
- $AddrSeek2 = fread($fd, 3);
- if(strlen($AddrSeek2) < 3) {
- fclose($fd);
- return 'System Error';
- }
- $AddrSeek2 = implode('', unpack('L', $AddrSeek2.chr(0)));
- fseek($fd, $AddrSeek2);
- } else {
- fseek($fd, -1, SEEK_CUR);
- }
- while(($char = fread($fd, 1)) != chr(0))
- $ipAddr2 .= $char;
- $AddrSeek = implode('', unpack('L', $AddrSeek.chr(0)));
- fseek($fd, $AddrSeek);
- while(($char = fread($fd, 1)) != chr(0))
- $ipAddr1 .= $char;
- } else {
- fseek($fd, -1, SEEK_CUR);
- while(($char = fread($fd, 1)) != chr(0))
- $ipAddr1 .= $char;
- $ipFlag = fread($fd, 1);
- if($ipFlag == chr(2)) {
- $AddrSeek2 = fread($fd, 3);
- if(strlen($AddrSeek2) < 3) {
- fclose($fd);
- return 'System Error';
- }
- $AddrSeek2 = implode('', unpack('L', $AddrSeek2.chr(0)));
- fseek($fd, $AddrSeek2);
- } else {
- fseek($fd, -1, SEEK_CUR);
- }
- while(($char = fread($fd, 1)) != chr(0)){
- $ipAddr2 .= $char;
- }
- }
- fclose($fd);
- //最后做相应的替换操作后返回结果
- if(preg_match('/http/i', $ipAddr2)) {
- $ipAddr2 = '';
- }
- $ipaddr = "$ipAddr1 $ipAddr2";
- $ipaddr = preg_replace('/CZ88.Net/is', '', $ipaddr);
- $ipaddr = preg_replace('/^s*/is', '', $ipaddr);
- $ipaddr = preg_replace('/s*$/is', '', $ipaddr);
- if(preg_match('/http/i', $ipaddr) || $ipaddr == '') {
- $ipaddr = 'Unknown';
- }
- return $ipaddr;
- }
- //========================
- //
- // 调用举例(速度很快)
- //
- //========================
- echo convertip('219.238.235.10');
- //输出: 北京市 电信通
- echo convertip('23.56.82.12');
- //输出:IANA
- echo convertip('250.69.52.0');
- //输出:IANA保留地址
- echo convertip('238.69.52.0');
- //输出:IANA保留地址 用于多点传送
- echo convertip('192.168.0.1');
- //输出:局域网 对方和您在同一内部网
- echo convertip('255.255.255.255');
- //输出:纯真网络 2006年11月20日IP数据
- ?>
附:(相应其他实现程序)
Php)
"
href=
"
http
:
//
www.coolcode.cn/?p=16" rel=bookmark>利用 QQWry.Dat 实现 IP 地址高效检索(Php)(作者: andot)
数据库(QQWry
.
Dat)查询 C源码
"
href=
"
http
:
//
www.douzi.org/wp/index.Php/articles/71" rel=bookmark>纯真IP数据库(QQWry.Dat)查询 C源码 (作者:Windix)
转载纯真ip库的更多相关文章
-
lib-qqwry v1.0 发布 nodejs解析纯真IP库(qqwry.dat)
lib-qqwry是当初学习node时用来练手的一个模块,用来解析纯真IP库的 现在发一个v1.0版本弥补我当时稚嫩的代码. 意外收获是,整理代码后发现,相比v0.x版本 急速模式下的效率提升大概20 ...
-
使用纯真IP库获取用户端地理位置信息
引言 在一些电商类或者引流类的网站中经常会有获取用户地理位置信息的需求,下面我分享一个用纯真IP库获取用户地理位置信息的方案. 正文 第一步:本文的方案是基于纯真IP库的,所以首先要去下载最新的纯真I ...
-
PHP Swoole 基于纯真IP库根据IP匹配城市
把纯真IP库读到内存,纯真IP库本来就是有序的,然后每次请求二分查找就行,44WIP查找十几次就搞定了 dispatch_mode最好写3,不然做服务的时候,会导致进程任务分配不均匀. max_req ...
-
Java使用纯真IP库获取IP对应省份和城市
原文:http://blog.csdn.net/chwshuang/article/details/78027873?locationNum=10&fps=1 Java使用纯真IP库获取IP对 ...
-
qqwry - 纯真ip库的golang服务
qqwry 纯真 IP 库的一个服务.通过http提供一个ip地址归属地查询支持 软件介绍 我们大家做网站的时候,都会需要将用户的IP地址转换为归属地址功能,而之前的作法大都是从硬盘的数据文件中读取, ...
-
解析纯真IP地址库
一周以来,一直在做 IP地址库的解析.从调研到编码到优化,大概花了有七八天的时间.感觉很好玩.总结一下整个做的过程. 1.关于IP 地址库的解析方式 目前主要的解析方式有两种:通过API,或通过IP数 ...
-
纯真IP根据IP地址获得地址
<?php /** * 纯真IP根据IP地址获得地址 */ class ipLocation { public $fp; public $firstip; //第一条ip索引的偏移地址 publ ...
-
PHP获取IP及地区信息(纯真IP数据库)
昨天在写程序的时候,发现在用户的时候记录IP和地区信息也许以后用得上,去网上找了找,发现实现的方式有好多好多,因为我用的ThinkPHP,后来又去TP官网找了找,最后采用了下面这种方法. <?p ...
-
C# 调用IP库(QQWry.Dat)查询IP位置及自动升级IP库方法【转】
前言 C# 用IP地址(123.125.114.144)查询位置(北京市百度公司)的东西,非常好用也非常方便,可手动升级刷新IP库,一次编码永久收益,可支持winform.asp.net等程序. 本文 ...
随机推荐
-
Entity FrameWork 6帮助类
public class BaseDAL { string strConn = ""; public BaseDAL(string connString) { strConn = ...
-
常用JS效果 需要时更新。。。
1.手风琴效果 JS: $(function() { var aMenuOneLi = $(".menu-one > li"); var aMenuTwo = ...
-
(5)Redis几个认识误区
前几天微博发生了一起大的系统故障,很多技术的朋友都比较关心,其中的原因不会超出James Hamilton在On Designing and Deploying Internet-Scale Serv ...
-
NoCache
<META HTTP-EQUIV="Pragma" CONTENT="no-cache"> <META HTTP-EQUIV="Ca ...
-
数据文件个数大于1024时ORACLE数据文件FILE_ID及RELATIVE_FNO的变化示例
通过ROWID计算数据块的相关信息: --详见: 数据文件头块保留大小.ROWID.数据文件最大大小等数据库限制的说明 根据small file tablespace的ROWID,计算出表空间.数据 ...
-
【转】[转]order by 1是什么意思?
[转][转]order by 1是什么意思? ORDER BY 1 表示 所select 的字段按第一个字段排序 ORDER BY ASC应该没有这样写法,ORDER BY 后面不是字段就是数字, 可 ...
-
https://www.chromestatus.com/features/5093566007214080
移动端滑动报错:Unable to preventDefault inside passive event listener due to target being treated as passiv ...
-
前端(二)之 CSS
前端之 CSS 前言 昨天学习了标记式语言,也就是无逻辑语言.了解了网页的骨架是什么构成的,了解了常用标签,两个指令以及转义字符:其中标签可以分为两大类: 一类是根据标签内容可以分类单双标签,单标签指 ...
-
Xcode8的调试技能Memory Graph 实战解决闭包引用循环问题
Xcode8的调试技能又增加了一个黑科技:Memory Graph.简单的说就是可以在运行时将内存中的对象生成一张图. 那么通过一个实际项目来练习一下吧. 首先我们写了一个自定义UIView:MyVi ...
-
zoj3765
题解: splay维护 注意是gcd 代码: #include<bits/stdc++.h> using namespace std; #define Key_value ch[ch[ro ...