October 24, 2011 分类: ASM/C/C++, PHP/MySQL     作者: hoverlees     留言: 5

最近有打算给博客做个统计插件,看看都是哪儿来的朋友在看我的博客儿,需要一个详细的IP地址归属地数据库。网上找了个纯真的MySQL版的,30多万条数据占了20M左右的数据库空间,觉得有点浪费数据库空间,而且数据库查询时间太慢,特别是要一次性查多个结果时,更是不得了.最主要的是这种应用场合不适合做成数据库形式的。

所以就准备自己做数据库了,自己也有50000亿条数据的数据文件的设计经验。这才30多万条没什么问题。。。按自己的想法生成了文件,并写了PHP和C语言的访问API,心情好的时候再补写上Java啊,汇编啊什么的API也是挺好的。

数据库下载:GBK版 UTF8版

PHP API(函数):这个是函数形式的PHP代码,博客版面上是写描述,具体代码在这里:ips.php ip_area.php

<?php
/**
 * IP地址归属地查询 PHP函数版
 * @author Hoverlees http://www.hoverlees.com me[at]hoverlees.com
 * @comment 此功能同时包含C/C++,PHP类,PHP函数,等版本提供下载。
 * 数据库文件及相关代码下载地址:http://www.hoverlees.com/blog/?p=906
 */

/*对象状态常量*/
define('IPV4_AREA_ERROR_SUCCESS',0); //正常
define('IPV4_AREA_ERROR_OPENFILE',-1); //无法打开文件
define('IPV4_AREA_ERROR_FORMAT',-2); //数据库格式不对

/**
 * IPv4地址转整数函数,注意本函数不检查IP地址的合法性
 * @param $ip IPv4地址
 * @return 转换后的整数
 */
function ipv4_atol($ip){}
/**
 * 创建IP表对象
 * @param $filename 数据库文件名
 * @param $index_read_parts 索引划分的块数,如果为1,表示索引全部读入内存(占用大约4M内存空间),如果为2则只需要占用4/2=2M内存临时空间。此值越大,需要的内存缓冲越少,但磁盘的IO次数增加。如果需要循环查询多个IP,建议此值设为1.如果只查询一次可以根据内存要求自行设定。
 * @return 用于查询的数据库对象,使用完成后请调用ipv4_area_free释放占用的资源
 */
function ipv4_area_create($filename,$index_read_parts=1){}
/**
 * IP址所在地查询
 * @param $obj 数据库对象
 * @param $ip IP地址,可以为'XXX.XXX.XXX.XXX'格式
 * @return 归属地名称
*/
function ipv4_area_search($obj,$ip){}
/**
 * 释放IP数据库
 * @param $object 由函数ipv4_area_create返回的表对象
 */
function ipv4_area_free($object){}
?>

代码调用示例:

<?php
include('./ips.php'); //包含测试数组$test_ips,包含1760个需要查询所在地的IP地址
$s=microtime(true);
$object=ipv4_area_create('./ip_utf8.dat');//初始化数据库
if($object['status']==IPV4_AREA_ERROR_SUCCESS){
	foreach($test_ips as $ip){ //循环查询每一个IP的所在地
		$area=ipv4_area_search($object,$ip);
		echo "IP:{$ip}<BR>AREA:{$area}<BR><BR>\n";
	}
}
ipv4_area_free($object); //释放对象
echo microtime(true)-$s; //当创建时参数index_read_parts=1时,查询1760条IP所在地仅需3.68秒(上网本)
?>

PHP IPArea类,功能都跟上面一样的,不过不同的人习惯不一样呗,咱就多提供一种思路了。完整的代码在这里: IPArea.php

<?php
/**
 * IP地址归属地查询 PHP类版
 * @author Hoverlees http://www.hoverlees.com me[at]hoverlees.com
 * @comment 此功能同时包含C/C++,PHP类,PHP函数,等版本提供下载。
 * 数据库文件及相关代码下载地址:http://www.hoverlees.com/blog/?p=906
 */

class IPArea{
	/*对象状态常量*/
	const IPV4_AREA_ERROR_SUCCESS=0;//正常
	const IPV4_AREA_ERROR_OPENFILE=-1; //无法打开文件
	const IPV4_AREA_ERROR_FORMAT=-2; //数据库格式不对
	private $innerObject;
	/**
	 * 构造函数
	 * @param $filename 数据库文件名
	 * @param $index_read_parts 索引划分的块数,如果为1,表示索引全部读入内存(占用大约4M内存空间),如果为2则只需要占用4/2=2M内存临时空间。此值越大,需要的内存缓冲越少,但磁盘的IO次数增加。如果需要循环查询多个IP,建议此值设为1.如果只查询一次可以根据内存要求自行设定。
	 */
	public function IPArea($filename,$index_read_parts=1){}
	/**
	 * 取得对象状态
	 * @return 返回状态常量之一
	 */
	public function getStatus(){}
	/**
	 * 取得数据库的编码
	 * @return 返回编码,可能是UTF-8或GBK
	 */
	public function getDBEncoding(){}
	/**
	 * IP址所在地查询
	 * @param $ip IP地址,可以为'XXX.XXX.XXX.XXX'格式
	 * @return 归属地名称
	*/
	public function search($ip){}
	/**
	 * 释放对象占用的资源
	 */
	public function release(){
}
	/**
	 * IPv4地址转整数函数,注意本函数不检查IP地址的合法性
	 * @param $ip IPv4地址
	 * @return 转换后的整数
	 */
	public static function atol($ip){}
	private function getAreaString($section_offset){}
}
?>

