Merge branch 'master' of github.com:lionsoul2016/ip2region into nodejsclient

This commit is contained in:
dongyado 2016-07-03 00:07:52 +08:00
commit 4a8aa275e0
13 changed files with 31495 additions and 8890 deletions

7
CHANGES.md Normal file
View File

@ -0,0 +1,7 @@
### 1.2
1. 新增分列式升级算法大大的增加了数据的准确率基本避免之前各大网友反馈的些许ip定位错误
2. 数据升级是2016/06/30版
3. 优化数据文件生成算法ip2region.db文件由原来的3.5M降为1.5M42亿个IP地址皆大欢喜啊
4. C/PHP/JAVA客户端增加纯内存搜索模式python, php扩展nodejs会在后续版本加上
说明因为数据文件只有1.5M对于PHP,java这类IO优化类语言提升速度不大C有一个数量级的提升

View File

@ -1,6 +1,6 @@
ip2region - ip到地区的映射库妈妈再也不用担心我的ip定位。
ip2region - 最自由的ip地址查询库ip到地区的映射库提供Binary,B树和纯内存三种查询算法妈妈再也不用担心我的ip地址定位。
**1. 99.9%准确率,定时更新:**
**1. 99.9%准确率,定时更新:**
数据聚合了一些知名ip到地名查询提供商的数据这些是他们官方的的准确率经测试着实比纯真啥的准确多了。<br />
每次聚合一下数据需要1-2天会不定时更新。
@ -14,20 +14,20 @@ ip2region - ip到地区的映射库妈妈再也不用担心我的ip定位。
**3. 体积小:**
生成的数据库文件ip2region.db只有3.5M
生成的数据库文件ip2region.db只有1.5M1.2版本前是3.5M
**4. 多查询客户端的支持0.0x毫秒级别的查询**
已经集成的客户端有java, php, c, pythonphp扩展(支持linux, php5, php7版本已支持)。
提供了两种查询算法,响应时间如下:
客户端/binary算法/b-tree算法
java/0.x毫秒/0.x毫秒 (使用RandomAccessFile)
php/0.x毫秒/0.1x毫秒
c/0.0x毫秒/0.0x毫秒(b-tree算法基本稳定在0.02x毫秒级别)
python/0.x毫秒/0.1x毫秒
客户端/binary算法/b-tree算法/Memory算法
java/0.x毫秒/0.x毫秒/0.1x毫秒 (使用RandomAccessFile)
php/0.x毫秒/0.1x毫秒/0.1x毫秒
c/0.0x毫秒/0.0x毫秒/0.00x毫秒(b-tree算法基本稳定在0.02x毫秒级别)
python/0.x毫秒/0.1x毫秒/未知
任何客户端b-tree都比binary算法快
任何客户端b-tree都比binary算法快当然Memory算法固然是最快的
**5. 测试程序:**
java:
@ -61,10 +61,6 @@ python:
p2region>> 101.105.35.57
2163|中国|华南|广东省|深圳市|鹏博士 in 0.02295 millseconds
输入ip地址开始测试第一次会稍微有点慢在运行命令后面接入binary来尝试binary算法建议使用b-tree算法。
输入ip地址开始测试第一次会稍微有点慢在运行命令后面接入binary,memory来尝试其他算法建议使用b-tree算法速度和并发需求的可以使用memory算法。
具体集成请参考不同客户端的测试源码。
**6. 联系作者:**
狮子的魂: chenxin619315@gmail.com

View File