调用示例代码:

<?php
include('./ips.php'); //包含测试数组$test_ips,包含1760个需要查询所在地的IP地址
$ia=new IPArea('./ip_utf8.dat');
if($ia->getStatus()==0){
	foreach($test_ips as $ip){ //循环查询每一个IP的所在地
		$area=$ia->search($ip);
		echo "IP:{$ip}<BR>AREA:{$area}<BR><BR>\n";
	}
}
$ia->release();
?>

接下来是大家期待的C语言代码了,C语言查询1760个IP地址归属地仅需要0.6s。
ip_area.h ip_area.c ips.h

/**
 * IP地址归属地查询 C/C++版
 * @author Hoverlees http://www.hoverlees.com me[at]hoverlees.com
 * @comment 此功能同时包含C/C++,PHP类,PHP函数,等版本提供下载。
 * 数据库文件及相关代码下载地址:http://www.hoverlees.com/blog/?p=906
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifndef __HOVERLEES_IP_AREA_H
#define __HOVERLEES_IP_AREA_H

/*对象状态常量*/

#define IPV4_AREA_ERROR_SUCCESS 0 //正常

#define IPV4_AREA_ERROR_OPENFILE -1 //无法打开文件

#define IPV4_AREA_ERROR_FORMAT -2 //数据库格式不对

typedef struct _IP_AREA_OBJ{
	int status;
	int num_items;
	int index_pos;
	int data_pos;
	int index_read_parts;
	int each_read_item;
	int each_read_byte;
	FILE* fp;
	char* last_data;
	char encoding[8];
}IP_AREA_OBJ;

/**
 * IPv4地址转整数函数,注意本函数不检查IP地址的合法性
 * @param ip IPv4地址
 * @return 转换后的32位整数
 */
unsigned int ipv4_atoi(const char* ip);

/**
 * 创建IP表对象
 * @param filename 数据库文件名
 * @param index_read_parts 索引划分的块数,如果为1,表示索引全部读入内存(占用大约4M内存空间),如果为2则只需要占用4/2=2M内存临时空间。此值越大,需要的内存缓冲越少,但磁盘的IO次数增加。如果需要循环查询多个IP,建议此值设为1.如果只查询一次可以根据内存要求自行设定。
 * @return 用于查询的数据库对象,使用完成后请调用ipv4_area_free释放占用的资源
 */
IP_AREA_OBJ* ipv4_area_create(const char* filename,int index_read_parts);
/**
 * 释放IP数据库
 * @param obj 由函数ipv4_area_create返回的表对象
 */
void ipv4_area_free(IP_AREA_OBJ* obj);

/**
 * IP址所在地查询
 * @param obj 数据库对象
 * @param ip IP地址,可以为'XXX.XXX.XXX.XXX'格式
 * @param buf 地址缓存,请确保大于100字节。
 * @return buf本身
*/
char* ipv4_area_search(IP_AREA_OBJ* obj,const char* ip,char* buf);

#endif

C语言调用示例:

#include <stdio.h>
#include <stdlib.h>
#include "ip_area.h"

#include "ips.h" //1760多个IP地址数组

void main(int argc,char* argv[]){
	int i;
	char out[100];
	IP_AREA_OBJ* obj;
	obj=ipv4_area_create("./ip_utf8.dat",1);
	for(i=0;i<1767;i++){
		ipv4_area_search(obj,ips[i],out);
		printf("IP:%s  AREA:%s\n",ips[i],out);
	}
	ipv4_area_free(obj);
}

有空的时候再传传JAVA的版本上来。如果代码有错误,请指出,我会尽快fix掉。

标签: , , ,
访客留言[谢谢!]
junguo
做的不错!赞一个
2011-10-31 20:40:24
liulang
50000亿条数据?!!能透露下是什么应用吗?不用数据库?
2012-12-15 19:27:45
hoverlees
是一个超过100T的静态数据库,自己用B+,二分,等算法散列到多个数据文件就行了.无须数据库.
2012-12-17 08:59:35
vindong
您好,我现在的一个项目也遇到了这种情况,mysql存放ip库查询太慢了,我想请问您是如何压缩成dat文件的,十分感谢!!!我的联系邮箱xuanmumu@gmail.com
2012-12-19 20:21:40
hoverlees
你好,我是自己写程序组织的数据文件,主要使用了二分查找法建立索引数据. 如果你是数据库查一条IP很慢,可能是你数据库设计的问题.
2012-12-19 21:24:35
我来留个言

您的电子邮箱我一定会保密的哦!

昵称

邮箱

评论内容