@ -35,12 +35,15 @@ IP2R_API uint_t ip2region_create(ip2region_t ip2rObj, char *dbFile)
if ( ip2rObj->dbHandler == NULL ) {
IP2R_FREE(ip2rObj->HeaderSip);
IP2R_FREE(ip2rObj->HeaderPtr);
//fprintf(stderr, "Fail to open the db file %s\n", ip2rObj>dbFile);
//exit(-1);
return 0;
}
ip2rObj->firstIndexPtr = 0;
ip2rObj->lastIndexPtr = 0;
ip2rObj->totalBlocks = 0;
ip2rObj->dbBinStr = NULL;
return 1;
}
@ -63,9 +66,94 @@ IP2R_API uint_t ip2region_destroy(ip2region_t ip2rObj)
ip2rObj->dbHandler = NULL;
}
//free the db binary string
if ( ip2rObj->dbBinStr != NULL ) {
IP2R_FREE(ip2rObj->dbBinStr);
ip2rObj->dbBinStr = NULL;
}
return 1;
}
/**
* get the region associated with the specified ip address with the memory binary search algorithm
*
* @param ip2rObj
* @param ip
* @param datablock
*/
IP2R_API uint_t ip2region_memory_search(ip2region_t ip2rObj, uint_t ip, datablock_t datablock)
{
int l, h, m, p;
uint_t sip, eip, dptr;
int dataLen, dataptr;
long filesize;
char *buffer;
if ( ip2rObj->dbBinStr == NULL ) {
//get the size of the file
fseek(ip2rObj->dbHandler, 0, SEEK_END);
filesize = ftell(ip2rObj->dbHandler);
fseek(ip2rObj->dbHandler, 0, SEEK_SET);
//alloc the buffer size
ip2rObj->dbBinStr = IP2R_MALLOC(filesize);
if ( ip2rObj->dbBinStr == NULL ) {
return 0;
}
//now read the whole file
if ( fread(ip2rObj->dbBinStr, filesize, 1, ip2rObj->dbHandler) != 1 ) {
return 0;
}
buffer = ip2rObj->dbBinStr;
ip2rObj->firstIndexPtr = getUnsignedInt(buffer, 0);
ip2rObj->lastIndexPtr = getUnsignedInt(buffer, 4);
ip2rObj->totalBlocks = (ip2rObj->lastIndexPtr-ip2rObj->firstIndexPtr)/INDEX_BLOCK_LENGTH + 1;
}
l = 0; h = ip2rObj->totalBlocks; dptr = 0;
while ( l <= h ) {
m = (l + h) >> 1;
p = ip2rObj->firstIndexPtr + m * INDEX_BLOCK_LENGTH;
buffer = ip2rObj->dbBinStr + p;
sip = getUnsignedInt(buffer, 0);
if ( ip < sip ) {
h = m - 1;
} else {
eip = getUnsignedInt(buffer, 4);
if ( ip > eip ) {
l = m + 1;
} else {
dptr = getUnsignedInt(buffer, 8);
break;
}
}
}
if ( dptr == 0 ) return 0;
//get the data
dataLen = ((dptr >> 24) & 0xFF);
dataptr = (dptr & 0x00FFFFFF);
buffer = ip2rObj->dbBinStr + dataptr;
//fill the data to the datablock
datablock->city_id = getUnsignedInt(buffer, 0);
dataLen -= 4; //reduce the length of the city_id
memcpy(datablock->region, buffer + 4, dataLen);
datablock->region[dataLen] = '\0';
return 1;
}
IP2R_API uint_t ip2region_memory_search_string(ip2region_t ip2rObj, char *ip, datablock_t datablock)
{
return ip2region_memory_search(ip2rObj, ip2long(ip), datablock);
}
/**
* get the region associated with the specifield ip address with binary search algorithm
*

View File

@ -52,7 +52,9 @@ typedef struct {
uint_t *HeaderSip; //header start ip blocks
uint_t *HeaderPtr; //header ptr blocks
uint_t headerLen; //header block number
char *dbFile; //path of db file
FILE *dbHandler; //file handler
char *dbBinStr; //db binary string for memory search mode
uint_t firstIndexPtr; //first index ptr
uint_t lastIndexPtr; //last index ptr
@ -79,14 +81,26 @@ typedef datablock_entry * datablock_t;
IP2R_API uint_t ip2region_create(ip2region_t, char *);
/**
* destroy the specifield ip2region object
* destroy the specified ip2region object
*
* @param ip2region_t
*/
IP2R_API uint_t ip2region_destroy(ip2region_t);
/**
* get the region associated with the specifield ip address with binary search algorithm
* get the region associated with the specified ip address with the memory binary search algorithm
*
* @param ip2region_t
* @param uint_t
* @param datablock_t
* @date 2016/06/30
*/
IP2R_API uint_t ip2region_memory_search(ip2region_t, uint_t, datablock_t);
IP2R_API uint_t ip2region_memory_search_string(ip2region_t, char *, datablock_t);
/**
* get the region associated with the specified ip address with binary search algorithm
*
* @param ip2rObj
* @param ip
@ -97,7 +111,7 @@ IP2R_API uint_t ip2region_binary_search(ip2region_t, uint_t, datablock_t);
IP2R_API uint_t ip2region_binary_search_string(ip2region_t, char *, datablock_t);
/**
* get the region associated with the specifield ip address with b-tree algorithm
* get the region associated with the specified ip address with b-tree algorithm
*
* @param ip2rObj
* @param ip
@ -108,7 +122,7 @@ IP2R_API uint_t ip2region_btree_search(ip2region_t, uint_t, datablock_t);
IP2R_API uint_t ip2region_btree_search_string(ip2region_t, char *, datablock_t);
/**
* get a unsinged long(4bytes) from a specifield buffer start from the specifield offset
* get a unsinged long(4bytes) from a specified buffer start from the specified offset
*
* @param buffer
* @param offset

View File

@ -54,22 +54,27 @@ int main( int argc, char **argv )
memset(&datablock, 0x00, sizeof(datablock_entry));
if ( argc < 2 ) {
printf("Usage: a.out [ip2region db file path] [algorithm]");
printf("Usage: a.out [ip2region db file path] [algorithm]\n");
return 0;
}
dbFile = argv[1];
algorithm = "B-tree";
func_ptr = ip2region_btree_search_string;
if ( argc >= 3 && strcmp(argv[2], "binary") == 0 ) {
if ( argc >= 3 ) {
if ( strcmp(argv[2], "binary") == 0 ) {
algorithm = "Binary";
func_ptr = ip2region_binary_search_string;
} else if ( strcmp(argv[2], "memory") == 0 ) {
algorithm = "Memory";
func_ptr = ip2region_memory_search_string;
}
}
//create a new ip2rObj
printf("+--initializing %s ... \n", algorithm);
if ( ip2region_create(&ip2rEntry, dbFile) == 0 ) {
println("Error: Fail to create the ip2region object");
println("Error: Fail to create the ip2region object\n");
return 0;
}

View File

@ -9,7 +9,7 @@
<property name="jars" value="${basedir}"/>
<property name="sources" value="${basedir}/src"/>
<property name="classes" value="${basedir}/classes"/>
<property name="version" value="1.0"/>
<property name="version" value="1.2"/>
<property name="api" value="${basedir}/doc"/>
<mkdir dir="${classes}"/>
<mkdir dir="${api}"/>

View File

@ -17,10 +17,28 @@ public class DataBlock
*/
private String region;
public DataBlock( int city_id, String region )
/**
* region ptr in the db file
*/
private int dataPtr;
/**
* construct method
*
* @param city_id
* @param region region string
* @param ptr data ptr
*/
public DataBlock( int city_id, String region, int dataPtr )
{
this.city_id = city_id;
this.region = region;
this.dataPtr = dataPtr;
}
public DataBlock(int city_id, String region)
{
this(city_id, region, 0);
}
public int getCityId()
@ -45,12 +63,24 @@ public class DataBlock
return this;
}
public int getDataPtr()
{
return dataPtr;
}
public DataBlock setDataPtr(int dataPtr)
{
this.dataPtr = dataPtr;
return this;
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append(city_id).append('|').append(region);
sb.append(city_id).append('|').append(region).append('|').append(dataPtr);
return sb.toString();
}
}

View File

@ -12,7 +12,8 @@ import java.io.RandomAccessFile;
public class DbSearcher
{
public static final int BTREE_ALGORITHM = 1;
public static final int BIN_ALGORITHM = 2;
public static final int BINARY_ALGORITHM = 2;
public static final int MEMORY_ALGORITYM = 3;
/**
* db config
@ -38,6 +39,12 @@ public class DbSearcher
private long lastIndexPtr = 0;
private int totalIndexBlocks = 0;
/**
* for memory mode
* the original db binary string
*/
private byte[] dbBinStr = null;
/**
* construct class
*
@ -51,6 +58,72 @@ public class DbSearcher
raf = new RandomAccessFile(dbFile, "r");
}
/**
* get the region with a int ip address with memory binary search algorithm
*
* @param ip
* @throws IOException
*/
public DataBlock memorySearch(long ip) throws IOException
{
int blen = IndexBlock.getIndexBlockLength();
if ( dbBinStr == null ) {
dbBinStr = new byte[(int)raf.length()];
raf.seek(0L);
raf.readFully(dbBinStr, 0, dbBinStr.length);
//initialize the global vars
firstIndexPtr = Util.getIntLong(dbBinStr, 0);
lastIndexPtr = Util.getIntLong(dbBinStr, 4);
totalIndexBlocks = (int)((lastIndexPtr - firstIndexPtr)/blen) + 1;
}
//search the index blocks to define the data
int l = 0, h = totalIndexBlocks;
long sip, eip, dataptr = 0;
while ( l <= h ) {
int m = (l + h) >> 1;
int p = (int)(firstIndexPtr + m * blen);
sip = Util.getIntLong(dbBinStr, p);
if ( ip < sip ) {
h = m - 1;
} else {
eip = Util.getIntLong(dbBinStr, p + 4);
if ( ip > eip ) {
l = m + 1;
} else {
dataptr = Util.getIntLong(dbBinStr, p + 8);
break;
}
}
}
//not matched
if ( dataptr == 0 ) return null;
//get the data
int dataLen = (int)((dataptr >> 24) & 0xFF);
int dataPtr = (int)((dataptr & 0x00FFFFFF));
int city_id = (int)Util.getIntLong(dbBinStr, dataPtr);
String region = new String(dbBinStr, dataPtr + 4, dataLen - 4, "UTF-8");
return new DataBlock(city_id, region, dataPtr);
}
/**
* get the region throught the ip address with memory binary search algorithm
*
* @param ip
* @return DataBlock
* @throws IOException
*/
public DataBlock memorySearch( String ip ) throws IOException
{
return memorySearch(Util.ip2long(ip));
}
/**
* get by index ptr
*
@ -76,7 +149,7 @@ public class DbSearcher
int city_id = (int)Util.getIntLong(data, 0);
String region = new String(data, 4, data.length - 4, "UTF-8");
return new DataBlock(city_id, region);
return new DataBlock(city_id, region, dataPtr);
}
/**
@ -204,7 +277,7 @@ public class DbSearcher
int city_id = (int)Util.getIntLong(data, 0);
String region = new String(data, 4, data.length - 4, "UTF-8");
return new DataBlock(city_id, region);
return new DataBlock(city_id, region, dataPtr);
}
/**
@ -274,7 +347,7 @@ public class DbSearcher
int city_id = (int)Util.getIntLong(data, 0);
String region = new String(data, 4, data.length - 4, "UTF-8");
return new DataBlock(city_id, region);
return new DataBlock(city_id, region, dataPtr);
}
/**
@ -308,6 +381,8 @@ public class DbSearcher
{
HeaderSip = null; //let gc do its work
HeaderPtr = null;
dbBinStr = null;
raf.close();
}
}

View File

@ -4,6 +4,8 @@ import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import org.lionsoul.ip2region.DataBlock;
import org.lionsoul.ip2region.DbConfig;
@ -25,23 +27,45 @@ public class TestSearcher
return;
}
int algorithm = DbSearcher.BTREE_ALGORITHM;
File file = new File(argv[0]);
if ( file.exists() == false ) {
System.out.println("Error: Invalid ip2region.db file");
return;
}
int algorithm = DbSearcher.BTREE_ALGORITHM;
String algoName = "B-tree";
if ( argv.length > 1 ) {
if ( argv[1].equalsIgnoreCase("binary")) algorithm = DbSearcher.BIN_ALGORITHM;
if ( argv[1].equalsIgnoreCase("binary")) {
algoName = "Binary";
algorithm = DbSearcher.BINARY_ALGORITHM;
} else if ( argv[1].equalsIgnoreCase("memory") ) {
algoName = "Memory";
algorithm = DbSearcher.MEMORY_ALGORITYM;
}
}
try {
System.out.println("initializing "+((algorithm==2)?"Binary":"B-tree")+" ... ");
System.out.println("initializing "+algoName+" ... ");
DbConfig config = new DbConfig();
DbSearcher seacher = new DbSearcher(config, argv[0]);
DbSearcher searcher = new DbSearcher(config, argv[0]);
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
//define the method
Method method = null;
switch ( algorithm )
{
case DbSearcher.BTREE_ALGORITHM:
method = searcher.getClass().getMethod("btreeSearch", String.class);
break;
case DbSearcher.BINARY_ALGORITHM:
method = searcher.getClass().getMethod("binarySearch", String.class);
break;
case DbSearcher.MEMORY_ALGORITYM:
method = searcher.getClass().getMethod("memorySearch", String.class);
break;
}
System.out.println("+----------------------------------+");
System.out.println("| ip2region test shell |");
System.out.println("| Author: chenxin619315@gmail.com |");
@ -62,13 +86,13 @@ public class TestSearcher
}
sTime = System.nanoTime();
dataBlock = algorithm==2 ? seacher.binarySearch(line) : seacher.btreeSearch(line);
dataBlock = (DataBlock) method.invoke(searcher, line);
cTime = (System.nanoTime() - sTime) / 1000000;
System.out.printf("%s in %.5f millseconds\n", dataBlock, cTime);
}
reader.close();
seacher.close();
searcher.close();
System.out.println("+--Bye");
} catch (IOException e) {
// TODO Auto-generated catch block
@ -76,6 +100,21 @@ public class TestSearcher
} catch (DbMakerConfigException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchMethodException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

View File

@ -30,6 +30,13 @@ class Ip2Region
private $lastIndexPtr = 0;
private $totalBlocks = 0;
/**
* for memory mode only
* the original db binary string
*/
private $dbBinStr = NULL;
private $dbFile = NULL;
/**
* construct method
*
@ -37,7 +44,65 @@ class Ip2Region
*/
public function __construct( $ip2regionFile )
{
$this->dbFileHandler = fopen($ip2regionFile, 'r');
$this->dbFile = $ip2regionFile;
}
/**
* all the db binary string will be loaded into memory
* then search the memory only and this will a lot faster than disk base search
* @Note:
* invoke it once before put it to public invoke could make it thread safe
*
* @param $ip
*/
public function memorySearch($ip)
{
//check and load the binary string for the first time
if ( $this->dbBinStr == NULL ) {
$this->dbBinStr = file_get_contents($this->dbFile);
if ( $this->dbBinStr == false ) {
throw new Exception("Fail to open the db file {$this->dbFile}");
}
$this->firstIndexPtr = self::getLong($this->dbBinStr, 0);
$this->lastIndexPtr = self::getLong($this->dbBinStr, 4);
$this->totalBlocks = ($this->lastIndexPtr-$this->firstIndexPtr)/INDEX_BLOCK_LENGTH + 1;
}
if ( is_string($ip) ) $ip = ip2long($ip);
//binary search to define the data
$l = 0;
$h = $this->totalBlocks;
$dataPtr = 0;
while ( $l <= $h ) {
$m = (($l + $h) >> 1);
$p = $this->firstIndexPtr + $m * INDEX_BLOCK_LENGTH;
$sip = self::getLong($this->dbBinStr, $p);
if ( $ip < $sip ) {
$h = $m - 1;
} else {
$eip = self::getLong($this->dbBinStr, $p + 4);
if ( $ip > $eip ) {
$l = $m + 1;
} else {
$dataPtr = self::getLong($this->dbBinStr, $p + 8);
break;
}
}
}
//not matched just stop it here
if ( $dataPtr == 0 ) return NULL;
//get the data
$dataLen = (($dataPtr >> 24) & 0xFF);
$dataPtr = ($dataPtr & 0x00FFFFFF);
return array(
'city_id' => self::getLong($this->dbBinStr, $dataPtr),
'region' => substr($this->dbBinStr, $dataPtr + 4, $dataLen - 4)
);
}
/**
@ -51,6 +116,14 @@ class Ip2Region
//check and conver the ip address
if ( is_string($ip) ) $ip = ip2long($ip);
if ( $this->totalBlocks == 0 ) {
//check and open the original db file
if ( $this->dbFileHandler == NULL ) {
$this->dbFileHandler = fopen($this->dbFile, 'r');
if ( $this->dbFileHandler == false ) {
throw new Exception("Fail to open the db file {$this->dbFile}");
}
}
fseek($this->dbFileHandler, 0);
$superBlock = fread($this->dbFileHandler, 8);
@ -102,6 +175,7 @@ class Ip2Region
/**
* get the data block associated with the specifield ip with b-tree search algorithm
* @Note: not thread safe
*
* @param ip
* @return Mixed Array for NULL for any error
@ -112,6 +186,14 @@ class Ip2Region
//check and load the header
if ( $this->HeaderSip == NULL ) {
//check and open the original db file
if ( $this->dbFileHandler == NULL ) {
$this->dbFileHandler = fopen($this->dbFile, 'r');
if ( $this->dbFileHandler == false ) {
throw new Exception("Fail to open the db file {$this->dbFile}");
}
}
fseek($this->dbFileHandler, 8);
$buffer = fread($this->dbFileHandler, TOTAL_HEADER_LENGTH);
@ -240,7 +322,11 @@ class Ip2Region
*/
public function __destruct()
{
if ( $this->dbFileHandler != NULL ) fclose($this->dbFileHandler);
if ( $this->dbFileHandler != NULL ) {
fclose($this->dbFileHandler);
}
$this->dbBinStr = NULL;
$this->HeaderSip = NULL;
$this->HeaderPtr = NULL;
}

View File

@ -15,12 +15,19 @@ EOF;
array_shift($argv);
$dbFile = $argv[0];
$method = 1;
$method = 'btreeSearch';
$algorithm = 'B-tree';
if ( isset($argv[1])
&& strtolower($argv[1]) == 'binary' ) {
$method = 2;
if ( isset($argv[1]) ) {
switch ( strtolower($argv[1]) ) {
case 'binary':
$algorithm = 'Binary';
$method = 'binarySearch';
break;
case 'memory':
$algorithm = 'Memory';
$method = 'memorySearch';
break;
}
}
require dirname(__FILE__) . '/Ip2Region.class.php';
@ -47,7 +54,7 @@ while ( true ) {
}
$s_time = getTime();
$data = $method==2 ? $ip2regionObj->binarySearch($line) : $ip2regionObj->btreeSearch($line);
$data = $ip2regionObj->{$method}($line);
$c_time = getTime() - $s_time;
printf("%s|%s in %.5f millseconds\n", $data['city_id'], $data['region'], $c_time);
}

File diff suppressed because it is too large Load Diff

Binary file not shown.