Merge remote-tracking branch 'origin/master'
This commit is contained in:
commit
91923d4f80
|
|
@ -27,3 +27,101 @@ patches/*.fix
|
|||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
*.class
|
||||
*.out
|
||||
*.o
|
||||
*.pyc
|
||||
*~
|
||||
*.log
|
||||
*.la
|
||||
*.iml
|
||||
META-INF/
|
||||
|
||||
# Binary Files #
|
||||
*.jar
|
||||
!dbMaker-*.jar
|
||||
|
||||
# ignore all xdb except the one in ./data/
|
||||
*.xdb
|
||||
!/data/*.xdb
|
||||
|
||||
# Package Files #
|
||||
.settings/
|
||||
.classpath
|
||||
.project
|
||||
|
||||
# vim swp file #
|
||||
.idea
|
||||
.vscode
|
||||
|
||||
# binding
|
||||
/v1.0//binding/java/classes/
|
||||
/v1.0//binding/java/doc/
|
||||
/v1.0//binding/java/target/
|
||||
/v1.0//binding/java/*.jar
|
||||
/binding/java/classes/
|
||||
/binding/java/doc/
|
||||
/binding/java/target/
|
||||
/binding/java/*.jar
|
||||
|
||||
# python
|
||||
/**/__pycache__
|
||||
|
||||
# clang
|
||||
/binding/c/xdb_searcher
|
||||
/binding/c/test_util
|
||||
/binding/c/cmake-build-debug
|
||||
|
||||
# lua/luc_c
|
||||
/binding/lua_c/cmake-build-debug
|
||||
|
||||
# golang
|
||||
/binding/golang/searcher
|
||||
/binding/golang/xdb_searcher
|
||||
/binding/golang/golang
|
||||
|
||||
# rust
|
||||
Cargo.lock
|
||||
target
|
||||
|
||||
|
||||
# VS ignore cases
|
||||
/**/*.sln
|
||||
/v1.0/binding/c#/**/.vs/
|
||||
/v1.0/binding/c#/**/packages
|
||||
/v1.0/binding/c#/**/bin
|
||||
/v1.0/binding/c#/**/obj
|
||||
/binding/c#/**/.vs/
|
||||
/binding/c#/**/packages
|
||||
/binding/c#/**/bin
|
||||
/binding/c#/**/obj
|
||||
|
||||
# Nodejs
|
||||
/v1.0/binding/nodejs/tests/unitTests/__snapshots__
|
||||
/v1.0/binding/nodejs/coverage
|
||||
/binding/nodejs/tests/unitTests/__snapshots__
|
||||
/binding/nodejs/coverage
|
||||
/binding/nodejs/node_modules
|
||||
/binding/nodejs/.nyc_output
|
||||
/binging/nodejs/package-lock.json
|
||||
|
||||
# Javascript
|
||||
/binding/javascript/tests/unitTests/__snapshots__
|
||||
/binding/javascript/coverage
|
||||
/binding/javascript/node_modules
|
||||
/binding/javascript/.nyc_output
|
||||
/binding/javascript/package-lock.json
|
||||
|
||||
# maker
|
||||
## golang
|
||||
/maker/golang/dbmaker
|
||||
/maker/golang/xdb_maker
|
||||
/maker/golang/golang
|
||||
|
||||
#erlang
|
||||
/binding/erlang/_build
|
||||
/binding/erlang/doc
|
||||
|
||||
#vscode
|
||||
.vscode
|
||||
build
|
||||
|
|
|
|||
|
|
@ -0,0 +1,225 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
==========================================================================
|
||||
The following license applies to the ip2region library
|
||||
--------------------------------------------------------------------------
|
||||
Copyright (c) 2015 Lionsoul<chenxin619315@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
|
@ -0,0 +1,113 @@
|
|||
# Ip2region 是什么
|
||||
|
||||
[ip2region](https://ip2region.net) - 是一个离线IP地址定位库和IP定位数据管理框架,同时支持`IPv4`和`IPv6`,10微秒级别的查询效率,提供了众多主流编程语言的 `xdb` 数据生成和查询客户端实现。
|
||||
|
||||
|
||||
|
||||
# Ip2region 特性
|
||||
|
||||
### 1、离线定位库
|
||||
|
||||
项目本身同时了提供了一份 IPv4(`data/ipv4_source.txt`) 和 IPv6(`data/ipv6_source.txt`) 的原始数据和对应的 xdb 文件(`data/ip2region_v4.xdb` 和 `data/ip2region_v6.xdb`) 用于实现精确到城市的的查询定位功能。
|
||||
|
||||
### 2、数据管理框架
|
||||
|
||||
`xdb` 支持亿级别的 IP 数据段行数,region 信息支持完全自定义,自带数据的 region 信息固定了格式为:`国家|省份|城市|ISP`,你可以在 region 中追加特定业务需求的数据,例如:GPS信息/国际统一地域信息编码/邮编等。也就是你完全可以使用 ip2region 来管理你自己的 IP 定位数据。
|
||||
|
||||
### 3、数据去重和压缩
|
||||
|
||||
`xdb` 格式生成程序会自动处理输入的原始数据,检查并且完成相连 IP 段的的合并以及相同地域信息的去重和压缩。
|
||||
|
||||
### 4、极速查询响应
|
||||
|
||||
即使是完全基于 `xdb` 文件的查询,单次查询响应时间在十微秒级别,可通过如下两种方式开启内存加速查询:
|
||||
|
||||
1. `vIndex` 索引缓存 :使用固定的 `512KiB` 的内存空间缓存 vector index 数据,减少一次 IO 磁盘操作,保持平均查询效率稳定在100微秒之内。
|
||||
2. `xdb` 整个文件缓存:将整个 `xdb` 文件全部加载到内存,内存占用等同于 `xdb` 文件大小,无磁盘 IO 操作,保持10微秒级别的查询效率。
|
||||
|
||||
### 5、统一的查询接口
|
||||
|
||||
`xdb` 提供了版本兼容的查询实现,一个统一的 API 可以同时提供对 IPv4 和 IPv6 数据的查询并且返回统一的数据。
|
||||
|
||||
# `xdb` 数据查询
|
||||
|
||||
API 介绍,使用文档和测试程序请参考对应 `searcher` 查询客户端下的 ReadMe 介绍,全部查询 binding 实现情况如下:
|
||||
| 编程语言 | 描述 | IPv4 支持 | IPv6 支持 | 贡献者 |
|
||||
|:---------------------------------|:-----------------------------------------------|:-------------------|:------------------ |:--------------------------------------------------|
|
||||
| [Golang](binding/golang) | golang xdb 查询客户端 | :white_check_mark: | :white_check_mark: | [Lion](https://github.com/lionsoul2014) |
|
||||
| [PHP](binding/php) | php xdb 查询客户端 | :white_check_mark: | :white_check_mark: | [Lion](https://github.com/lionsoul2014) |
|
||||
| [Java](binding/java) | java xdb 查询客户端 | :white_check_mark: | :white_check_mark: | [Lion](https://github.com/lionsoul2014) |
|
||||
| [C](binding/c) | POSIX C xdb 查询客户端 | :white_check_mark: | :white_check_mark: | [Lion](https://github.com/lionsoul2014) |
|
||||
| [Lua_c](binding/lua_c) | lua c 扩展 xdb 查询客户端 | :white_check_mark: | :white_check_mark: | [Lion](https://github.com/lionsoul2014) |
|
||||
| [Lua](binding/lua) | lua xdb 查询客户端 | :white_check_mark: | :white_check_mark: | [Lion](https://github.com/lionsoul2014) |
|
||||
| [Rust](binding/rust) | rust xdb 查询客户端 | :white_check_mark: | :white_check_mark: | [gongzhengyang](https://github.com/gongzhengyang) |
|
||||
| [Python](binding/python) | python xdb 查询客户端 | :white_check_mark: | :white_check_mark: | [Lion](https://github.com/lionsoul2014) |
|
||||
| [Javascript](binding/javascript) | javascript xdb 查询客户端 | :white_check_mark: | :white_check_mark: | [Lion](https://github.com/lionsoul2014) |
|
||||
| [Csharp](binding/csharp) | csharp xdb 查询客户端 | :white_check_mark: | :white_check_mark: | [Alen Lee](https://github.com/malus2077) & [ArgoZhang](https://github.com/ArgoZhang) |
|
||||
| [Erlang](binding/erlang) | erlang xdb 查询客户端 | :white_check_mark: | :x: | [malou](https://github.com/malou996) |
|
||||
| [Nginx](binding/nginx) | nginx 扩展 xdb 查询客户端 | :white_check_mark: | :x: | [Wu Jian Ping](https://github.com/wujjpp) |
|
||||
| [C++](binding/cpp) | C++ xdb 查询客户端 | :white_check_mark: | :white_check_mark: | [Yunbin Liu](https://github.com/liuyunbin) |
|
||||
|
||||
|
||||
以下工具链实现由社区开发者通过第三方仓库贡献:
|
||||
| 编程语言 | 描述 | 贡献者 |
|
||||
|:--------------------------------------------------------------- |:------------------------|:-----------------------------------------------|
|
||||
| [ip2region-composer](https://github.com/zoujingli/ip2region) | php composer 管理客户端 | [邹景立](https://github.com/zoujingli) |
|
||||
| [ip2region-ts](https://github.com/Steven-Qiang/ts-ip2region2) | node.js addon 管理客户端| [Steven Qiang](https://github.com/Steven-Qiang)|
|
||||
| [ruby-ip2region](https://github.com/jicheng1014/ruby-ip2region) | ruby xdb 查询客户端实现 | [jicheng1014](https://github.com/jicheng1014) |
|
||||
| [Ip2regionTool](https://github.com/orestonce/Ip2regionTool) | ip2region 数据转换工具 | [orestonce](https://github.com/orestonce) |
|
||||
|
||||
|
||||
# `xdb` 数据生成
|
||||
|
||||
API 介绍,使用文档和测试程序请参考如下 `maker` 生成程序下的 ReadMe 文档:
|
||||
|
||||
| 编程语言 | 描述 | IPv4 支持 | IPv6 支持 | 贡献者 |
|
||||
| :---------------------- | :------------------- |:-------------------|:-------------------| :------------------------------------------------------------------------------------------ |
|
||||
| [Golang](maker/golang) | golang xdb 生成程序 | :white_check_mark: | :white_check_mark: | [Lion](https://github.com/lionsoul2014) |
|
||||
| [Java](maker/java) | java xdb 生成程序 | :white_check_mark: | :white_check_mark: | [Lion](https://github.com/lionsoul2014) |
|
||||
| [Python](maker/python) | python xdb 生成程序 | :white_check_mark: | :x: | [leolin49](https://github.com/leolin49) |
|
||||
| [Csharp](maker/csharp) | csharp xdb 生成程序 | :white_check_mark: | :x: | [Alan Lee](https://github.com/malus2077) |
|
||||
| [Rust](maker/rust) | rust xdb 生成程序 | :white_check_mark: | :white_check_mark: | [KevinWang](https://github.com/KevinWL) & [gongzhengyang](https://github.com/gongzhengyang) |
|
||||
| [C++](maker/cpp) | C++ xdb 生成程序 | :white_check_mark: | :white_check_mark: | [Yunbin Liu](https://github.com/liuyunbin) |
|
||||
|
||||
|
||||
# `xdb` 数据更新
|
||||
|
||||
ip2region 项目的核心在于 <b>研究 IP 数据的存储和快速查询的设计和实现</b>, 项目自带的 `./data/ipv4_source.txt` 和 `./data/ipv6_source.txt` 原始数据不会再提供更新,对于数据精度和更新频率要求很高的使用场景建议到 [Ip2Region社区](https://ip2region.net/products/offline) 或者第三方购买商用离线数据,你可以使用如下几种方式来尝试自己更新数据:
|
||||
|
||||
### 手动编辑更新
|
||||
你可以基于 ip2region 自带的 `./data/ipv4_source.txt` 和 `./data/ipv6_source.txt` 原始 IP 数据用 ip2region 提供的编辑工具来自己修改,目前数据源有如下几种方式:
|
||||
1. ip2region 社区提供的数据(请参考地底部的公众号关注社区通知)
|
||||
2. ip2region Github/Gitee 中带有 `[数据源补充]` 标签的 Issue
|
||||
3. 其他自定义数据:例如客户提供的数据,或者通过 GPS 和 WIFI 定位得到的数据,或者来自其他平台的合法合规的数据
|
||||
|
||||
原始 IP 数据编辑工具使用方法请参考如下的 `maker` 生成程序下的 ReadMe 文档:
|
||||
| 编程语言 | 描述 | IPv4 支持 | IPv6 支持 | 贡献者 |
|
||||
|:------------------------------------|:-------------------------|:-------------------|:-------------------|:-------------------------------------------|
|
||||
| [Golang](maker/golang#xdb-数据编辑) | golang IP 原始数据编辑器 | :white_check_mark: | :white_check_mark: | [Lion](https://github.com/lionsoul2014) |
|
||||
| [Java](maker/java#xdb-数据编辑) | java IP 原始数据编辑器 | :white_check_mark: | :x: | [Lion](https://github.com/lionsoul2014) |
|
||||
| [C++](maker/cpp) | C++ IP 原始数据编辑器 | :white_check_mark: | :white_check_mark: | [Yunbin Liu](https://github.com/liuyunbin) |
|
||||
|
||||
|
||||
### 检测自动更新
|
||||
如果你想通过你自己的 API 或数据源来更新数据,你可以参考以下视频分享的 `基于检测算法` 的更新算法来自己编写一个更新程序:
|
||||
1. [数据更新实现视频分享 - part1](https://www.bilibili.com/video/BV1934y1E7Q5/)
|
||||
2. [数据更新实现视频分享 - part2](https://www.bilibili.com/video/BV1pF411j7Aw/)
|
||||
|
||||
# 官方社区
|
||||
Ip2Region 官方社区正式上线于 `2025/06/12` 日,一方面提供了稳定的 [商用离线数据](https://ip2region.net/products/offline) 服务,另一方面便于在核心代码外强化 IP 工具链和数据服务,例如 [使用文档](https://ip2region.net/doc/),[查询测试](https://ip2region.net/search/demo),数据纠错等,更多关于社区的信息和服务请访问 [Ip2Region 官方社区](https://ip2region.net/)。
|
||||
|
||||
# 相关备注
|
||||
|
||||
### 1、xdb 技术文档:
|
||||
1. xdb 数据结构分析:[“ip2region xdb-数据结构描述“](https://ip2region.net/doc/xdb/structure)
|
||||
2. xdb 查询过程分析:[“ip2region xdb-查询过程描述”](https://ip2region.net/doc/xdb/search)
|
||||
3. xdb 生成过程分析:[“ip2region xdb-生成过程描述”](https://ip2region.net/doc/xdb/generate)
|
||||
4. xdb 文件生成教程:[“ip2region xdb-文件生成教程”](https://ip2region.net/doc/data/xdb_make)
|
||||
5. xdb 并发安全查询:[“ip2region xdb-并发安全查询”](https://ip2region.net/doc/xdb/concurrent)
|
||||
6. xdb 数据更新方法:[“ip2region 数据更新和 xdb 数据编辑器的使用”](https://mp.weixin.qq.com/s/cZH5qIn4E5rQFy6N32RCzA)
|
||||
|
||||
### 3、技术信息博客
|
||||
1. 微信公众号 - lionsoul-org,作者活跃的技术分享渠道
|
||||
2. [Ip2Region 官方社区](https://ip2region.net)
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
all: xdb_searcher test_util
|
||||
|
||||
xdb_searcher: xdb_api.h xdb_util.c xdb_searcher.c main.c
|
||||
gcc -std=c99 -Wall -O2 -I./ xdb_util.c xdb_searcher.c main.c -o xdb_searcher
|
||||
|
||||
test_util: xdb_api.h xdb_util.c test_util.c
|
||||
gcc -std=c99 -Wall -O2 -I./ xdb_util.c test_util.c -o test_util
|
||||
|
||||
xdb_searcher.o: xdb_searcher.c
|
||||
gcc -std=c99 -Wall -c xdb_searcher.c
|
||||
|
||||
xdb_util.o: xdb_util.c
|
||||
gcc -std=c99 -Wall -c xdb_util.c
|
||||
|
||||
xdb_searcher_lib: xdb_util.o xdb_searcher.o
|
||||
mkdir -p build/lib
|
||||
mkdir -p build/include
|
||||
ar -rc build/lib/libxdb.a `find . -name *.o`
|
||||
cp xdb_api.h build/include
|
||||
|
||||
clean:
|
||||
find ./ -name \*.o | xargs rm -f
|
||||
find ./ -name test_util | xargs rm -f
|
||||
find ./ -name xdb_searcher | xargs rm -f
|
||||
rm -rf build
|
||||
|
||||
.PHONY: all clean xdb_searcher test_util
|
||||
|
|
@ -0,0 +1,353 @@
|
|||
# ip2region xdb c 查询客户端实现
|
||||
|
||||
|
||||
# 使用方式
|
||||
|
||||
### 关于查询 API
|
||||
查询 API 的原型如下:
|
||||
```c
|
||||
// 通过字符串 IP 进行查询
|
||||
int xdb_search_by_string(xdb_searcher_t *, const string_ip_t *, xdb_region_buffer_t *);
|
||||
// 通过 xdb_parse_ip 返回的二进制 IP 进行查询
|
||||
int xdb_search(xdb_searcher_t *, const bytes_ip_t *, int, xdb_region_buffer_t *);
|
||||
```
|
||||
如果查询失败将会返回非 `0` 的错误代码,如果查询成功 xdb_region_buffer_t 可以获取到字符串的 `region` 信息,如果输入的 IP 找不到相关的信息,xdb_region_buffer_t 将会得到一个空的字符串 `""`。
|
||||
|
||||
### 关于 IPv4 和 IPv6
|
||||
该 xdb 查询客户端实现同时支持对 IPv4 和 IPv6 的查询,使用方式如下:
|
||||
```c
|
||||
#include "xdb_api.h";
|
||||
|
||||
// 如果是 IPv4: 设置 xdb 路径为 v4 的 xdb 文件,IP版本指定为 IPv4
|
||||
const char *db_path = "../../data/ip2region_v4.xdb"; // 或者你的 ipv4 xdb 的路径
|
||||
xdb_version_t *version = XDB_IPv4;
|
||||
|
||||
// 如果是 IPv6: 设置 xdb 路径为 v6 的 xdb 文件,IP版本指定为 IPv6
|
||||
const char *db_path = "../../data/ip2region_v6.xdb"; // 或者你的 ipv6 xdb 路径
|
||||
xdb_version_t *version = XDB_IPv6;
|
||||
|
||||
// db_path 指定的 xdb 的 IP 版本必须和 version 指定的一致,不然查询执行的时候会报错
|
||||
// 备注:以下演示直接使用 db_path 和 version 变量
|
||||
```
|
||||
|
||||
### XDB 文件验证
|
||||
建议您主动去验证 xdb 文件的适用性,因为后期的一些新功能可能会导致目前的 Searcher 版本无法适用你使用的 xdb 文件,验证可以避免运行过程中的一些不可预测的错误。 你不需要每次都去验证,例如在服务启动的时候,或者手动调用命令验证确认版本匹配即可,不要在每次创建的 Searcher 的时候运行验证,这样会影响查询的响应速度,尤其是高并发的使用场景。
|
||||
```c
|
||||
#include "xdb_api.h";
|
||||
|
||||
int errcode = xdb_verify(db_path);
|
||||
if ($err != 0) {
|
||||
// 适用性验证失败!!!
|
||||
// 当前查询客户端实现不适用于 db_path 指定的 xdb 文件的查询.
|
||||
// 应该停止启动服务,使用合适的 xdb 文件或者升级到适合 db_path 的 Searcher 实现。
|
||||
printf("failed to verify xdb file `%s`, errcode: %d\n", db_path, errcode);
|
||||
return;
|
||||
}
|
||||
|
||||
// 验证通过,当前使用的 Searcher 可以安全的用于对 dbPath 指向的 xdb 的查询操作
|
||||
```
|
||||
|
||||
### 完全基于文件的查询
|
||||
|
||||
```c
|
||||
#include <stdio.h>
|
||||
#include "xdb_api.h"
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
xdb_searcher_t searcher;
|
||||
char region_buffer[512] = {'\0'};
|
||||
xdb_region_buffer_t region;
|
||||
|
||||
// 使用栈空间的 region_buffer 初始化 region_buffer_t
|
||||
int err = xdb_region_buffer_init(®ion, region_buffer, sizeof(region_buffer));
|
||||
if (err != 0) {
|
||||
printf("failed to init the region buffer with errcode=%d\n", err);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 在服务启动的时候初始化 winsock,不需要重复调用,只需要在 windows 系统下调用
|
||||
err = xdb_init_winsock();
|
||||
if (err != 0) {
|
||||
printf("failed to init the winsock with errno=%d\n", err);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 1、从 db_path 初始化 xdb 查询对象.
|
||||
// @Note: 使用顶部描述的 db_path 和 version 来创建 searcher
|
||||
err = xdb_new_with_file_only(version, &searcher, db_path);
|
||||
if (err != 0) {
|
||||
printf("failed to create xdb searcher from `%s` with errno=%d\n", db_path, err);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 2、调用 search API 查询,IPv4 和 IPv6 都支持.
|
||||
const char *ip_string = "1.2.3.4";
|
||||
// ip_string = "2001:4:112:ffff:ffff:ffff:ffff:ffff"; // IPv6
|
||||
|
||||
long cost_time = 0, s_time = xdb_now();
|
||||
err = xdb_search_by_string(&searcher, ip_string, ®ion);
|
||||
cost_time = (int) (xdb_now() - s_time);
|
||||
if (err != 0) {
|
||||
printf("failed search(%s) with errno=%d\n", ip_string, err);
|
||||
} else {
|
||||
printf("{region: %s, took: %d μs}", region.value, cost_time);
|
||||
}
|
||||
|
||||
// 清理 region 信息的内存资源,每次 search 之后都得调用
|
||||
xdb_region_buffer_free(®ion);
|
||||
|
||||
// 备注:并发使用,每一个线程需要单独定义并且初始化一个 searcher 查询对象。
|
||||
|
||||
// 3、关闭 xdb 查询器
|
||||
xdb_close(&searcher);
|
||||
xdb_clean_winsock(); // windows 下调用
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
### 缓存 `VectorIndex` 索引
|
||||
|
||||
我们可以提前从 xdb 文件中加载出来 VectorIndex 数据,然后全局缓存,每次创建 Searcher 对象的时候使用全局的 VectorIndex 缓存可以减少一次固定的 IO 操作,从而加速查询,减少 IO 压力。
|
||||
```c
|
||||
#include <stdio.h>
|
||||
#include "xdb_api.h"
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
xdb_vector_index_t *v_index;
|
||||
xdb_searcher_t searcher;
|
||||
xdb_region_buffer_t region;
|
||||
|
||||
// 使用 NULL 初始化 region_buffer,让其自动管理内存的分配
|
||||
int err = xdb_region_buffer_init(®ion, NULL, 0);
|
||||
if (err != 0) {
|
||||
printf("failed to init the region buffer with errcode=%d\n", err);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 在服务启动的时候初始化 winsock,不需要重复调用,只需要在 windows 系统下调用
|
||||
err = xdb_init_winsock();
|
||||
if (err != 0) {
|
||||
printf("failed to init the winsock with errno=%d\n", err);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 1、从顶部描述的 db_path 加载 VectorIndex 索引。
|
||||
// 得到 v_index 做成全局缓存,便于后续反复使用。
|
||||
// 注意:v_index 不需要每次都加载,建议在服务启动的时候加载一次,然后做成全局资源。
|
||||
v_index = xdb_load_vector_index_from_file(db_path);
|
||||
if (v_index == NULL) {
|
||||
printf("failed to load vector index from `%s`\n", db_path);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 2、使用全局的 VectorIndex 变量创建带 VectorIndex 缓存的 xdb 查询对象.
|
||||
// @Note: 使用顶部描述的 db_path 和 version 来创建 searcher
|
||||
err = xdb_new_with_vector_index(version, &searcher, db_path, v_index);
|
||||
if (err != 0) {
|
||||
printf("failed to create vector index cached searcher with errcode=%d\n", err);
|
||||
return 2;
|
||||
}
|
||||
|
||||
|
||||
// 3、调用 search API 查询,IPv4 和 IPv6 都支持
|
||||
const char *ip_string = "1.2.3.4";
|
||||
// ip_string = "2001:4:112:ffff:ffff:ffff:ffff:ffff"; // IPv6
|
||||
|
||||
long cost_time = 0, s_time = xdb_now();
|
||||
err = xdb_search_by_string(&searcher, ip_string, ®ion);
|
||||
cost_time = (int) (xdb_now() - s_time);
|
||||
if (err != 0) {
|
||||
printf("failed search(%s) with errno=%d\n", ip_string, err);
|
||||
} else {
|
||||
printf("{region: %s, took: %d μs}", region.value, cost_time);
|
||||
}
|
||||
|
||||
// 清理 region 信息的内存资源,每次 search 之后都得调用
|
||||
xdb_region_buffer_free(®ion);
|
||||
|
||||
|
||||
// 备注:并发使用,每一个线程需要单独定义并且初始化一个 searcher 查询对象。
|
||||
|
||||
// 4、关闭 xdb 查询器,如果是要关闭服务,也需要释放 v_index 的内存。
|
||||
xdb_close(&searcher);
|
||||
xdb_close_vector_index(v_index);
|
||||
xdb_clean_winsock();
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
### 缓存整个 `xdb` 数据
|
||||
|
||||
我们也可以预先加载整个 xdb 文件到内存,然后基于这个数据创建查询对象来实现完全基于内存的查询,类似之前的 memory search。
|
||||
```c
|
||||
#include <stdio.h>
|
||||
#include "xdb_api.h"
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
xdb_content_t *c_buffer;
|
||||
xdb_searcher_t searcher;
|
||||
xdb_region_buffer_t region;
|
||||
|
||||
// 使用 NULL 初始化 region_buffer,让其自动管理内存的分配
|
||||
int err = xdb_region_buffer_init(®ion, NULL, 0);
|
||||
if (err != 0) {
|
||||
printf("failed to init the region buffer with errcode=%d\n", err);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// 在服务启动的时候初始化 winsock,不需要重复调用,只需要在 windows 系统下调用
|
||||
err = xdb_init_winsock();
|
||||
if (err != 0) {
|
||||
printf("failed to init the winsock with errno=%d\n", err);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 1、从 顶部描述的 db_path 加载整个 xdb 的数据。
|
||||
c_buffer = xdb_load_content_from_file(db_path);
|
||||
if (v_index == NULL) {
|
||||
printf("failed to load xdb content from `%s`\n", db_path);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 2、使用全局的 c_buffer 变量创建一个完全基于内存的 xdb 查询对象.
|
||||
// @Note: 使用顶部描述的 version 来创建 searcher.
|
||||
err = xdb_new_with_buffer(version, &searcher, c_buffer);
|
||||
if (err != 0) {
|
||||
printf("failed to create content cached searcher with errcode=%d\n", err);
|
||||
return 2;
|
||||
}
|
||||
|
||||
// 3、调用 search API 查询,IPv4 和 IPv6 都支持
|
||||
const char *ip_string = "1.2.3.4";
|
||||
// ip_string = "2001:4:112:ffff:ffff:ffff:ffff:ffff"; // IPv6
|
||||
|
||||
long cost_time = 0, s_time = xdb_now();
|
||||
err = xdb_search_by_string(&searcher, ip_string, ®ion);
|
||||
cost_time = (int) (xdb_now() - s_time);
|
||||
if (err != 0) {
|
||||
printf("failed search(%s) with errno=%d\n", ip_string, err);
|
||||
} else {
|
||||
printf("{region: %s, took: %d μs}", region.value, cost_time);
|
||||
}
|
||||
|
||||
// 清理 region 信息的内存资源,每次 search 之后都得调用
|
||||
xdb_region_buffer_free(®ion);
|
||||
|
||||
|
||||
// 备注:并发使用,使用这种方式创建的 xdb 查询对象可以安全用于并发。
|
||||
// 建议在服务启动的时候创建好,然后一直安全并发使用,直到服务关闭。
|
||||
|
||||
// 4、关闭 xdb 查询器,关闭服务的时候需要释放 c_buffer 的内存。
|
||||
xdb_close(&searcher);
|
||||
xdb_close_content(c_buffer);
|
||||
xdb_clean_winsock();
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
### 关于定位信息的存储
|
||||
在旧版本的实现中,search相关的函数都是依靠指定一个 `region_buffer` 内存来用于存储地域信息,这种方式还是有很大的局限性。
|
||||
新的实现提供了一个 `xdb_region_buffer_t` 对象来管理这些内存的分配,你依然可以指定一个固定的 `region_buffer` 来创建 region 的内存管理,这个情况适合当你的地域信息的最大长度是可知的,这种方式可以减少运行过程中内存的碎片堆积。如果地域信息的长度不确定或者你的程序不适合提前分配一块内存来管理,你可以通过指定 NULL 的方式来初始化 `xdb_region_buffer_t`,这样对象会自动管理内存的分配,也适合任意长度的地域信息的存储,不过这种方式在长期的运行过程中肯定会增加内存碎片的堆积。
|
||||
```c
|
||||
// 1, 通过指定一块内存来创建 region_buffer
|
||||
char buffer[512];
|
||||
xdb_region_buffer_t region;
|
||||
int err = xdb_region_buffer_init(®ion, buffer, sizeof(buffer));
|
||||
if (err != 0) {
|
||||
// 初始化失败
|
||||
printf("failed to init region buffer width errcode=%d", err);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2,通过指定 NULL 来创建 region_buffer,让其自动按需分配内存
|
||||
xdb_region_buffer_t region;
|
||||
int err = xdb_region_buffer_init(®ion, NULL, 0);
|
||||
if (err != 0) {
|
||||
// 初始化失败
|
||||
printf("failed to init region buffer width errcode=%d", err);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// 备注:在每次调用 search 完成 IP 定位信息的查询后,你需要手动调用函数来释放内存 .
|
||||
// search 函数使用未经清理的 region 信息会报错。
|
||||
xdb_region_buffer_free(®ion);
|
||||
```
|
||||
|
||||
|
||||
# 测试程序编译
|
||||
|
||||
通过如下方式编译得到 xdb_searcher 可执行程序:
|
||||
```bash
|
||||
# cd 到 c binding 根目录
|
||||
➜ c git:(master) ✗ make
|
||||
gcc -std=c99 -Wall -O2 -I./ xdb_util.c xdb_searcher.c main.c -o xdb_searcher
|
||||
gcc -std=c99 -Wall -O2 -I./ xdb_util.c test_util.c -o test_util
|
||||
```
|
||||
|
||||
|
||||
# 查询测试
|
||||
|
||||
通过 `xdb_searcher search` 命令来测试对 xdb 的查询:
|
||||
```bash
|
||||
➜ c git:(fr_c_ipv6) ✗ ./xdb_searcher search
|
||||
./xdb_searcher search [command options]
|
||||
options:
|
||||
--db string ip2region binary xdb file path
|
||||
--cache-policy string cache policy: file/vectorIndex/content
|
||||
```
|
||||
|
||||
例如:使用默认的 data/ip2region_v4.xdb 进行 IPv4 查询测试:
|
||||
```bash
|
||||
➜ c git:(fr_c_ipv6) ✗ ./xdb_searcher search --db=../../data/ip2region_v4.xdb
|
||||
ip2region xdb searcher test program
|
||||
source xdb: ../../data/ip2region_v4.xdb (IPv4, vectorIndex)
|
||||
type 'quit' to exit
|
||||
ip2region>> 120.229.45.2
|
||||
{region: 中国|广东省|深圳市|移动, io_count: 3, took: 29 μs}
|
||||
```
|
||||
|
||||
例如:使用默认的 data/ip2region_v6.xdb 进行 IPv6 查询测试:
|
||||
```bash
|
||||
➜ c git:(fr_c_ipv6) ✗ ./xdb_searcher search --db=../../data/ip2region_v6.xdb
|
||||
ip2region xdb searcher test program
|
||||
source xdb: ../../data/ip2region_v6.xdb (IPv6, vectorIndex)
|
||||
type 'quit' to exit
|
||||
ip2region>> ::
|
||||
{region: , io_count: 1, took: 38 μs}
|
||||
ip2region>> 2604:bc80:8001:11a4:ffff:ffff:ffff:ffff
|
||||
{region: 美国|特拉华州|刘易斯|数据中心, io_count: 14, took: 77 μs}
|
||||
ip2region>> 240e:3b7:3272:d8d0:db09:c067:8d59:539e
|
||||
{region: 中国|广东省|深圳市|家庭宽带, io_count: 8, took: 46 μs}
|
||||
```
|
||||
|
||||
输入 ip 即可进行查询,输入 quit 即可退出测试程序。也可以分别设置 `cache-policy` 为 file/vectorIndex/content 来测试三种不同的缓存实现的效率。
|
||||
|
||||
|
||||
|
||||
# bench 测试
|
||||
|
||||
通过 `xdb_searcher bench` 命令来进行 bench 测试,一方面确保查询程序和 `xdb` 文件没有错误,另一方面可以通过大量的查询得到平均的查询性能:
|
||||
```bash
|
||||
➜ c git:(fr_c_ipv6) ✗ ./xdb_searcher bench
|
||||
./xdb_searcher bench [command options]
|
||||
options:
|
||||
--db string ip2region binary xdb file path
|
||||
--src string source ip text file path
|
||||
--cache-policy string cache policy: file/vectorIndex/content
|
||||
```
|
||||
|
||||
例如:通过默认的 data/ip2region_v4.xdb 和 data/ipv4_source.txt 来进行 IPv4 的 bench 测试:
|
||||
```bash
|
||||
➜ c git:(fr_c_ipv6) ✗ ./xdb_searcher bench --db=../../data/ip2region_v4.xdb --src=../../data/ipv4_source.txt
|
||||
Bench finished, {cache_policy: vectorIndex, total: 1367686, took: 7.640s, cost: 5 μs/op}
|
||||
```
|
||||
|
||||
例如:通过默认的 data/ip2region_v6.xdb 和 data/ipv6_source.txt 来进行 IPv6 的 bench 测试:
|
||||
```bash
|
||||
➜ c git:(fr_c_ipv6) ✗ ./xdb_searcher bench --db=../../data/ip2region_v6.xdb --src=../../data/ipv6_source.txt
|
||||
Bench finished, {cache_policy: vectorIndex, total: 34159862, took: 857.750s, cost: 24 μs/op}
|
||||
```
|
||||
|
||||
可以设置 `cache-policy` 参数来分别测试 file/vectorIndex/content 不同缓存实现机制的效率。 @Note:请注意 bench 使用的 src 文件需要是生成对应的 xdb 文件相同的源文件。
|
||||
|
|
@ -0,0 +1,428 @@
|
|||
// Copyright 2022 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// ---
|
||||
// @Author Lion <chenxin619315@gmail.com>
|
||||
// @Date 2022/06/28
|
||||
|
||||
#include "stdio.h"
|
||||
#include "xdb_api.h"
|
||||
|
||||
struct searcher_test_entry {
|
||||
xdb_searcher_t searcher;
|
||||
xdb_vector_index_t *v_index;
|
||||
xdb_content_t *c_buffer;
|
||||
|
||||
// xdb region buffer
|
||||
// char region_buffer[256];
|
||||
xdb_region_buffer_t region;
|
||||
};
|
||||
typedef struct searcher_test_entry searcher_test_t;
|
||||
|
||||
int init_searcher_test(searcher_test_t *test, char *db_path, char *cache_policy) {
|
||||
int err, errcode = 0;
|
||||
FILE *handle = fopen(db_path, "rb");
|
||||
if (handle == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// auto detect the version from the xdb header
|
||||
xdb_header_t *header = xdb_load_header(handle);
|
||||
if (header == NULL) {
|
||||
printf("failed to load header from `%s`\n", db_path);
|
||||
errcode = 1;
|
||||
goto defer;
|
||||
}
|
||||
|
||||
// verify the current xdb
|
||||
err = xdb_verify_from_header(handle, header);
|
||||
if (err != 0) {
|
||||
printf("failed to verify xdb file `%s` with errno=%d\n", db_path, err);
|
||||
errcode = 2;
|
||||
goto defer;
|
||||
}
|
||||
|
||||
xdb_version_t *version = xdb_version_from_header(header);
|
||||
if (version == NULL) {
|
||||
printf("failed to load version from header\n");
|
||||
errcode = 3;
|
||||
goto defer;
|
||||
}
|
||||
|
||||
test->v_index = NULL;
|
||||
test->c_buffer = NULL;
|
||||
|
||||
if (strcmp(cache_policy, "file") == 0) {
|
||||
err = xdb_new_with_file_only(version, &test->searcher, db_path);
|
||||
if (err != 0) {
|
||||
printf("failed to create searcher with errcode=%d\n", err);
|
||||
errcode = 4;
|
||||
goto defer;
|
||||
}
|
||||
} else if (strcmp(cache_policy, "vectorIndex") == 0) {
|
||||
test->v_index = xdb_load_vector_index_from_file(db_path);
|
||||
if (test->v_index == NULL) {
|
||||
printf("failed to load vector index from `%s`\n", db_path);
|
||||
errcode = 4;
|
||||
goto defer;
|
||||
}
|
||||
|
||||
err = xdb_new_with_vector_index(version, &test->searcher, db_path, test->v_index);
|
||||
if (err != 0) {
|
||||
printf("failed to create vector index cached searcher with errcode=%d\n", err);
|
||||
errcode = 5;
|
||||
goto defer;
|
||||
}
|
||||
} else if (strcmp(cache_policy, "content") == 0) {
|
||||
test->c_buffer = xdb_load_content_from_file(db_path);
|
||||
if (test->c_buffer == NULL) {
|
||||
printf("failed to load xdb content from `%s`\n", db_path);
|
||||
errcode = 4;
|
||||
goto defer;
|
||||
}
|
||||
|
||||
err = xdb_new_with_buffer(version, &test->searcher, test->c_buffer);
|
||||
if (err != 0) {
|
||||
printf("failed to create content cached searcher with errcode=%d\n", err);
|
||||
errcode = 5;
|
||||
goto defer;
|
||||
}
|
||||
} else {
|
||||
printf("invalid cache policy `%s`, options: file/vectorIndex/content\n", cache_policy);
|
||||
errcode = 6;
|
||||
goto defer;
|
||||
}
|
||||
|
||||
// init the region buffer
|
||||
// err = xdb_region_buffer_init(&test->region, test->region_buffer, sizeof(test->region_buffer));
|
||||
err = xdb_region_buffer_init(&test->region, NULL, 0);
|
||||
if (err != 0) {
|
||||
printf("failed to init the region buffer with err=%d\n", err);
|
||||
errcode = 7;
|
||||
goto defer;
|
||||
}
|
||||
|
||||
defer:
|
||||
if (header != NULL) {
|
||||
xdb_free_header(header);
|
||||
}
|
||||
|
||||
if (handle != NULL) {
|
||||
fclose(handle);
|
||||
}
|
||||
|
||||
return errcode;
|
||||
}
|
||||
|
||||
void destroy_searcher_test(searcher_test_t *test) {
|
||||
xdb_close(&test->searcher);
|
||||
|
||||
// check and free the vector index
|
||||
if (test->v_index != NULL) {
|
||||
xdb_free_vector_index(test->v_index);
|
||||
test->v_index = NULL;
|
||||
}
|
||||
|
||||
// check and free the content buffer
|
||||
if (test->c_buffer != NULL) {
|
||||
xdb_free_content(test->c_buffer);
|
||||
test->c_buffer = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
//read a line from a command line.
|
||||
static char *get_line(FILE *fp, char *__dst) {
|
||||
register int c;
|
||||
register char *cs;
|
||||
|
||||
cs = __dst;
|
||||
while ( ( c = getc( fp ) ) != EOF ) {
|
||||
if ( c == '\n' ) break;
|
||||
*cs++ = c;
|
||||
}
|
||||
*cs = '\0';
|
||||
|
||||
return ( c == EOF && cs == __dst ) ? NULL : __dst;
|
||||
}
|
||||
|
||||
void print_help(char *argv[]) {
|
||||
printf("ip2region xdb searcher\n");
|
||||
printf("%s [command] [command options]\n", argv[0]);
|
||||
printf("Command: \n");
|
||||
printf(" search search input test\n");
|
||||
printf(" bench search bench test\n");
|
||||
}
|
||||
|
||||
void test_search(int argc, char *argv[]) {
|
||||
int i, n, err;
|
||||
|
||||
// for args parse
|
||||
char *r, key[33] = {'\0'}, val[256] = {'\0'};
|
||||
char db_file[256] = {'\0'}, cache_policy[16] = {"vectorIndex"};
|
||||
|
||||
// for search
|
||||
long s_time, c_time;
|
||||
char line[512] = {'\0'};
|
||||
|
||||
// ip parse
|
||||
xdb_version_t *version;
|
||||
bytes_ip_t ip_bytes[16] = {'\0'};
|
||||
|
||||
searcher_test_t test;
|
||||
|
||||
for (i = 2; i < argc; i++) {
|
||||
r = argv[i];
|
||||
if (strlen(r) < 5) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (r[0] != '-' || r[1] != '-') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strchr(r, '=') == NULL) {
|
||||
printf("missing = for args pair '%s'\n", r);
|
||||
return;
|
||||
}
|
||||
|
||||
n = sscanf(r+2, "%32[^=]=%255[^\n]", key, val);
|
||||
if (n != 2) {
|
||||
printf("invalid option flag `%s`\n", r);
|
||||
return;
|
||||
}
|
||||
|
||||
// printf("key=%s, val=%s\n", key, val);
|
||||
if (strcmp(key, "db") == 0) {
|
||||
snprintf(db_file, sizeof(db_file), "%s", val);
|
||||
} else if (strcmp(key, "cache-policy") == 0) {
|
||||
memcpy(cache_policy, val, sizeof(cache_policy) - 1);
|
||||
// snprintf(cache_policy, sizeof(cache_policy), "%s", val);
|
||||
} else {
|
||||
printf("undefined option `%s`\n", r);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (strlen(db_file) < 1) {
|
||||
printf("%s search [command options]\n", argv[0]);
|
||||
printf("options:\n");
|
||||
printf(" --db string ip2region binary xdb file path\n");
|
||||
printf(" --cache-policy string cache policy: file/vectorIndex/content\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// init the win sock
|
||||
err = xdb_init_winsock();
|
||||
if (err != 0) {
|
||||
printf("failed to init the winsock with errno=%d\n", err);
|
||||
return;
|
||||
}
|
||||
|
||||
// printf("db_file=%s, cache_policy=%s\n", db_file, cache_policy);
|
||||
err = init_searcher_test(&test, db_file, cache_policy);
|
||||
if (err != 0) {
|
||||
// init program will print the error reasons;
|
||||
return;
|
||||
}
|
||||
|
||||
printf("ip2region xdb searcher test program\n"
|
||||
"source xdb: %s (%s, %s)\n"
|
||||
"type 'quit' to exit\n", db_file, xdb_get_version(&test.searcher)->name, cache_policy);
|
||||
while ( 1 ) {
|
||||
printf("ip2region>> ");
|
||||
get_line(stdin, line);
|
||||
if ( strlen(line) < 2 ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strcmp(line, "quit") == 0 ) {
|
||||
break;
|
||||
}
|
||||
|
||||
version = xdb_parse_ip(line, ip_bytes, sizeof(ip_bytes));
|
||||
if (version == NULL) {
|
||||
printf("invalid ip address `%s`\n", line);
|
||||
continue;
|
||||
}
|
||||
|
||||
s_time = xdb_now();
|
||||
err = xdb_search(&test.searcher, ip_bytes, version->bytes, &test.region);
|
||||
if (err != 0) {
|
||||
printf("{err: %d, io_count: %d}\n", err, xdb_get_io_count(&test.searcher));
|
||||
} else {
|
||||
c_time = xdb_now() - s_time;
|
||||
printf("{region: %s, io_count: %d, took: %ld μs}\n", test.region.value, xdb_get_io_count(&test.searcher), c_time);
|
||||
}
|
||||
|
||||
// free the region
|
||||
xdb_region_buffer_free(&test.region);
|
||||
}
|
||||
|
||||
destroy_searcher_test(&test);
|
||||
xdb_clean_winsock();
|
||||
printf("searcher test program exited, thanks for trying\n");
|
||||
}
|
||||
|
||||
void test_bench(int argc, char *argv[]) {
|
||||
int i, n, err;
|
||||
char *r, key[33] = {'\0'}, val[256] = {'\0'};
|
||||
char db_file[256] = {'\0'}, src_file[256] = {'\0'}, cache_policy[16] = {"vectorIndex"};
|
||||
|
||||
FILE *handle;
|
||||
char line[1024] = {'\0'}, sip_str[INET6_ADDRSTRLEN+1] = {'\0'}, eip_str[INET6_ADDRSTRLEN+1] = {'\0'};
|
||||
char src_region[512] = {'\0'};
|
||||
int count = 0, took;
|
||||
long s_time, t_time, c_time = 0;
|
||||
|
||||
// ip parse
|
||||
xdb_version_t *s_version, *e_version;
|
||||
bytes_ip_t sip_bytes[16] = {'\0'}, eip_bytes[16] = {'\0'};
|
||||
string_ip_t ip_string[INET6_ADDRSTRLEN] = {'\0'};
|
||||
bytes_ip_t *ip_list[2];
|
||||
|
||||
searcher_test_t test;
|
||||
for (i = 2; i < argc; i++) {
|
||||
r = argv[i];
|
||||
if (strlen(r) < 5) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (r[0] != '-' || r[1] != '-') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strchr(r, '=') == NULL) {
|
||||
printf("missing = for args pair '%s'\n", r);
|
||||
return;
|
||||
}
|
||||
|
||||
n = sscanf(r+2, "%32[^=]=%255[^\n]", key, val);
|
||||
if (n != 2) {
|
||||
printf("invalid option flag `%s`\n", r);
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp(key, "db") == 0) {
|
||||
snprintf(db_file, sizeof(db_file), "%s", val);
|
||||
} else if (strcmp(key, "src") == 0) {
|
||||
snprintf(src_file, sizeof(src_file), "%s", val);
|
||||
} else if (strcmp(key, "cache-policy") == 0) {
|
||||
memcpy(cache_policy, val, sizeof(cache_policy) - 1);
|
||||
} else {
|
||||
printf("undefined option `%s`\n", r);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (strlen(db_file) < 1 || strlen(src_file) < 1) {
|
||||
printf("%s bench [command options]\n", argv[0]);
|
||||
printf("options:\n");
|
||||
printf(" --db string ip2region binary xdb file path\n");
|
||||
printf(" --src string source ip text file path\n");
|
||||
printf(" --cache-policy string cache policy: file/vectorIndex/content\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// init the win sock
|
||||
err = xdb_init_winsock();
|
||||
if (err != 0) {
|
||||
printf("failed to init the winsock with errno=%d\n", err);
|
||||
return;
|
||||
}
|
||||
|
||||
// printf("db_file=%s, src_file=%s, cache_policy=%s\n", db_file, src_file, cache_policy);
|
||||
s_time = xdb_now();
|
||||
err = init_searcher_test(&test, db_file, cache_policy);
|
||||
if (err != 0) {
|
||||
// the init function will print the details;
|
||||
return;
|
||||
}
|
||||
|
||||
// open the source file
|
||||
handle = fopen(src_file, "r");
|
||||
if (handle == NULL) {
|
||||
printf("failed to open source text file `%s`\n", src_file);
|
||||
return;
|
||||
}
|
||||
|
||||
while(fgets(line, sizeof(line), handle) != NULL) {
|
||||
n = sscanf(line, "%46[^|]|%46[^|]|%511[^\n]", sip_str, eip_str, src_region);
|
||||
if (n != 3) {
|
||||
printf("invalid ip segment line `%s`\n", line);
|
||||
return;
|
||||
}
|
||||
|
||||
s_version = xdb_parse_ip(sip_str, sip_bytes, sizeof(sip_bytes));
|
||||
if (s_version == NULL) {
|
||||
printf("invalid start ip `%s`\n", sip_str);
|
||||
return;
|
||||
}
|
||||
|
||||
e_version = xdb_parse_ip(eip_str, eip_bytes, sizeof(eip_bytes));
|
||||
if (e_version == NULL) {
|
||||
printf("invalid end ip `%s`\n", sip_str);
|
||||
return;
|
||||
}
|
||||
|
||||
if (s_version->id != e_version->id) {
|
||||
printf("start ip and end ip version not match for line `%s`\n", line);
|
||||
return;
|
||||
}
|
||||
|
||||
if (xdb_ip_sub_compare(sip_bytes, s_version->bytes, (string_ip_t *) eip_bytes, 0) > 0) {
|
||||
printf("start ip(%s) should not be greater than end ip(%s)\n", sip_str, eip_str);
|
||||
return;
|
||||
}
|
||||
|
||||
ip_list[0] = sip_bytes;
|
||||
ip_list[1] = eip_bytes;
|
||||
for (i = 0; i < 2; i++) {
|
||||
t_time = xdb_now();
|
||||
err = xdb_search(&test.searcher, ip_list[i], s_version->bytes, &test.region);
|
||||
c_time += xdb_now() - t_time;
|
||||
if (err != 0) {
|
||||
xdb_ip_to_string(ip_list[i], s_version->bytes, ip_string, sizeof(ip_string));
|
||||
printf("failed to search ip `%s` with errno=%d\n", ip_string, err);
|
||||
return;
|
||||
}
|
||||
|
||||
// check the region info
|
||||
if (strcmp(test.region.value, src_region) != 0) {
|
||||
xdb_ip_to_string(ip_list[i], s_version->bytes, ip_string, sizeof(ip_string));
|
||||
printf("failed to search(%s) with (%s != %s)\n", ip_string, test.region.value, src_region);
|
||||
return;
|
||||
}
|
||||
|
||||
// free the region buffer
|
||||
xdb_region_buffer_free(&test.region);
|
||||
|
||||
count++;
|
||||
}
|
||||
};
|
||||
|
||||
took = xdb_now() - s_time;
|
||||
destroy_searcher_test(&test);
|
||||
xdb_clean_winsock();
|
||||
fclose(handle);
|
||||
printf("Bench finished, {cache_policy: %s, total: %d, took: %.3fs, cost: %d μs/op}\n",
|
||||
cache_policy, count, took/1e6, count == 0 ? 0 : (int)(c_time/count));
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
if (argc < 2) {
|
||||
print_help(argv);
|
||||
return 0;
|
||||
}
|
||||
|
||||
char *opt = argv[1];
|
||||
if (strcmp(opt, "search") == 0) {
|
||||
test_search(argc, argv);
|
||||
} else if (strcmp(opt, "bench") == 0) {
|
||||
test_bench(argc, argv);
|
||||
} else {
|
||||
print_help(argv);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,213 @@
|
|||
// Copyright 2022 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// ---
|
||||
// @Author Lion <chenxin619315@gmail.com>
|
||||
// @Date 2022/06/27
|
||||
|
||||
#include "stdio.h"
|
||||
#include "xdb_api.h"
|
||||
|
||||
typedef void (* test_func_ptr) ();
|
||||
struct test_func_entry {
|
||||
char *name;
|
||||
test_func_ptr func;
|
||||
};
|
||||
typedef struct test_func_entry test_func_t;
|
||||
|
||||
void test_load_header() {
|
||||
xdb_header_t *header = xdb_load_header_from_file("../../data/ip2region_v4.xdb");
|
||||
if (header == NULL) {
|
||||
printf("failed to load header");
|
||||
} else {
|
||||
printf("header loaded: {\n"
|
||||
" version: %d, \n"
|
||||
" index_policy: %d, \n"
|
||||
" created_at: %u, \n"
|
||||
" start_index_ptr: %d, \n"
|
||||
" end_index_ptr: %d\n"
|
||||
" ip_version: %d\n"
|
||||
" runtime_ptr_bytes: %d\n"
|
||||
" length: %d\n"
|
||||
"}\n",
|
||||
header->version, header->index_policy, header->created_at,
|
||||
header->start_index_ptr, header->end_index_ptr,
|
||||
header->ip_version, header->runtime_ptr_bytes, header->length
|
||||
);
|
||||
}
|
||||
|
||||
xdb_free_header(header);
|
||||
}
|
||||
|
||||
void test_load_vector_index() {
|
||||
xdb_vector_index_t *v_index = xdb_load_vector_index_from_file("../../data/ip2region_v4.xdb");
|
||||
if (v_index == NULL) {
|
||||
printf("failed to load vector index from file\n");
|
||||
} else {
|
||||
printf("vector index loaded from file, length=%d\n", v_index->length);
|
||||
}
|
||||
|
||||
xdb_free_vector_index(v_index);
|
||||
}
|
||||
|
||||
void test_load_content() {
|
||||
xdb_content_t *content = xdb_load_content_from_file("../../data/ip2region_v4.xdb");
|
||||
if (content == NULL) {
|
||||
printf("failed to load content from file\n");
|
||||
} else {
|
||||
printf("content loaded from file, length=%d\n", content->length);
|
||||
}
|
||||
|
||||
xdb_free_content(content);
|
||||
}
|
||||
|
||||
void test_parse_ip() {
|
||||
const char *ip_list[] = {
|
||||
"1.0.0.0", "58.251.30.115", "192.168.1.100",
|
||||
"::", "2c0f:fff0::", "2fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", "240e:982:e617:ffff:ffff:ffff:ffff:ffff",
|
||||
"219.xx.xx.11", "::xx:ffff",
|
||||
NULL
|
||||
};
|
||||
|
||||
int errcode;
|
||||
xdb_version_t *version;
|
||||
bytes_ip_t ip_bytes[16] = {'\0'};
|
||||
string_ip_t ip_string[INET6_ADDRSTRLEN] = {'\0'};
|
||||
|
||||
// init the sock env (for windows)
|
||||
if ((errcode = xdb_init_winsock()) != 0) {
|
||||
printf("failed to init the winsock");
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0;; i++) {
|
||||
if (ip_list[i] == NULL) {
|
||||
break;
|
||||
}
|
||||
|
||||
version = xdb_parse_ip(ip_list[i], ip_bytes, sizeof(ip_bytes));
|
||||
if (version == NULL) {
|
||||
printf("failed to parse ip `%s`\n", ip_list[i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
xdb_ip_to_string(ip_bytes, version->bytes, ip_string, sizeof(ip_string));
|
||||
printf("ip: %s (version=v%d), toString: %s\n", ip_list[i], version->id, ip_string);
|
||||
}
|
||||
|
||||
// clean up the winsock
|
||||
xdb_clean_winsock();
|
||||
}
|
||||
|
||||
struct ip_pair {
|
||||
const char *sip;
|
||||
const char *eip;
|
||||
};
|
||||
void test_ip_compare() {
|
||||
struct ip_pair ip_pair_list[] = {
|
||||
{"1.0.0.0", "1.0.0.1"},
|
||||
{"192.168.1.101", "192.168.1.90"},
|
||||
{"219.133.111.87", "114.114.114.114"},
|
||||
{"1.0.4.0", "1.0.1.0"},
|
||||
{"1.0.4.0", "1.0.3.255"},
|
||||
{"2000::", "2000:ffff:ffff:ffff:ffff:ffff:ffff:ffff"},
|
||||
{"2001:4:112::", "2001:4:112:ffff:ffff:ffff:ffff:ffff"},
|
||||
{"ffff::", "2001:4:ffff:ffff:ffff:ffff:ffff:ffff"},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
struct ip_pair *pair_ptr = NULL;
|
||||
bytes_ip_t sip_bytes[16] = {'\0'};
|
||||
bytes_ip_t eip_bytes[16] = {'\0'};
|
||||
xdb_version_t *s_version, *e_version;
|
||||
int errcode;
|
||||
|
||||
// init the sock env (for windows)
|
||||
if ((errcode = xdb_init_winsock()) != 0) {
|
||||
printf("failed to init the winsock");
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; ;i++) {
|
||||
pair_ptr = &ip_pair_list[i];
|
||||
if (pair_ptr->sip == NULL) {
|
||||
break;
|
||||
}
|
||||
|
||||
s_version = xdb_parse_ip(pair_ptr->sip, sip_bytes, sizeof(sip_bytes));
|
||||
if (s_version == NULL) {
|
||||
printf("failed to parse sip `%s`", pair_ptr->sip);
|
||||
continue;
|
||||
}
|
||||
|
||||
e_version = xdb_parse_ip(pair_ptr->eip, eip_bytes, sizeof(eip_bytes));
|
||||
if (e_version == NULL) {
|
||||
printf("failed to parse eip `%s`", pair_ptr->eip);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (s_version->id != e_version->id) {
|
||||
printf("sip and eip version not match `%s` != `%s`\n", s_version->name, e_version->name);
|
||||
continue;
|
||||
}
|
||||
|
||||
printf(
|
||||
"ip_sub_compare(%s, %s): %d\n",
|
||||
pair_ptr->sip, pair_ptr->eip,
|
||||
xdb_ip_sub_compare(sip_bytes, s_version->bytes, (string_ip_t *) eip_bytes, 0)
|
||||
);
|
||||
}
|
||||
|
||||
// clean up the winsock
|
||||
xdb_clean_winsock();
|
||||
}
|
||||
|
||||
// please register your function heare
|
||||
static test_func_t _test_function_list[] = {
|
||||
// xdb buffer
|
||||
{"test_load_header", test_load_header},
|
||||
{"test_load_vector_index", test_load_vector_index},
|
||||
{"test_load_content", test_load_content},
|
||||
|
||||
// ip utils
|
||||
{"test_parse_ip", test_parse_ip},
|
||||
{"test_ip_compare", test_ip_compare},
|
||||
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
// valgrind --tool=memcheck --leak-check=full ./a.out
|
||||
int main(int argc, char *argv[]) {
|
||||
int i;
|
||||
char *name;
|
||||
|
||||
// check and call the function
|
||||
if (argc < 2) {
|
||||
printf("please specified the function name to call\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
name = argv[1];
|
||||
test_func_ptr func = NULL;
|
||||
for (i = 0; ; i++) {
|
||||
if (_test_function_list[i].name == NULL) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (strcmp(name, _test_function_list[i].name) == 0) {
|
||||
func = _test_function_list[i].func;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (func == NULL) {
|
||||
printf("can't find test function `%s`\n", name);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// call the function
|
||||
func();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,276 @@
|
|||
// Copyright 2022 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// ---
|
||||
// @Author Lion <chenxin619315@gmail.com>
|
||||
// @Date 2022/06/27
|
||||
|
||||
#ifndef C_IP2REGION_XDB_H
|
||||
#define C_IP2REGION_XDB_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#if ( defined(WIN32) || defined(_WIN32) || defined(__WINDOWS_) || defined(WINNT) )
|
||||
# define XDB_PUBLIC(type) extern __declspec(dllexport) type
|
||||
# define XDB_PRIVATE(type) static type
|
||||
# define XDB_WINDOWS
|
||||
#include <windows.h>
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#pragma comment(lib, "ws2_32.lib")
|
||||
#elif (defined(linux) || defined(_UNIX) || defined(__APPLE__) || defined(unix) || defined(__unix) || defined(__unix__) || defined(__linux__) || defined(linux) || defined(__linux))
|
||||
# define XDB_PUBLIC(type) extern type
|
||||
# define XDB_PRIVATE(type) static inline type
|
||||
# define XDB_LINUX
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#else
|
||||
# define XDB_PUBLIC(type) type
|
||||
# define XDB_PRIVATE(type) static type
|
||||
#endif
|
||||
|
||||
#define xdb_calloc( _blocks, _bytes ) calloc( _blocks, _bytes )
|
||||
#define xdb_malloc( _bytes ) malloc( _bytes )
|
||||
#define xdb_free( _ptr ) free( _ptr )
|
||||
|
||||
// public constants define
|
||||
#define xdb_structure_20 2
|
||||
#define xdb_structure_30 3
|
||||
#define xdb_header_info_length 256
|
||||
#define xdb_vector_index_rows 256
|
||||
#define xdb_vector_index_cols 256
|
||||
#define xdb_vector_index_size 8
|
||||
#define xdb_v4_index_size 14 // 4 + 4 + 2 + 4
|
||||
#define xdb_v6_index_size 38 // 16 + 16 + 2 + 4
|
||||
|
||||
// --- ip version info
|
||||
#define xdb_ipv4_id 4
|
||||
#define xdb_ipv6_id 6
|
||||
#define xdb_ipv4_bytes 4
|
||||
#define xdb_ipv6_bytes 16
|
||||
// cache of vector_index_row × vector_index_rows × vector_index_size
|
||||
#define xdb_vector_index_length 524288
|
||||
|
||||
// --- xdb buffer functions
|
||||
|
||||
// use the following buffer struct to wrap the binary buffer data
|
||||
// since the buffer data could not be operated with the string API.
|
||||
struct xdb_header {
|
||||
unsigned short version;
|
||||
unsigned short index_policy;
|
||||
unsigned int created_at;
|
||||
unsigned int start_index_ptr;
|
||||
unsigned int end_index_ptr;
|
||||
|
||||
// since 3.0+ with IPv6 supporting
|
||||
unsigned short ip_version;
|
||||
unsigned short runtime_ptr_bytes;
|
||||
|
||||
// the original buffer
|
||||
unsigned int length;
|
||||
char buffer[xdb_header_info_length];
|
||||
};
|
||||
typedef struct xdb_header xdb_header_t;
|
||||
|
||||
XDB_PUBLIC(xdb_header_t *) xdb_load_header(FILE *);
|
||||
|
||||
XDB_PUBLIC(xdb_header_t *) xdb_load_header_from_file(const char *);
|
||||
|
||||
XDB_PUBLIC(void) xdb_free_header(void *);
|
||||
|
||||
|
||||
// --- vector index buffer
|
||||
struct xdb_vector_index {
|
||||
unsigned int length;
|
||||
char buffer[xdb_vector_index_length];
|
||||
};
|
||||
typedef struct xdb_vector_index xdb_vector_index_t;
|
||||
|
||||
XDB_PUBLIC(xdb_vector_index_t *) xdb_load_vector_index(FILE *);
|
||||
|
||||
XDB_PUBLIC(xdb_vector_index_t *) xdb_load_vector_index_from_file(const char *);
|
||||
|
||||
XDB_PUBLIC(void) xdb_free_vector_index(void *);
|
||||
|
||||
|
||||
// --- content buffer
|
||||
struct xdb_content {
|
||||
unsigned int length;
|
||||
char *buffer;
|
||||
};
|
||||
typedef struct xdb_content xdb_content_t;
|
||||
|
||||
XDB_PUBLIC(xdb_content_t *) xdb_load_content(FILE *);
|
||||
|
||||
XDB_PUBLIC(xdb_content_t *) xdb_load_content_from_file(const char *);
|
||||
|
||||
XDB_PUBLIC(void) xdb_free_content(void *);
|
||||
|
||||
// --- xdb verify
|
||||
|
||||
// Verify if the current Searcher could be used to search the specified xdb file.
|
||||
// Why do we need this check ?
|
||||
// The future features of the xdb impl may cause the current searcher not able to work properly.
|
||||
//
|
||||
// @Note: You Just need to check this ONCE when the service starts
|
||||
// Or use another process (eg, A command) to check once Just to confirm the suitability.
|
||||
XDB_PUBLIC(int) xdb_verify(FILE *);
|
||||
|
||||
XDB_PUBLIC(int) xdb_verify_from_header(FILE *handle, xdb_header_t *);
|
||||
|
||||
XDB_PUBLIC(int) xdb_verify_from_file(const char *);
|
||||
|
||||
// --- End xdb buffer
|
||||
|
||||
|
||||
// types type define
|
||||
typedef char string_ip_t;
|
||||
typedef unsigned char bytes_ip_t;
|
||||
|
||||
// --- ip version
|
||||
#define XDB_IPv4 (xdb_version_v4())
|
||||
#define XDB_IPv6 (xdb_version_v6())
|
||||
typedef int (* ip_compare_fn_t) (const bytes_ip_t *, int, const char *, int);
|
||||
struct xdb_ip_version_entry {
|
||||
int id; // version id
|
||||
char *name; // version name
|
||||
int bytes; // ip bytes number
|
||||
int segment_index_size; // segment index size in bytes
|
||||
|
||||
// function to compare two ips
|
||||
ip_compare_fn_t ip_compare;
|
||||
};
|
||||
typedef struct xdb_ip_version_entry xdb_version_t;
|
||||
|
||||
XDB_PUBLIC(xdb_version_t *) xdb_version_v4();
|
||||
XDB_PUBLIC(xdb_version_t *) xdb_version_v6();
|
||||
|
||||
XDB_PUBLIC(int) xdb_version_is_v4(const xdb_version_t *);
|
||||
XDB_PUBLIC(int) xdb_version_is_v6(const xdb_version_t *);
|
||||
|
||||
XDB_PUBLIC(xdb_version_t *) xdb_version_from_name(char *);
|
||||
XDB_PUBLIC(xdb_version_t *) xdb_version_from_header(xdb_header_t *);
|
||||
|
||||
// --- END ip version
|
||||
|
||||
// --- xdb util functions
|
||||
|
||||
// to compatiable with the windows
|
||||
// returns: 0 for ok and -1 for failed
|
||||
XDB_PUBLIC(int) xdb_init_winsock();
|
||||
XDB_PUBLIC(void) xdb_clean_winsock();
|
||||
|
||||
// get the current time in microseconds
|
||||
XDB_PUBLIC(long) xdb_now();
|
||||
|
||||
// get unsigned long (4bytes) from a specified buffer start from the specified offset with little-endian
|
||||
XDB_PUBLIC(unsigned int) xdb_le_get_uint32(const char *, int);
|
||||
|
||||
// get unsigned short (2bytes) from a specified buffer start from the specified offset with little-endian
|
||||
XDB_PUBLIC(int) xdb_le_get_uint16(const char *, int);
|
||||
|
||||
|
||||
// parse the specified IP address to byte array.
|
||||
// returns: xdb_version_t for valid ipv4 / ipv6, or NULL for failed
|
||||
XDB_PUBLIC(xdb_version_t *) xdb_parse_ip(const string_ip_t *, bytes_ip_t *, size_t);
|
||||
|
||||
// parse the specified IPv4 address to byte array
|
||||
// returns: xdb_version_t for valid ipv4, or NULL for failed
|
||||
XDB_PUBLIC(xdb_version_t *) xdb_parse_v4_ip(const string_ip_t *, bytes_ip_t *, size_t);
|
||||
|
||||
// parse the specified IPv6 address to byte array
|
||||
// returns: xdb_version_t for valid ipv6, or NULL for failed
|
||||
XDB_PUBLIC(xdb_version_t *) xdb_parse_v6_ip(const string_ip_t *, bytes_ip_t *, size_t);
|
||||
|
||||
// convert a specified ip bytes to humen-readable string.
|
||||
// returns: 0 for success or -1 for failed.
|
||||
XDB_PUBLIC(int) xdb_ip_to_string(const bytes_ip_t *, int, char *, size_t);
|
||||
|
||||
// ipv4 bytes to string
|
||||
XDB_PUBLIC(int) xdb_v4_ip_to_string(const bytes_ip_t *, char *, size_t);
|
||||
|
||||
// ipv6 bytes to string
|
||||
XDB_PUBLIC(int) xdb_v6_ip_to_string(const bytes_ip_t *, char *, size_t);
|
||||
|
||||
// compare the specified ip bytes with another ip bytes in the specified buff from offset.
|
||||
// ip args must be the return value from #xdb_parse_ip.
|
||||
// returns: -1 if ip1 < ip2, 1 if ip1 > ip2 or 0
|
||||
XDB_PUBLIC(int) xdb_ip_sub_compare(const bytes_ip_t *, int, const char *, int);
|
||||
|
||||
// --- END xdb utils
|
||||
|
||||
|
||||
// --- xdb searcher api
|
||||
|
||||
// xdb region info structure
|
||||
#define xdb_region_buffer_wrapper 1
|
||||
#define xdb_region_buffer_auto 2
|
||||
struct xdb_region_buffer_entry {
|
||||
int type; // buffer type
|
||||
char *value; // region value
|
||||
size_t length; // buffer length
|
||||
};
|
||||
typedef struct xdb_region_buffer_entry xdb_region_buffer_t;
|
||||
|
||||
// wrapper the region from a local stack buffer.
|
||||
// returns: 0 for succeed or failed
|
||||
XDB_PUBLIC(int) xdb_region_buffer_init(xdb_region_buffer_t *, char *, size_t);
|
||||
|
||||
// do the buffer alloc.
|
||||
// returns: 0 for ok or failed
|
||||
XDB_PUBLIC(int) xdb_region_buffer_alloc(xdb_region_buffer_t *, int);
|
||||
|
||||
// empty alloc - empty string
|
||||
// returns: 0 - always
|
||||
XDB_PUBLIC(int) xdb_region_buffer_empty(xdb_region_buffer_t *);
|
||||
|
||||
XDB_PUBLIC(void) xdb_region_buffer_free(xdb_region_buffer_t *);
|
||||
|
||||
// xdb searcher structure
|
||||
struct xdb_searcher_entry {
|
||||
// ip version
|
||||
xdb_version_t *version;
|
||||
|
||||
// xdb file handle
|
||||
FILE *handle;
|
||||
|
||||
// header info
|
||||
const char *header;
|
||||
int io_count;
|
||||
|
||||
// vector index buffer cache.
|
||||
// preload the vector index will reduce the number of IO operations
|
||||
// thus speedup the search process.
|
||||
const xdb_vector_index_t *v_index;
|
||||
|
||||
// content buffer.
|
||||
// cache the whole xdb content.
|
||||
const xdb_content_t *content;
|
||||
};
|
||||
typedef struct xdb_searcher_entry xdb_searcher_t;
|
||||
|
||||
// xdb searcher new api define
|
||||
XDB_PUBLIC(int) xdb_new_with_file_only(xdb_version_t *, xdb_searcher_t *, const char *);
|
||||
|
||||
XDB_PUBLIC(int) xdb_new_with_vector_index(xdb_version_t *, xdb_searcher_t *, const char *, const xdb_vector_index_t *);
|
||||
|
||||
XDB_PUBLIC(int) xdb_new_with_buffer(xdb_version_t *, xdb_searcher_t *, const xdb_content_t *);
|
||||
|
||||
XDB_PUBLIC(void) xdb_close(void *);
|
||||
|
||||
// xdb searcher search api define
|
||||
XDB_PUBLIC(int) xdb_search_by_string(xdb_searcher_t *, const string_ip_t *, xdb_region_buffer_t *);
|
||||
|
||||
XDB_PUBLIC(int) xdb_search(xdb_searcher_t *, const bytes_ip_t *, int, xdb_region_buffer_t *);
|
||||
|
||||
XDB_PUBLIC(xdb_version_t *) xdb_get_version(xdb_searcher_t *);
|
||||
|
||||
XDB_PUBLIC(int) xdb_get_io_count(xdb_searcher_t *);
|
||||
|
||||
// --- END xdb searcher api
|
||||
|
||||
#endif // C_IP2REGION_XDB_H
|
||||
|
|
@ -0,0 +1,273 @@
|
|||
// Copyright 2022 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// ---
|
||||
// @Author Lion <chenxin619315@gmail.com>
|
||||
// @Date 2022/06/27
|
||||
|
||||
#include "xdb_api.h"
|
||||
|
||||
// --- region buffer
|
||||
XDB_PUBLIC(int) xdb_region_buffer_init(xdb_region_buffer_t *region, char *buffer, size_t length) {
|
||||
if (buffer == NULL) {
|
||||
region->type = xdb_region_buffer_auto;
|
||||
region->length = 0;
|
||||
} else if (length <= 0) {
|
||||
return 1;
|
||||
} else {
|
||||
region->type = xdb_region_buffer_wrapper;
|
||||
region->length = length;
|
||||
memset(buffer, 0x00, length); // zero-fill the buffer
|
||||
}
|
||||
|
||||
region->value = buffer;
|
||||
return 0;
|
||||
}
|
||||
|
||||
XDB_PUBLIC(int) xdb_region_buffer_alloc(xdb_region_buffer_t *region, int length) {
|
||||
if (length <= 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// no allocation supports for the buffer wapper
|
||||
if (region->type == xdb_region_buffer_wrapper) {
|
||||
if (length >= region->length) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
region->value[length] = '\0';
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ensure that the value were freed
|
||||
// by calling #xdb_region_buffer_free
|
||||
if (region->value != NULL) {
|
||||
return 3;
|
||||
}
|
||||
|
||||
char *ptr = (char *) xdb_malloc(length + 1);
|
||||
if (ptr == NULL) {
|
||||
return 4;
|
||||
}
|
||||
|
||||
ptr[length] = '\0'; // NULL-end
|
||||
region->value = ptr;
|
||||
region->length = length;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// fixed internal empty string ptr
|
||||
static char * _empty_region_string = "\0";
|
||||
|
||||
XDB_PUBLIC(int) xdb_region_buffer_empty(xdb_region_buffer_t *region) {
|
||||
// no allocation supports for the buffer wapper
|
||||
if (region->type == xdb_region_buffer_wrapper) {
|
||||
region->value[0] = '\0';
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ensure that the value were freed
|
||||
// by calling #xdb_region_buffer_free
|
||||
if (region->value != NULL) {
|
||||
return 3;
|
||||
}
|
||||
|
||||
region->value = _empty_region_string;
|
||||
region->length = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
XDB_PUBLIC(void) xdb_region_buffer_free(xdb_region_buffer_t *region) {
|
||||
if (region->type == xdb_region_buffer_auto) {
|
||||
// empty string interception
|
||||
if (region->length == 0
|
||||
|| region->value == _empty_region_string) {
|
||||
// do nothing for empty string
|
||||
} else {
|
||||
xdb_free(region->value);
|
||||
}
|
||||
|
||||
// reset the value
|
||||
region->value = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// --- END region buffer
|
||||
|
||||
// internal function prototype define
|
||||
XDB_PRIVATE(int) read(xdb_searcher_t *, long offset, char *, size_t length);
|
||||
|
||||
XDB_PRIVATE(int) xdb_new_base(xdb_version_t *version, xdb_searcher_t *xdb, const char *db_path, const xdb_vector_index_t *v_index, const xdb_content_t *c_buffer) {
|
||||
memset(xdb, 0x00, sizeof(xdb_searcher_t));
|
||||
|
||||
// set the version
|
||||
xdb->version = version;
|
||||
|
||||
// check the content buffer first
|
||||
if (c_buffer != NULL) {
|
||||
xdb->v_index = NULL;
|
||||
xdb->content = c_buffer;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// open the xdb binary file
|
||||
FILE *handle = fopen(db_path, "rb");
|
||||
if (handle == NULL) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
xdb->handle = handle;
|
||||
xdb->v_index = v_index;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// xdb searcher new api define
|
||||
XDB_PUBLIC(int) xdb_new_with_file_only(xdb_version_t *version, xdb_searcher_t *xdb, const char *db_path) {
|
||||
return xdb_new_base(version, xdb, db_path, NULL, NULL);
|
||||
}
|
||||
|
||||
XDB_PUBLIC(int) xdb_new_with_vector_index(xdb_version_t *version, xdb_searcher_t *xdb, const char *db_path, const xdb_vector_index_t *v_index) {
|
||||
return xdb_new_base(version, xdb, db_path, v_index, NULL);
|
||||
}
|
||||
|
||||
XDB_PUBLIC(int) xdb_new_with_buffer(xdb_version_t *version, xdb_searcher_t *xdb, const xdb_content_t *c_buffer) {
|
||||
return xdb_new_base(version, xdb, NULL, NULL, c_buffer);
|
||||
}
|
||||
|
||||
XDB_PUBLIC(void) xdb_close(void *ptr) {
|
||||
xdb_searcher_t *xdb = (xdb_searcher_t *) ptr;
|
||||
if (xdb->handle != NULL) {
|
||||
fclose(xdb->handle);
|
||||
xdb->handle = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// --- xdb searcher search api define
|
||||
|
||||
XDB_PUBLIC(int) xdb_search_by_string(xdb_searcher_t *xdb, const string_ip_t *ip_string, xdb_region_buffer_t *region) {
|
||||
bytes_ip_t ip_bytes[16] = {'\0'};
|
||||
xdb_version_t *version = xdb_parse_ip(ip_string, ip_bytes, sizeof(ip_bytes));
|
||||
if (version == NULL) {
|
||||
return 10;
|
||||
} else {
|
||||
return xdb_search(xdb, ip_bytes, version->bytes, region);
|
||||
}
|
||||
}
|
||||
|
||||
XDB_PUBLIC(int) xdb_search(xdb_searcher_t *xdb, const bytes_ip_t *ip_bytes, int ip_len, xdb_region_buffer_t *region) {
|
||||
int il0, il1, idx, err, bytes, d_bytes;
|
||||
register int seg_index_size, l, h, m, p;
|
||||
unsigned int s_ptr, e_ptr, data_ptr, data_len;
|
||||
char vector_buffer[xdb_vector_index_size];
|
||||
char segment_buffer[xdb_v6_index_size];
|
||||
|
||||
// ip version check
|
||||
if (ip_len != xdb->version->bytes) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// some resets
|
||||
err = 0;
|
||||
data_len = 0;
|
||||
bytes = xdb->version->bytes;
|
||||
d_bytes = xdb->version->bytes << 1;
|
||||
xdb->io_count = 0;
|
||||
|
||||
// locate the segment index block based on the vector index
|
||||
il0 = (int) (ip_bytes[0]);
|
||||
il1 = (int) (ip_bytes[1]);
|
||||
idx = il0 * xdb_vector_index_cols * xdb_vector_index_size + il1 * xdb_vector_index_size;
|
||||
if (xdb->v_index != NULL) {
|
||||
s_ptr = xdb_le_get_uint32(xdb->v_index->buffer, idx);
|
||||
e_ptr = xdb_le_get_uint32(xdb->v_index->buffer, idx + 4);
|
||||
} else if (xdb->content != NULL) {
|
||||
s_ptr = xdb_le_get_uint32(xdb->content->buffer, xdb_header_info_length + idx);
|
||||
e_ptr = xdb_le_get_uint32(xdb->content->buffer, xdb_header_info_length + idx + 4);
|
||||
} else {
|
||||
err = read(xdb, xdb_header_info_length + idx, vector_buffer, sizeof(vector_buffer));
|
||||
if (err != 0) {
|
||||
return 10 + err;
|
||||
}
|
||||
|
||||
s_ptr = xdb_le_get_uint32(vector_buffer, 0);
|
||||
e_ptr = xdb_le_get_uint32(vector_buffer, 4);
|
||||
}
|
||||
|
||||
// printf("s_ptr=%u, e_ptr=%u\n", s_ptr, e_ptr);
|
||||
// binary search to get the final region info
|
||||
// segment_buffer = xdb_malloc(seg_index_size);
|
||||
seg_index_size = xdb->version->segment_index_size;
|
||||
data_len = 0, data_ptr = 0;
|
||||
l = 0, h = ((int) (e_ptr - s_ptr)) / seg_index_size;
|
||||
while (l <= h) {
|
||||
m = (l + h) >> 1;
|
||||
p = s_ptr + m * seg_index_size;
|
||||
|
||||
// read the segment index item
|
||||
err = read(xdb, p, segment_buffer, seg_index_size);
|
||||
if (err != 0) {
|
||||
return 20 + err;
|
||||
}
|
||||
|
||||
// decode the data fields as needed
|
||||
if (xdb->version->ip_compare(ip_bytes, bytes, segment_buffer, 0) < 0) {
|
||||
h = m - 1;
|
||||
} else if (xdb->version->ip_compare(ip_bytes, bytes, segment_buffer, bytes) > 0) {
|
||||
l = m + 1;
|
||||
} else {
|
||||
data_len = xdb_le_get_uint16(segment_buffer, d_bytes);
|
||||
data_ptr = xdb_le_get_uint32(segment_buffer, d_bytes + 2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// printf("data_len=%u, data_ptr=%u\n", data_len, data_ptr);
|
||||
if (data_len == 0) {
|
||||
// return 100;
|
||||
xdb_region_buffer_empty(region);
|
||||
return err;
|
||||
}
|
||||
|
||||
// buffer alloc checking
|
||||
err = xdb_region_buffer_alloc(region, data_len);
|
||||
if (err != 0) {
|
||||
return 100 + err;
|
||||
}
|
||||
|
||||
err = read(xdb, data_ptr, region->value, data_len);
|
||||
if (err != 0) {
|
||||
return 30 + err;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
XDB_PRIVATE(int) read(xdb_searcher_t *xdb, long offset, char *buffer, size_t length) {
|
||||
// check the xdb content cache first
|
||||
if (xdb->content != NULL) {
|
||||
memcpy(buffer, xdb->content->buffer + offset, length);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// seek to the offset
|
||||
if (fseek(xdb->handle, offset, SEEK_SET) == -1) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
xdb->io_count++;
|
||||
if (fread(buffer, 1, length, xdb->handle) != length) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
XDB_PUBLIC(xdb_version_t *) xdb_get_version(xdb_searcher_t *xdb) {
|
||||
return xdb->version;
|
||||
}
|
||||
|
||||
XDB_PUBLIC(int) xdb_get_io_count(xdb_searcher_t *xdb) {
|
||||
return xdb->io_count;
|
||||
}
|
||||
|
|
@ -0,0 +1,498 @@
|
|||
// Copyright 2022 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// ---
|
||||
// @Author Lion <chenxin619315@gmail.com>
|
||||
// @Date 2022/06/27
|
||||
|
||||
#include "xdb_api.h"
|
||||
#include <ctype.h>
|
||||
|
||||
// for Linux
|
||||
#ifdef XDB_LINUX
|
||||
#include "sys/time.h"
|
||||
#endif
|
||||
|
||||
#ifdef XDB_WINDOWS
|
||||
#include <time.h>
|
||||
#endif
|
||||
|
||||
// @Note: since 2023/10/13 to compatible with the windows system
|
||||
#ifdef XDB_WINDOWS
|
||||
static int winsock_initialized = 0;
|
||||
XDB_PUBLIC(int) xdb_init_winsock() {
|
||||
if (winsock_initialized == 1) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
WSADATA wsaData;
|
||||
if (WSAStartup(MAKEWORD(2,2), &wsaData) != 0) {
|
||||
return -1;
|
||||
}
|
||||
winsock_initialized = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
XDB_PUBLIC(void) xdb_clean_winsock() {
|
||||
if (winsock_initialized == 1) {
|
||||
WSACleanup();
|
||||
winsock_initialized = 0;
|
||||
}
|
||||
}
|
||||
|
||||
XDB_PRIVATE(int) gettimeofday(struct timeval* tp, void* tzp) {
|
||||
time_t clock;
|
||||
struct tm tm;
|
||||
SYSTEMTIME wtm;
|
||||
GetLocalTime(&wtm);
|
||||
tm.tm_year = wtm.wYear - 1900;
|
||||
tm.tm_mon = wtm.wMonth - 1;
|
||||
tm.tm_mday = wtm.wDay;
|
||||
tm.tm_hour = wtm.wHour;
|
||||
tm.tm_min = wtm.wMinute;
|
||||
tm.tm_sec = wtm.wSecond;
|
||||
tm.tm_isdst = -1;
|
||||
clock = mktime(&tm);
|
||||
tp->tv_sec = clock;
|
||||
tp->tv_usec = wtm.wMilliseconds * 1000;
|
||||
return (0);
|
||||
}
|
||||
#else
|
||||
XDB_PUBLIC(int) xdb_init_winsock() {return 0;}
|
||||
XDB_PUBLIC(void) xdb_clean_winsock() {}
|
||||
#endif
|
||||
|
||||
// --- xdb buffer function implementations
|
||||
|
||||
XDB_PUBLIC(xdb_header_t *) xdb_load_header(FILE *handle) {
|
||||
xdb_header_t *header;
|
||||
unsigned int size = xdb_header_info_length;
|
||||
|
||||
// entry alloc
|
||||
header = (xdb_header_t *) xdb_malloc(sizeof(xdb_header_t));
|
||||
if (header == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (fseek(handle, 0, SEEK_SET) == -1) {
|
||||
xdb_free(header);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (fread(header->buffer, 1,size, handle) != size) {
|
||||
xdb_free(header);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// fill the fields
|
||||
header->length = size;
|
||||
header->version = (unsigned short) xdb_le_get_uint16(header->buffer, 0);
|
||||
header->index_policy = (unsigned short) xdb_le_get_uint16(header->buffer, 2);
|
||||
header->created_at = xdb_le_get_uint32(header->buffer, 4);
|
||||
header->start_index_ptr = xdb_le_get_uint32(header->buffer, 8);
|
||||
header->end_index_ptr = xdb_le_get_uint32(header->buffer,12);
|
||||
|
||||
// since IPv6 supporting
|
||||
header->ip_version = xdb_le_get_uint16(header->buffer, 16);
|
||||
header->runtime_ptr_bytes = xdb_le_get_uint16(header->buffer, 18);
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
XDB_PUBLIC(xdb_header_t *) xdb_load_header_from_file(const char *db_path) {
|
||||
xdb_header_t *header;
|
||||
FILE *handle = fopen(db_path, "rb");
|
||||
if (handle == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
header = xdb_load_header(handle);
|
||||
fclose(handle);
|
||||
return header;
|
||||
}
|
||||
|
||||
XDB_PUBLIC(void) xdb_free_header(void *ptr) {
|
||||
xdb_header_t *header = (xdb_header_t *) ptr;
|
||||
if (header->length > 0) {
|
||||
header->length = 0;
|
||||
xdb_free(header);
|
||||
}
|
||||
}
|
||||
|
||||
// --- vector index
|
||||
|
||||
XDB_PUBLIC(xdb_vector_index_t *) xdb_load_vector_index(FILE *handle) {
|
||||
xdb_vector_index_t *v_index;
|
||||
unsigned int size = xdb_vector_index_length;
|
||||
|
||||
// seek to the vector index offset
|
||||
if (fseek(handle, xdb_header_info_length, SEEK_SET) == -1) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// do the buffer read
|
||||
v_index = (xdb_vector_index_t *) xdb_malloc(sizeof(xdb_vector_index_t));
|
||||
if (v_index == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
v_index->length = size;
|
||||
if (fread(v_index->buffer, 1, size, handle) != size) {
|
||||
xdb_free(v_index);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return v_index;
|
||||
}
|
||||
|
||||
XDB_PUBLIC(xdb_vector_index_t *) xdb_load_vector_index_from_file(const char *db_path) {
|
||||
xdb_vector_index_t *v_index;
|
||||
FILE *handle = fopen(db_path, "rb");
|
||||
if (handle == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
v_index = xdb_load_vector_index(handle);
|
||||
fclose(handle);
|
||||
return v_index;
|
||||
}
|
||||
|
||||
XDB_PUBLIC(void) xdb_free_vector_index(void *ptr) {
|
||||
xdb_vector_index_t *v_index = (xdb_vector_index_t *) ptr;
|
||||
if (v_index->length > 0) {
|
||||
v_index->length = 0;
|
||||
xdb_free(v_index);
|
||||
}
|
||||
}
|
||||
|
||||
// --- content buffer
|
||||
|
||||
XDB_PUBLIC(xdb_content_t *) xdb_load_content(FILE *handle) {
|
||||
unsigned int size;
|
||||
xdb_content_t *content;
|
||||
|
||||
// determine the file size
|
||||
if (fseek(handle, 0, SEEK_END) == -1) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
size = (unsigned int) ftell(handle);
|
||||
if (fseek(handle, 0, SEEK_SET) == -1) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// do the file read
|
||||
content = (xdb_content_t *) xdb_malloc(sizeof(xdb_content_t));
|
||||
if (content == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// do the buffer alloc
|
||||
content->buffer = (char *) xdb_malloc(size);
|
||||
if (content->buffer == NULL) {
|
||||
xdb_free(content);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// read the content into the buffer
|
||||
content->length = size;
|
||||
if (fread(content->buffer, 1, size, handle) != size) {
|
||||
xdb_free(content);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
XDB_PUBLIC(xdb_content_t *) xdb_load_content_from_file(const char *db_path) {
|
||||
xdb_content_t *content;
|
||||
FILE *handle = fopen(db_path, "rb");
|
||||
if (handle == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
content = xdb_load_content(handle);
|
||||
fclose(handle);
|
||||
return content;
|
||||
}
|
||||
|
||||
XDB_PUBLIC(void) xdb_free_content(void *ptr) {
|
||||
xdb_content_t *content = (xdb_content_t *) ptr;
|
||||
if (content->length > 0) {
|
||||
content->length = 0;
|
||||
xdb_free(content->buffer);
|
||||
content->buffer = NULL;
|
||||
xdb_free(content);
|
||||
}
|
||||
}
|
||||
|
||||
XDB_PUBLIC(int) xdb_verify_from_header(FILE *handle, xdb_header_t *header) {
|
||||
int runtime_ptr_bytes = 0; // runtime ptr bytes
|
||||
if (header->version == xdb_structure_20) {
|
||||
runtime_ptr_bytes = 4;
|
||||
} else if (header->version == xdb_structure_30) {
|
||||
runtime_ptr_bytes = header->runtime_ptr_bytes;
|
||||
} else {
|
||||
return 2;
|
||||
}
|
||||
|
||||
// 1, confirm the xdb file size.
|
||||
// to ensure that the maximum file pointer does not overflow.
|
||||
int err = fseek(handle, 0L, SEEK_END);
|
||||
if (err != 0) {
|
||||
return 3;
|
||||
}
|
||||
|
||||
long int fileBytes = ftell(handle);
|
||||
long int maxFilePtr = (1L << (runtime_ptr_bytes * 8)) - 1;
|
||||
// printf("fileBytes: %ld, maxFilePtr: %ld\n", fileBytes, maxFilePtr);
|
||||
if (fileBytes > maxFilePtr) {
|
||||
return 4;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
XDB_PUBLIC(int) xdb_verify(FILE *handle) {
|
||||
xdb_header_t *header = xdb_load_header(handle);
|
||||
if (header == NULL) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
int errcode = xdb_verify_from_header(handle, header);
|
||||
if (errcode != 0) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
// what next ?
|
||||
done:
|
||||
xdb_free_header(header);
|
||||
return errcode;
|
||||
}
|
||||
|
||||
XDB_PUBLIC(int) xdb_verify_from_file(const char *db_path) {
|
||||
FILE *handle = fopen(db_path, "rb");
|
||||
if (handle == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int r = xdb_verify(handle);
|
||||
fclose(handle);
|
||||
return r;
|
||||
}
|
||||
|
||||
// --- End content buffer
|
||||
|
||||
|
||||
// --- ip version
|
||||
|
||||
// ip compare for IPv4
|
||||
// ip1 - with Big endian byte order parsed from an input
|
||||
// ip2 - with Little endian byte order read from the xdb index.
|
||||
// to compatiable with the Little Endian encoded IPv4 on xdb 2.0.
|
||||
XDB_PRIVATE(int) _ipv4_sub_compare(const bytes_ip_t *ip_bytes, int bytes, const char *buffer, int offset) {
|
||||
register int i0, i1;
|
||||
for (int i = 0, j = offset + bytes - 1; i < bytes; i++, j--) {
|
||||
i0 = ip_bytes[i];
|
||||
i1 = buffer[j] & 0xFF;
|
||||
if (i0 > i1) {
|
||||
return 1;
|
||||
} else if (i0 < i1) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static xdb_version_t _ip_version_list[] = {
|
||||
// 14 = 4 + 4 + 2 + 4
|
||||
{xdb_ipv4_id, "IPv4", xdb_ipv4_bytes, xdb_v4_index_size, _ipv4_sub_compare},
|
||||
|
||||
// 38 = 16 + 16 + 2 + 4
|
||||
{xdb_ipv6_id, "IPv6", xdb_ipv6_bytes, xdb_v6_index_size, xdb_ip_sub_compare},
|
||||
|
||||
// END
|
||||
{0, NULL, 0, 0, NULL}
|
||||
};
|
||||
|
||||
XDB_PUBLIC(xdb_version_t *) xdb_version_v4() {
|
||||
return &_ip_version_list[0];
|
||||
}
|
||||
|
||||
XDB_PUBLIC(xdb_version_t *) xdb_version_v6() {
|
||||
return &_ip_version_list[1];
|
||||
}
|
||||
|
||||
XDB_PUBLIC(int) xdb_version_is_v4(const xdb_version_t *version) {
|
||||
return version->id == xdb_ipv4_id;
|
||||
}
|
||||
|
||||
XDB_PUBLIC(int) xdb_version_is_v6(const xdb_version_t *version) {
|
||||
return version->id == xdb_ipv6_id;
|
||||
}
|
||||
|
||||
XDB_PUBLIC(xdb_version_t *) xdb_version_from_name(char *name) {
|
||||
// to upper case the name
|
||||
for (int i = 0; name[i] != '\0'; i++) {
|
||||
name[i] = toupper((unsigned char) name[i]);
|
||||
}
|
||||
|
||||
if (strcmp(name, "V4") == 0 || strcmp(name, "IPV4") == 0) {
|
||||
return xdb_version_v4();
|
||||
} else if (strcmp(name, "V6") == 0 || strcmp(name, "IPV6") == 0) {
|
||||
return xdb_version_v6();
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
XDB_PUBLIC(xdb_version_t *) xdb_version_from_header(xdb_header_t *header) {
|
||||
// Old structure with ONLY IPv4 supports
|
||||
if (header->version == xdb_structure_20) {
|
||||
return xdb_version_v4();
|
||||
}
|
||||
|
||||
// structure 3.0 with IPv6 supporting
|
||||
if (header->version != xdb_structure_30) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (header->ip_version == xdb_ipv4_id) {
|
||||
return xdb_version_v4();
|
||||
} else if (header->ip_version == xdb_ipv6_id) {
|
||||
return xdb_version_v6();
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// --- END ip version
|
||||
|
||||
XDB_PUBLIC(long) xdb_now() {
|
||||
struct timeval c_time;
|
||||
gettimeofday(&c_time, NULL);
|
||||
return c_time.tv_sec * (int)1e6 + c_time.tv_usec;
|
||||
}
|
||||
|
||||
XDB_PUBLIC(unsigned int) xdb_le_get_uint32(const char *buffer, int offset) {
|
||||
return (
|
||||
((buffer[offset ]) & 0x000000FF) |
|
||||
((buffer[offset+1] << 8) & 0x0000FF00) |
|
||||
((buffer[offset+2] << 16) & 0x00FF0000) |
|
||||
((buffer[offset+3] << 24) & 0xFF000000)
|
||||
);
|
||||
}
|
||||
|
||||
XDB_PUBLIC(int) xdb_le_get_uint16(const char *buffer, int offset) {
|
||||
return (
|
||||
((buffer[offset ]) & 0x000000FF) |
|
||||
((buffer[offset+1] << 8) & 0x0000FF00)
|
||||
);
|
||||
}
|
||||
|
||||
XDB_PUBLIC(xdb_version_t *) xdb_parse_ip(const string_ip_t *ip_string, bytes_ip_t *buffer, size_t length) {
|
||||
char *d_ptr = strchr(ip_string, '.');
|
||||
char *c_ptr = strchr(ip_string, ':');
|
||||
// version check
|
||||
if (d_ptr != NULL && c_ptr == NULL) {
|
||||
return xdb_parse_v4_ip(ip_string, buffer, length);
|
||||
} else if (c_ptr != NULL) {
|
||||
return xdb_parse_v6_ip(ip_string, buffer, length);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
XDB_PUBLIC(xdb_version_t *) xdb_parse_v4_ip(const string_ip_t *ip_string, bytes_ip_t *buffer, size_t length) {
|
||||
struct in_addr addr;
|
||||
|
||||
// buffer length checking
|
||||
if (length < xdb_ipv4_bytes) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (inet_pton(AF_INET, ip_string, &addr) != 1) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// encode the address to buffer with big endian byte bufffer.
|
||||
buffer[0] = (addr.s_addr) & 0xFF;
|
||||
buffer[1] = (addr.s_addr >> 8) & 0xFF;
|
||||
buffer[2] = (addr.s_addr >> 16) & 0xFF;
|
||||
buffer[3] = (addr.s_addr >> 24) & 0xFF;
|
||||
return XDB_IPv4;
|
||||
}
|
||||
|
||||
XDB_PUBLIC(xdb_version_t *) xdb_parse_v6_ip(const string_ip_t *ip_string, bytes_ip_t *buffer, size_t length) {
|
||||
struct in6_addr addr;
|
||||
|
||||
// buffer length checking
|
||||
if (length < xdb_ipv6_bytes) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (inet_pton(AF_INET6, ip_string, &addr) != 1) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
memcpy(buffer, addr.s6_addr, xdb_ipv6_bytes);
|
||||
return XDB_IPv6;
|
||||
}
|
||||
|
||||
XDB_PUBLIC(int) xdb_ip_to_string(const bytes_ip_t *ip_bytes, int bytes, char *ip_string, size_t length) {
|
||||
if (bytes == xdb_ipv4_bytes) {
|
||||
return xdb_v4_ip_to_string(ip_bytes, ip_string, length);
|
||||
} else if (bytes == xdb_ipv6_bytes) {
|
||||
return xdb_v6_ip_to_string(ip_bytes, ip_string, length);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
XDB_PUBLIC(int) xdb_v4_ip_to_string(const bytes_ip_t *ip_bytes, char *ip_string, size_t length) {
|
||||
if (!ip_bytes || !ip_string || length == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// buffer length checking
|
||||
if (length < INET_ADDRSTRLEN) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (inet_ntop(AF_INET, ip_bytes, ip_string, length) == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
XDB_PUBLIC(int) xdb_v6_ip_to_string(const bytes_ip_t *ip_bytes, char *ip_string, size_t length) {
|
||||
if (!ip_bytes || !ip_string || length == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (length < INET6_ADDRSTRLEN) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (inet_ntop(AF_INET6, ip_bytes, ip_string, length) == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
XDB_PUBLIC(int) xdb_ip_sub_compare(const bytes_ip_t *ip1, int bytes, const char *buffer, int offset) {
|
||||
register int i, i1, i2;
|
||||
for (i = 0; i < bytes; i++) {
|
||||
i1 = ip1[i];
|
||||
i2 = buffer[offset + i] & 0xFF;
|
||||
if (i1 > i2) {
|
||||
return 1;
|
||||
} else if (i1 < i2) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
|
||||
all: bin header search bench make edit
|
||||
|
||||
FILES=$(wildcard src/*.cc)
|
||||
|
||||
bin:
|
||||
mkdir -p bin
|
||||
|
||||
header: $(FILES) test/header.cc
|
||||
g++ -std=c++11 -O2 $^ -o bin/$@
|
||||
|
||||
search: $(FILES) test/search.cc
|
||||
g++ -std=c++11 -O2 $^ -o bin/$@
|
||||
|
||||
bench: $(FILES) test/bench.cc
|
||||
g++ -std=c++11 -O2 $^ -o bin/$@
|
||||
|
||||
make: $(FILES) test/make.cc
|
||||
g++ -std=c++11 -O2 $^ -o bin/$@
|
||||
|
||||
edit: $(FILES)
|
||||
g++ -std=c++11 -O2 $^ test/edit_v4.cc -o bin/edit_v4
|
||||
g++ -std=c++11 -O2 $^ test/edit_v6.cc -o bin/edit_v6
|
||||
|
||||
clean:
|
||||
rm -rf bin
|
||||
|
||||
|
|
@ -0,0 +1,563 @@
|
|||
# ip2region xdb C++ 实现
|
||||
|
||||
## 0. 文件说明
|
||||
```
|
||||
Makefile --------- 构建
|
||||
|
||||
src ------------------ 源文件目录
|
||||
src/base.* ----------- 常量及工具函数
|
||||
src/ip.* ------------- 实现 IP 处理
|
||||
src/header.* --------- 实现 xdb 头部解析
|
||||
src/search.* --------- 实现 xdb 查找
|
||||
src/bench.* ---------- 实现 查找 测速
|
||||
src/make.* ----------- 实现 生成 xdb 文件
|
||||
src/edit.* ----------- 实现 原始数据编辑
|
||||
|
||||
test ---------------- 测试目录
|
||||
test/header.cc ------ 测试 头部
|
||||
test/search.cc ------ 测试 查找
|
||||
test/bench.cc ------- 测速
|
||||
test/make.cc -------- 生成 xdb 文件
|
||||
test/edit_v4.cc ----- 测试 原始数据编辑(ipv4)
|
||||
test/edit_v6.cc ----- 测试 原始数据编辑(ipv6)
|
||||
|
||||
|
||||
bin --------------- 可执行文件目录(通过 make 生成)
|
||||
bin/header -------- 测试 头部
|
||||
bin/search -------- 测试 查找
|
||||
bin/bench --------- 测速
|
||||
bin/make ---------- 生成 xdb 文件
|
||||
bin/edit_v4 ------- 测试 原始数据编辑(ipv4)
|
||||
bin/edit_v6 ------- 测试 原始数据编辑(ipv6)
|
||||
|
||||
readme.md --------- readme
|
||||
```
|
||||
|
||||
## 1. 编译
|
||||
```
|
||||
$ make
|
||||
```
|
||||
|
||||
## 2. 查找
|
||||
### 2.1 示例
|
||||
```
|
||||
#include "src/search.h"
|
||||
|
||||
// IP 版本: xdb::ipv4 xdb::ipv6
|
||||
// 策略: xdb::policy_file xdb::policy_vector xdb::policy_content
|
||||
// 不缓存 部分缓存 全部缓存
|
||||
int main() {
|
||||
std::string xdb_name = "../../data/ip2region_v6.xdb";
|
||||
int version = xdb::ipv6;
|
||||
int policy = xdb::policy_content;
|
||||
std::string ip = "2001:200:124::";
|
||||
|
||||
xdb::search_t s(xdb_name, version, policy);
|
||||
std::cout << s.search(ip) << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// $ g++ src/*.cc 1.cc --- 编译
|
||||
// $ ./a.out ------------- 测试
|
||||
// 日本|东京都|千代田区|专线用户
|
||||
```
|
||||
|
||||
### 2.2 测试 xdb 头部
|
||||
```
|
||||
$ ./bin/header
|
||||
测试 IPv4
|
||||
版本号: 3
|
||||
缓存策略: 1
|
||||
文件生成时间: 2025-09-06 02:24:16
|
||||
索引起始地址: 955933
|
||||
索引结束地址: 11042415
|
||||
IP版本: 4
|
||||
指针字节数: 4
|
||||
|
||||
测试 IPv6
|
||||
版本号: 3
|
||||
缓存策略: 1
|
||||
文件生成时间: 2025-10-17 04:41:04
|
||||
索引起始地址: 3094259
|
||||
索引结束地址: 36258303
|
||||
IP版本: 6
|
||||
指针字节数: 4
|
||||
```
|
||||
|
||||
### 2.3 测试查找
|
||||
```
|
||||
$ ./bin/search
|
||||
测试 IPv4 不缓存: 成功
|
||||
测试 IPv4 部分缓存: 成功
|
||||
测试 IPv4 全部缓存: 成功
|
||||
测试 IPv6 不缓存: 成功
|
||||
测试 IPv6 部分缓存: 成功
|
||||
测试 IPv6 全部缓存: 成功
|
||||
```
|
||||
|
||||
## 3. 测速以及检验正确性
|
||||
```
|
||||
./bin/bench
|
||||
测试 IPv4, 不缓存, total: 3910284, took: 27.60s, cost: 6.59μs/op, io count: 28227147
|
||||
测试 IPv4, 部分缓存, total: 3910284, took: 21.85s, cost: 5.15μs/op, io count: 24316863
|
||||
测试 IPv4, 全部缓存, total: 3910284, took: 2.26s, cost: 0.25μs/op, io count: 0
|
||||
测试 IPv6, 不缓存, total: 4792520, took: 100.40s, cost: 20.22μs/op, io count: 80758866
|
||||
测试 IPv6, 部分缓存, total: 4792520, took: 93.06s, cost: 18.71μs/op, io count: 75966346
|
||||
测试 IPv6, 全部缓存, total: 4792520, took: 6.24s, cost: 0.81μs/op, io count: 0
|
||||
```
|
||||
|
||||
## 4. 生成 xdb 文件
|
||||
### 4.1 生成 xdb 文件
|
||||
```
|
||||
$ ./bin/make
|
||||
生成 ipv4 的 xdb 文件, took: 0.57s
|
||||
生成 ipv6 的 xdb 文件, took: 1.24s
|
||||
```
|
||||
|
||||
### 4.2 测试正确性
|
||||
```
|
||||
# ipv4 --- 只有时间不同
|
||||
$ diff <(xxd ./ip2region_v4.xdb) <(xxd ../../data/ip2region_v4.xdb)
|
||||
1c1
|
||||
< 00000000: 0300 0100 9f2f 2969 1d96 0e00 6f7e a800 ...../)i....o~..
|
||||
---
|
||||
> 00000000: 0300 0100 509b bb68 1d96 0e00 6f7e a800 ....P..h....o~..
|
||||
|
||||
# ipv6 --- 只有时间不同
|
||||
$ diff <(xxd ./ip2region_v6.xdb) <(xxd ../../data/ip2region_v6.xdb)
|
||||
1c1
|
||||
< 00000000: 0300 0100 a02f 2969 f336 2f00 ff41 2902 ...../)i.6/..A).
|
||||
---
|
||||
> 00000000: 0300 0100 e0c8 f168 f336 2f00 ff41 2902 .......h.6/..A).
|
||||
```
|
||||
|
||||
## 5. 原始数据编辑
|
||||
### 5.1. 使用说明
|
||||
* 新的IP归属地文件可以包含空行
|
||||
* 新的IP归属地文件顺序可以乱序, 程序会自动排序
|
||||
* 新的IP归属地文件顺序可以重叠, 只要无二义性, 程序会自动合并
|
||||
* 最终的结果会将相邻的且归属地相同的行自动合并
|
||||
* 以下测试, 原文件使用仓库自带的数据文件, 新文件使用当前目录下的 1.txt
|
||||
|
||||
### 5.2. 数据正确性测试 -- ipv4
|
||||
#### 测试一: 测试数据文件包含空行以及重复的情况
|
||||
```
|
||||
$ cat -n 1.txt
|
||||
1
|
||||
2 1.0.128.0|1.0.128.255|测试归属地
|
||||
3
|
||||
4 1.0.128.0|1.0.128.255|测试归属地
|
||||
5
|
||||
$ ./bin/edit_v4
|
||||
took: 0.80s
|
||||
$ git diff ../../data/
|
||||
diff --git a/data/ipv4_source.txt b/data/ipv4_source.txt
|
||||
index 00dacc3..5d1fdfa 100644
|
||||
--- a/data/ipv4_source.txt
|
||||
+++ b/data/ipv4_source.txt
|
||||
@@ -7,7 +7,7 @@
|
||||
1.0.32.0|1.0.63.255|中国|广东省|广州市|电信
|
||||
1.0.64.0|1.0.79.255|日本|广岛县|0|0
|
||||
1.0.80.0|1.0.127.255|日本|冈山县|0|0
|
||||
-1.0.128.0|1.0.128.255|泰国|清莱府|0|TOT
|
||||
+1.0.128.0|1.0.128.255|测试归属地
|
||||
1.0.129.0|1.0.132.191|泰国|曼谷|曼谷|TOT
|
||||
1.0.132.192|1.0.132.255|泰国|Nakhon-Ratchasima|0|TOT
|
||||
1.0.133.0|1.0.133.255|泰国|素攀武里府|0|TOT
|
||||
```
|
||||
|
||||
#### 测试二: 测试数据文件乱序以及数据有交叉, 归属地相同的情况
|
||||
```
|
||||
$ cat 1.txt
|
||||
1.0.128.5|1.0.128.255|测试归属地
|
||||
1.0.128.0|1.0.128.9|测试归属地
|
||||
$ ./bin/edit_v4
|
||||
took: 0.88s
|
||||
$ git diff ../../data/
|
||||
diff --git a/data/ipv4_source.txt b/data/ipv4_source.txt
|
||||
index 00dacc3..5d1fdfa 100644
|
||||
--- a/data/ipv4_source.txt
|
||||
+++ b/data/ipv4_source.txt
|
||||
@@ -7,7 +7,7 @@
|
||||
1.0.32.0|1.0.63.255|中国|广东省|广州市|电信
|
||||
1.0.64.0|1.0.79.255|日本|广岛县|0|0
|
||||
1.0.80.0|1.0.127.255|日本|冈山县|0|0
|
||||
-1.0.128.0|1.0.128.255|泰国|清莱府|0|TOT
|
||||
+1.0.128.0|1.0.128.255|测试归属地
|
||||
1.0.129.0|1.0.132.191|泰国|曼谷|曼谷|TOT
|
||||
1.0.132.192|1.0.132.255|泰国|Nakhon-Ratchasima|0|TOT
|
||||
1.0.133.0|1.0.133.255|泰国|素攀武里府|0|TOT
|
||||
```
|
||||
|
||||
#### 测试三: 测试数据文件重叠, 归属地相同的情况
|
||||
```
|
||||
$ cat 1.txt
|
||||
1.0.128.0|1.0.128.8|测试归属地
|
||||
1.0.128.7|1.0.128.255|测试归属地
|
||||
$ ./bin/edit_v4
|
||||
took: 0.91s
|
||||
$ git diff ../../data/
|
||||
diff --git a/data/ipv4_source.txt b/data/ipv4_source.txt
|
||||
index 00dacc3..5d1fdfa 100644
|
||||
--- a/data/ipv4_source.txt
|
||||
+++ b/data/ipv4_source.txt
|
||||
@@ -7,7 +7,7 @@
|
||||
1.0.32.0|1.0.63.255|中国|广东省|广州市|电信
|
||||
1.0.64.0|1.0.79.255|日本|广岛县|0|0
|
||||
1.0.80.0|1.0.127.255|日本|冈山县|0|0
|
||||
-1.0.128.0|1.0.128.255|泰国|清莱府|0|TOT
|
||||
+1.0.128.0|1.0.128.255|测试归属地
|
||||
1.0.129.0|1.0.132.191|泰国|曼谷|曼谷|TOT
|
||||
1.0.132.192|1.0.132.255|泰国|Nakhon-Ratchasima|0|TOT
|
||||
1.0.133.0|1.0.133.255|泰国|素攀武里府|0|TOT
|
||||
```
|
||||
|
||||
#### 测试四: 测试数据文件重叠, 归属地相同的情况
|
||||
```
|
||||
$ cat 1.txt
|
||||
1.0.128.0|1.0.128.8|测试归属地
|
||||
1.0.128.8|1.0.128.255|测试归属地
|
||||
$ ./bin/edit_v4
|
||||
took: 0.81s
|
||||
git diff ../../data
|
||||
diff --git a/data/ipv4_source.txt b/data/ipv4_source.txt
|
||||
index 00dacc3..5d1fdfa 100644
|
||||
--- a/data/ipv4_source.txt
|
||||
+++ b/data/ipv4_source.txt
|
||||
@@ -7,7 +7,7 @@
|
||||
1.0.32.0|1.0.63.255|中国|广东省|广州市|电信
|
||||
1.0.64.0|1.0.79.255|日本|广岛县|0|0
|
||||
1.0.80.0|1.0.127.255|日本|冈山县|0|0
|
||||
-1.0.128.0|1.0.128.255|泰国|清莱府|0|TOT
|
||||
+1.0.128.0|1.0.128.255|测试归属地
|
||||
1.0.129.0|1.0.132.191|泰国|曼谷|曼谷|TOT
|
||||
1.0.132.192|1.0.132.255|泰国|Nakhon-Ratchasima|0|TOT
|
||||
1.0.133.0|1.0.133.255|泰国|素攀武里府|0|TOT
|
||||
```
|
||||
|
||||
#### 测试五: 测试数据文件连接, 归属地相同的情况
|
||||
```
|
||||
$ cat 1.txt
|
||||
1.0.128.0|1.0.128.8|测试归属地
|
||||
1.0.128.9|1.0.128.255|测试归属地
|
||||
$ ./bin/edit_v4
|
||||
took: 0.71s
|
||||
git diff ../../data
|
||||
diff --git a/data/ipv4_source.txt b/data/ipv4_source.txt
|
||||
index 00dacc3..5d1fdfa 100644
|
||||
--- a/data/ipv4_source.txt
|
||||
+++ b/data/ipv4_source.txt
|
||||
@@ -7,7 +7,7 @@
|
||||
1.0.32.0|1.0.63.255|中国|广东省|广州市|电信
|
||||
1.0.64.0|1.0.79.255|日本|广岛县|0|0
|
||||
1.0.80.0|1.0.127.255|日本|冈山县|0|0
|
||||
-1.0.128.0|1.0.128.255|泰国|清莱府|0|TOT
|
||||
+1.0.128.0|1.0.128.255|测试归属地
|
||||
1.0.129.0|1.0.132.191|泰国|曼谷|曼谷|TOT
|
||||
1.0.132.192|1.0.132.255|泰国|Nakhon-Ratchasima|0|TOT
|
||||
1.0.133.0|1.0.133.255|泰国|素攀武里府|0|TOT
|
||||
```
|
||||
|
||||
#### 测试六: 测试数据文件重叠, 归属地不同情况
|
||||
```
|
||||
$ cat 1.txt
|
||||
1.0.128.0|1.0.128.8|测试归属地123
|
||||
1.0.128.8|1.0.128.255|测试归属地
|
||||
$ ./bin/edit_v4
|
||||
数据有二义性: 1.0.128.0|1.0.128.8|测试归属地123, 1.0.128.8|1.0.128.255|测试归属地
|
||||
```
|
||||
|
||||
#### 测试七: 测试数据文件连接, 归属地不同情况
|
||||
```
|
||||
$ cat 1.txt
|
||||
1.0.128.0|1.0.128.8|测试归属地123
|
||||
1.0.128.9|1.0.128.255|测试归属地
|
||||
$ ./bin/edit_v4
|
||||
took: 0.75s
|
||||
git diff ../../data
|
||||
diff --git a/data/ipv4_source.txt b/data/ipv4_source.txt
|
||||
index 00dacc3..976e6bf 100644
|
||||
--- a/data/ipv4_source.txt
|
||||
+++ b/data/ipv4_source.txt
|
||||
@@ -7,7 +7,8 @@
|
||||
1.0.32.0|1.0.63.255|中国|广东省|广州市|电信
|
||||
1.0.64.0|1.0.79.255|日本|广岛县|0|0
|
||||
1.0.80.0|1.0.127.255|日本|冈山县|0|0
|
||||
-1.0.128.0|1.0.128.255|泰国|清莱府|0|TOT
|
||||
+1.0.128.0|1.0.128.8|测试归属地123
|
||||
+1.0.128.9|1.0.128.255|测试归属地
|
||||
1.0.129.0|1.0.132.191|泰国|曼谷|曼谷|TOT
|
||||
1.0.132.192|1.0.132.255|泰国|Nakhon-Ratchasima|0|TOT
|
||||
1.0.133.0|1.0.133.255|泰国|素攀武里府|0|TOT
|
||||
```
|
||||
|
||||
#### 测试八: 测试将一个IP数据拆成多个IP
|
||||
```
|
||||
$ cat 1.txt
|
||||
36.136.1.0|36.136.7.255|中国|0|广西|来宾市|移动
|
||||
36.136.8.0|36.136.15.255|中国|0|广西|玉林市|移动
|
||||
36.136.16.0|36.136.23.255|中国|0|广西|河池市|移动
|
||||
$ ./bin/edit_v4
|
||||
took: 0.80s
|
||||
git diff ../../data
|
||||
diff --git a/data/ipv4_source.txt b/data/ipv4_source.txt
|
||||
index 00dacc3..f895c2f 100644
|
||||
--- a/data/ipv4_source.txt
|
||||
+++ b/data/ipv4_source.txt
|
||||
@@ -54778,7 +54778,11 @@
|
||||
36.134.84.0|36.134.85.255|中国|安徽省|合肥市|移动
|
||||
36.134.86.0|36.134.87.255|中国|广西|南宁市|移动
|
||||
36.134.88.0|36.134.89.255|中国|内蒙古|呼和浩特市|移动
|
||||
-36.134.90.0|36.141.255.255|中国|0|0|移动
|
||||
+36.134.90.0|36.136.0.255|中国|0|0|移动
|
||||
+36.136.1.0|36.136.7.255|中国|0|广西|来宾市|移动
|
||||
+36.136.8.0|36.136.15.255|中国|0|广西|玉林市|移动
|
||||
+36.136.16.0|36.136.23.255|中国|0|广西|河池市|移动
|
||||
+36.136.24.0|36.141.255.255|中国|0|0|移动
|
||||
36.142.0.0|36.142.1.255|中国|四川省|成都市|移动
|
||||
36.142.2.0|36.142.31.255|中国|甘肃省|兰州市|移动
|
||||
36.142.32.0|36.142.127.255|中国|甘肃省|0|移动
|
||||
```
|
||||
|
||||
#### 测试九: 测试将多个IP数据并成一个IP数据
|
||||
```
|
||||
$ cat 1.txt
|
||||
1.0.16.0|1.0.127.255|测试归属地
|
||||
$ ./bin/edit_v4
|
||||
took: 0.76s
|
||||
git diff ../../data
|
||||
diff --git a/data/ipv4_source.txt b/data/ipv4_source.txt
|
||||
index 00dacc3..756354c 100644
|
||||
--- a/data/ipv4_source.txt
|
||||
+++ b/data/ipv4_source.txt
|
||||
@@ -3,10 +3,7 @@
|
||||
1.0.1.0|1.0.3.255|中国|福建省|福州市|电信
|
||||
1.0.4.0|1.0.7.255|澳大利亚|维多利亚|墨尔本|0
|
||||
1.0.8.0|1.0.15.255|中国|广东省|广州市|电信
|
||||
-1.0.16.0|1.0.31.255|日本|0|0|0
|
||||
-1.0.32.0|1.0.63.255|中国|广东省|广州市|电信
|
||||
-1.0.64.0|1.0.79.255|日本|广岛县|0|0
|
||||
-1.0.80.0|1.0.127.255|日本|冈山县|0|0
|
||||
+1.0.16.0|1.0.127.255|测试归属地
|
||||
1.0.128.0|1.0.128.255|泰国|清莱府|0|TOT
|
||||
1.0.129.0|1.0.132.191|泰国|曼谷|曼谷|TOT
|
||||
1.0.132.192|1.0.132.255|泰国|Nakhon-Ratchasima|0|TOT
|
||||
```
|
||||
|
||||
### 5.3 数据正确性测试 -- ipv6
|
||||
#### 测试一: 测试数据文件包含空行以及重复的情况
|
||||
```
|
||||
$ cat -n 1.txt
|
||||
1
|
||||
2 2001:200:120::|2001:200:122:ffff:ffff:ffff:ffff:ffff|测试归属地
|
||||
3
|
||||
4 2001:200:120::|2001:200:122:ffff:ffff:ffff:ffff:ffff|测试归属地
|
||||
5
|
||||
$ ./bin/edit_v6
|
||||
took: 1.74s
|
||||
git diff ../../data
|
||||
diff --git a/data/ipv6_source.txt b/data/ipv6_source.txt
|
||||
index 4dee31b..29617c4 100644
|
||||
--- a/data/ipv6_source.txt
|
||||
+++ b/data/ipv6_source.txt
|
||||
@@ -2,7 +2,7 @@
|
||||
2001:200::|2001:200:101:ffff:ffff:ffff:ffff:ffff|日本|神奈川县|藤泽市|专线用户
|
||||
2001:200:102::|2001:200:104:ffff:ffff:ffff:ffff:ffff|日本|东京都|千代田区|专线用户
|
||||
2001:200:105::|2001:200:11f:ffff:ffff:ffff:ffff:ffff|日本|神奈川县|藤泽市|专线用户
|
||||
-2001:200:120::|2001:200:122:ffff:ffff:ffff:ffff:ffff|日本|东京都|千代田区|专线用户
|
||||
+2001:200:120::|2001:200:122:ffff:ffff:ffff:ffff:ffff|测试归属地
|
||||
2001:200:123::|2001:200:123:ffff:ffff:ffff:ffff:ffff|日本|神奈川县|藤泽市|专线用户
|
||||
2001:200:124::|2001:200:129:ffff:ffff:ffff:ffff:ffff|日本|东京都|千代田区|专线用户
|
||||
2001:200:12a::|2001:200:12a:ffff:ffff:ffff:ffff:ffff|日本|神奈川县|藤泽市|专线用户
|
||||
```
|
||||
|
||||
#### 测试二: 测试数据文件乱序以及数据有交叉, 归属地相同的情况
|
||||
```
|
||||
$ cat 1.txt
|
||||
2001:200:121::|2001:200:125:ffff:ffff:ffff:ffff:ffff|测试归属地
|
||||
2001:200:120::|2001:200:122:ffff:ffff:ffff:ffff:ffff|测试归属地
|
||||
$ ./bin/edit_v6
|
||||
took: 1.68s
|
||||
git diff ../../data
|
||||
diff --git a/data/ipv6_source.txt b/data/ipv6_source.txt
|
||||
index 4dee31b..9e83b03 100644
|
||||
--- a/data/ipv6_source.txt
|
||||
+++ b/data/ipv6_source.txt
|
||||
@@ -2,9 +2,8 @@
|
||||
2001:200::|2001:200:101:ffff:ffff:ffff:ffff:ffff|日本|神奈川县|藤泽市|专线用户
|
||||
2001:200:102::|2001:200:104:ffff:ffff:ffff:ffff:ffff|日本|东京都|千代田区|专线用户
|
||||
2001:200:105::|2001:200:11f:ffff:ffff:ffff:ffff:ffff|日本|神奈川县|藤泽市|专线用户
|
||||
-2001:200:120::|2001:200:122:ffff:ffff:ffff:ffff:ffff|日本|东京都|千代田区|专线用户
|
||||
-2001:200:123::|2001:200:123:ffff:ffff:ffff:ffff:ffff|日本|神奈川县|藤泽市|专线用户
|
||||
-2001:200:124::|2001:200:129:ffff:ffff:ffff:ffff:ffff|日本|东京都|千代田区|专线用户
|
||||
+2001:200:120::|2001:200:125:ffff:ffff:ffff:ffff:ffff|测试归属地
|
||||
+2001:200:126::|2001:200:129:ffff:ffff:ffff:ffff:ffff|日本|东京都|千代田区|专线用户
|
||||
2001:200:12a::|2001:200:12a:ffff:ffff:ffff:ffff:ffff|日本|神奈川县|藤泽市|专线用户
|
||||
2001:200:12b::|2001:200:130:ffff:ffff:ffff:ffff:ffff|日本|东京都|千代田区|专线用户
|
||||
2001:200:131::|2001:200:132:ffff:ffff:ffff:ffff:ffff|日本|神奈川县|藤泽市|专线用户
|
||||
```
|
||||
|
||||
#### 测试三: 测试数据文件重叠, 归属地相同的情况
|
||||
```
|
||||
$ cat 1.txt
|
||||
2001:200:120::|2001:200:125:ffff:ffff:ffff:ffff:ffff|测试归属地
|
||||
2001:200:125::|2001:200:126:ffff:ffff:ffff:ffff:ffff|测试归属地
|
||||
$ ./bin/edit_v6
|
||||
took: 1.75s
|
||||
git diff ../../data
|
||||
diff --git a/data/ipv6_source.txt b/data/ipv6_source.txt
|
||||
index 4dee31b..7a23ba2 100644
|
||||
--- a/data/ipv6_source.txt
|
||||
+++ b/data/ipv6_source.txt
|
||||
@@ -2,9 +2,8 @@
|
||||
2001:200::|2001:200:101:ffff:ffff:ffff:ffff:ffff|日本|神奈川县|藤泽市|专线用户
|
||||
2001:200:102::|2001:200:104:ffff:ffff:ffff:ffff:ffff|日本|东京都|千代田区|专线用户
|
||||
2001:200:105::|2001:200:11f:ffff:ffff:ffff:ffff:ffff|日本|神奈川县|藤泽市|专线用户
|
||||
-2001:200:120::|2001:200:122:ffff:ffff:ffff:ffff:ffff|日本|东京都|千代田区|专线用户
|
||||
-2001:200:123::|2001:200:123:ffff:ffff:ffff:ffff:ffff|日本|神奈川县|藤泽市|专线用户
|
||||
-2001:200:124::|2001:200:129:ffff:ffff:ffff:ffff:ffff|日本|东京都|千代田区|专线用户
|
||||
+2001:200:120::|2001:200:126:ffff:ffff:ffff:ffff:ffff|测试归属地
|
||||
+2001:200:127::|2001:200:129:ffff:ffff:ffff:ffff:ffff|日本|东京都|千代田区|专线用户
|
||||
2001:200:12a::|2001:200:12a:ffff:ffff:ffff:ffff:ffff|日本|神奈川县|藤泽市|专线用户
|
||||
2001:200:12b::|2001:200:130:ffff:ffff:ffff:ffff:ffff|日本|东京都|千代田区|专线用户
|
||||
2001:200:131::|2001:200:132:ffff:ffff:ffff:ffff:ffff|日本|神奈川县|藤泽市|专线用户
|
||||
```
|
||||
|
||||
#### 测试四: 测试数据文件重叠, 归属地相同的情况
|
||||
```
|
||||
$ cat 1.txt
|
||||
2001:200:120::|2001:200:125::|测试归属地
|
||||
2001:200:125::|2001:200:126:ffff:ffff:ffff:ffff:ffff|测试归属地
|
||||
$ ./bin/edit_v6
|
||||
took: 1.46s
|
||||
git diff ../../data
|
||||
diff --git a/data/ipv6_source.txt b/data/ipv6_source.txt
|
||||
index 4dee31b..7a23ba2 100644
|
||||
--- a/data/ipv6_source.txt
|
||||
+++ b/data/ipv6_source.txt
|
||||
@@ -2,9 +2,8 @@
|
||||
2001:200::|2001:200:101:ffff:ffff:ffff:ffff:ffff|日本|神奈川县|藤泽市|专线用户
|
||||
2001:200:102::|2001:200:104:ffff:ffff:ffff:ffff:ffff|日本|东京都|千代田区|专线用户
|
||||
2001:200:105::|2001:200:11f:ffff:ffff:ffff:ffff:ffff|日本|神奈川县|藤泽市|专线用户
|
||||
-2001:200:120::|2001:200:122:ffff:ffff:ffff:ffff:ffff|日本|东京都|千代田区|专线用户
|
||||
-2001:200:123::|2001:200:123:ffff:ffff:ffff:ffff:ffff|日本|神奈川县|藤泽市|专线用户
|
||||
-2001:200:124::|2001:200:129:ffff:ffff:ffff:ffff:ffff|日本|东京都|千代田区|专线用户
|
||||
+2001:200:120::|2001:200:126:ffff:ffff:ffff:ffff:ffff|测试归属地
|
||||
+2001:200:127::|2001:200:129:ffff:ffff:ffff:ffff:ffff|日本|东京都|千代田区|专线用户
|
||||
2001:200:12a::|2001:200:12a:ffff:ffff:ffff:ffff:ffff|日本|神奈川县|藤泽市|专线用户
|
||||
2001:200:12b::|2001:200:130:ffff:ffff:ffff:ffff:ffff|日本|东京都|千代田区|专线用户
|
||||
2001:200:131::|2001:200:132:ffff:ffff:ffff:ffff:ffff|日本|神奈川县|藤泽市|专线用户
|
||||
```
|
||||
|
||||
#### 测试五: 测试数据文件连接, 归属地相同的情况
|
||||
```
|
||||
$ cat 1.txt
|
||||
2001:200:120::|2001:200:125:ffff:ffff:ffff:ffff:ffff|测试归属地
|
||||
2001:200:126::|2001:200:126:ffff:ffff:ffff:ffff:ffff|测试归属地
|
||||
$ ./bin/edit_v6
|
||||
took: 1.79s
|
||||
git diff ../../data
|
||||
diff --git a/data/ipv6_source.txt b/data/ipv6_source.txt
|
||||
index 4dee31b..7a23ba2 100644
|
||||
--- a/data/ipv6_source.txt
|
||||
+++ b/data/ipv6_source.txt
|
||||
@@ -2,9 +2,8 @@
|
||||
2001:200::|2001:200:101:ffff:ffff:ffff:ffff:ffff|日本|神奈川县|藤泽市|专线用户
|
||||
2001:200:102::|2001:200:104:ffff:ffff:ffff:ffff:ffff|日本|东京都|千代田区|专线用户
|
||||
2001:200:105::|2001:200:11f:ffff:ffff:ffff:ffff:ffff|日本|神奈川县|藤泽市|专线用户
|
||||
-2001:200:120::|2001:200:122:ffff:ffff:ffff:ffff:ffff|日本|东京都|千代田区|专线用户
|
||||
-2001:200:123::|2001:200:123:ffff:ffff:ffff:ffff:ffff|日本|神奈川县|藤泽市|专线用户
|
||||
-2001:200:124::|2001:200:129:ffff:ffff:ffff:ffff:ffff|日本|东京都|千代田区|专线用户
|
||||
+2001:200:120::|2001:200:126:ffff:ffff:ffff:ffff:ffff|测试归属地
|
||||
+2001:200:127::|2001:200:129:ffff:ffff:ffff:ffff:ffff|日本|东京都|千代田区|专线用户
|
||||
2001:200:12a::|2001:200:12a:ffff:ffff:ffff:ffff:ffff|日本|神奈川县|藤泽市|专线用户
|
||||
2001:200:12b::|2001:200:130:ffff:ffff:ffff:ffff:ffff|日本|东京都|千代田区|专线用户
|
||||
2001:200:131::|2001:200:132:ffff:ffff:ffff:ffff:ffff|日本|神奈川县|藤泽市|专线用户
|
||||
```
|
||||
|
||||
#### 测试六: 测试数据文件重叠, 归属地不同情况
|
||||
```
|
||||
$ cat 1.txt
|
||||
2001:200:120::|2001:200:126::|测试归属地123
|
||||
2001:200:126::|2001:200:126:ffff:ffff:ffff:ffff:ffff|测试归属地
|
||||
$ ./bin/edit_v6
|
||||
数据有二义性: 2001:200:120::|2001:200:126::|测试归属地123, 2001:200:126::|2001:200:126:ffff:ffff:ffff:ffff:ffff|测试归属地
|
||||
```
|
||||
|
||||
#### 测试七: 测试数据文件连接, 归属地不同情况
|
||||
```
|
||||
$ cat 1.txt
|
||||
2001:200:120::|2001:200:125:ffff:ffff:ffff:ffff:ffff|测试归属地123
|
||||
2001:200:126::|2001:200:126:ffff:ffff:ffff:ffff:ffff|测试归属地
|
||||
$ ./bin/edit_v6
|
||||
took: 1.78s
|
||||
git diff ../../data
|
||||
diff --git a/data/ipv6_source.txt b/data/ipv6_source.txt
|
||||
index 4dee31b..142f7cc 100644
|
||||
--- a/data/ipv6_source.txt
|
||||
+++ b/data/ipv6_source.txt
|
||||
@@ -2,9 +2,9 @@
|
||||
2001:200::|2001:200:101:ffff:ffff:ffff:ffff:ffff|日本|神奈川县|藤泽市|专线用户
|
||||
2001:200:102::|2001:200:104:ffff:ffff:ffff:ffff:ffff|日本|东京都|千代田区|专线用户
|
||||
2001:200:105::|2001:200:11f:ffff:ffff:ffff:ffff:ffff|日本|神奈川县|藤泽市|专线用户
|
||||
-2001:200:120::|2001:200:122:ffff:ffff:ffff:ffff:ffff|日本|东京都|千代田区|专线用户
|
||||
-2001:200:123::|2001:200:123:ffff:ffff:ffff:ffff:ffff|日本|神奈川县|藤泽市|专线用户
|
||||
-2001:200:124::|2001:200:129:ffff:ffff:ffff:ffff:ffff|日本|东京都|千代田区|专线用户
|
||||
+2001:200:120::|2001:200:125:ffff:ffff:ffff:ffff:ffff|测试归属地123
|
||||
+2001:200:126::|2001:200:126:ffff:ffff:ffff:ffff:ffff|测试归属地
|
||||
+2001:200:127::|2001:200:129:ffff:ffff:ffff:ffff:ffff|日本|东京都|千代田区|专线用户
|
||||
2001:200:12a::|2001:200:12a:ffff:ffff:ffff:ffff:ffff|日本|神奈川县|藤泽市|专线用户
|
||||
2001:200:12b::|2001:200:130:ffff:ffff:ffff:ffff:ffff|日本|东京都|千代田区|专线用户
|
||||
2001:200:131::|2001:200:132:ffff:ffff:ffff:ffff:ffff|日本|神奈川县|藤泽市|专线用户
|
||||
```
|
||||
|
||||
#### 测试八: 测试将一个IP数据拆成多个IP
|
||||
```
|
||||
$ cat 1.txt
|
||||
2001:200:105::|2001:200:11f:ffff:ffff:ffff:ffff:ff11|测试归属地1
|
||||
2001:200:11f:ffff:ffff:ffff:ffff:ff12|2001:200:11f:ffff:ffff:ffff:ffff:ff33|测试归属地2
|
||||
2001:200:11f:ffff:ffff:ffff:ffff:ff34|2001:200:11f:ffff:ffff:ffff:ffff:ffff|测试归属地3
|
||||
$ ./bin/edit_v6
|
||||
took: 1.52s
|
||||
git diff ../../data
|
||||
diff --git a/data/ipv6_source.txt b/data/ipv6_source.txt
|
||||
index 4dee31b..e450e27 100644
|
||||
--- a/data/ipv6_source.txt
|
||||
+++ b/data/ipv6_source.txt
|
||||
@@ -1,7 +1,9 @@
|
||||
1:1::|2001:1ff:ffff:ffff:ffff:ffff:ffff:ffff|0|0|内网IP|内网IP
|
||||
2001:200::|2001:200:101:ffff:ffff:ffff:ffff:ffff|日本|神奈川县|藤泽市|专线用户
|
||||
2001:200:102::|2001:200:104:ffff:ffff:ffff:ffff:ffff|日本|东京都|千代田区|专线用户
|
||||
-2001:200:105::|2001:200:11f:ffff:ffff:ffff:ffff:ffff|日本|神奈川县|藤泽市|专线用户
|
||||
+2001:200:105::|2001:200:11f:ffff:ffff:ffff:ffff:ff11|测试归属地1
|
||||
+2001:200:11f:ffff:ffff:ffff:ffff:ff12|2001:200:11f:ffff:ffff:ffff:ffff:ff33|测试归属地2
|
||||
+2001:200:11f:ffff:ffff:ffff:ffff:ff34|2001:200:11f:ffff:ffff:ffff:ffff:ffff|测试归属地3
|
||||
2001:200:120::|2001:200:122:ffff:ffff:ffff:ffff:ffff|日本|东京都|千代田区|专线用户
|
||||
2001:200:123::|2001:200:123:ffff:ffff:ffff:ffff:ffff|日本|神奈川县|藤泽市|专线用户
|
||||
2001:200:124::|2001:200:129:ffff:ffff:ffff:ffff:ffff|日本|东京都|千代田区|专线用户
|
||||
```
|
||||
|
||||
#### 测试九: 测试将多个IP数据并成一个IP数据
|
||||
```
|
||||
$ cat 1.txt
|
||||
2001:200:123::|2001:200:12a:ffff:ffff:ffff:ffff:ffff|测试归属地
|
||||
$ ./bin/edit_v6
|
||||
took: 1.99s
|
||||
git diff ../../data
|
||||
diff --git a/data/ipv6_source.txt b/data/ipv6_source.txt
|
||||
index 4dee31b..ecd29c3 100644
|
||||
--- a/data/ipv6_source.txt
|
||||
+++ b/data/ipv6_source.txt
|
||||
@@ -3,9 +3,7 @@
|
||||
2001:200:102::|2001:200:104:ffff:ffff:ffff:ffff:ffff|日本|东京都|千代田区|专线用户
|
||||
2001:200:105::|2001:200:11f:ffff:ffff:ffff:ffff:ffff|日本|神奈川县|藤泽市|专线用户
|
||||
2001:200:120::|2001:200:122:ffff:ffff:ffff:ffff:ffff|日本|东京都|千代田区|专线用户
|
||||
-2001:200:123::|2001:200:123:ffff:ffff:ffff:ffff:ffff|日本|神奈川县|藤泽市|专线用户
|
||||
-2001:200:124::|2001:200:129:ffff:ffff:ffff:ffff:ffff|日本|东京都|千代田区|专线用户
|
||||
-2001:200:12a::|2001:200:12a:ffff:ffff:ffff:ffff:ffff|日本|神奈川县|藤泽市|专线用户
|
||||
+2001:200:123::|2001:200:12a:ffff:ffff:ffff:ffff:ffff|测试归属地
|
||||
2001:200:12b::|2001:200:130:ffff:ffff:ffff:ffff:ffff|日本|东京都|千代田区|专线用户
|
||||
2001:200:131::|2001:200:132:ffff:ffff:ffff:ffff:ffff|日本|神奈川县|藤泽市|专线用户
|
||||
2001:200:133::|2001:200:135:ffff:ffff:ffff:ffff:ffff|日本|东京都|千代田区|专线用户
|
||||
```
|
||||
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
|
||||
#include "base.h"
|
||||
|
||||
namespace xdb {
|
||||
|
||||
int ip_version; // ip 版本
|
||||
int ip_size; // ip 占的字节数
|
||||
int content_size;
|
||||
|
||||
void init_xdb(int version) {
|
||||
ip_version = version;
|
||||
ip_size = version == ipv4 ? 4 : 16;
|
||||
content_size = ip_size * 2 + 2 + 4;
|
||||
}
|
||||
|
||||
void log_exit(const string &msg) {
|
||||
std::cout << msg << std::endl;
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
void read_bin(int index, char *buf, size_t len, FILE *db) {
|
||||
fseek(db, index, SEEK_SET);
|
||||
if (fread(buf, 1, len, db) != len)
|
||||
log_exit(__func__);
|
||||
}
|
||||
|
||||
unsigned to_uint(const char *buf) {
|
||||
return ((buf[0]) & 0x000000FF) | ((buf[1] << 8) & 0x0000FF00) |
|
||||
((buf[2] << 16) & 0x00FF0000) | ((buf[3] << 24) & 0xFF000000);
|
||||
}
|
||||
|
||||
unsigned to_ushort(const char *buf) {
|
||||
return ((buf[0]) & 0x000000FF) | ((buf[1] << 8) & 0x0000FF00);
|
||||
}
|
||||
|
||||
unsigned to_int(const char *buf, int n) {
|
||||
return n == 2 ? to_ushort(buf) : to_uint(buf);
|
||||
}
|
||||
|
||||
void write_uint(unsigned data, char buf[]) {
|
||||
buf[0] = (data >> 0) & 0xFF;
|
||||
buf[1] = (data >> 8) & 0xFF;
|
||||
buf[2] = (data >> 16) & 0xFF;
|
||||
buf[3] = (data >> 24) & 0xFF;
|
||||
}
|
||||
|
||||
void write_uint(unsigned data, FILE *dst) {
|
||||
char buf[4];
|
||||
write_uint(data, buf);
|
||||
fwrite(buf, 1, sizeof(buf), dst);
|
||||
}
|
||||
|
||||
void write_ushort(unsigned data, char buf[]) {
|
||||
buf[0] = (data >> 0) & 0xFF;
|
||||
buf[1] = (data >> 8) & 0xFF;
|
||||
}
|
||||
|
||||
void write_ushort(unsigned data, FILE *dst) {
|
||||
char buf[2];
|
||||
write_ushort(data, buf);
|
||||
fwrite(buf, 1, sizeof(buf), dst);
|
||||
}
|
||||
|
||||
void write_string(const char *buf, unsigned len, FILE *dst) {
|
||||
fwrite(buf, 1, len, dst);
|
||||
}
|
||||
|
||||
unsigned long long get_time() {
|
||||
struct timeval tv1;
|
||||
gettimeofday(&tv1, NULL);
|
||||
return (unsigned long long)tv1.tv_sec * 1000 * 1000 + tv1.tv_usec;
|
||||
}
|
||||
|
||||
} // namespace xdb
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
#ifndef BASE_H
|
||||
#define BASE_H
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace xdb {
|
||||
|
||||
using std::string;
|
||||
|
||||
constexpr int ipv4 = 4;
|
||||
constexpr int ipv6 = 6;
|
||||
|
||||
constexpr int policy_file = 0;
|
||||
constexpr int policy_vector = 1;
|
||||
constexpr int policy_content = 2;
|
||||
|
||||
constexpr int length_header = 256;
|
||||
constexpr int length_vector = 256 * 256 * 8;
|
||||
|
||||
extern int ip_version; // ip 版本
|
||||
extern int ip_size; // ip 占的字节数
|
||||
extern int content_size;
|
||||
|
||||
void init_xdb(int version);
|
||||
|
||||
void log_exit(const string &msg);
|
||||
|
||||
void read_bin(int index, char *buf, size_t len, FILE *db);
|
||||
|
||||
unsigned to_uint(const char *buf);
|
||||
unsigned to_ushort(const char *buf);
|
||||
unsigned to_int(const char *buf, int n);
|
||||
|
||||
void write_uint(unsigned data, char buf[]);
|
||||
void write_uint(unsigned data, FILE *dst);
|
||||
|
||||
void write_ushort(unsigned data, char buf[]);
|
||||
void write_ushort(unsigned data, FILE *dst);
|
||||
|
||||
void write_string(const char *buf, unsigned len, FILE *dst);
|
||||
|
||||
unsigned long long get_time();
|
||||
|
||||
} // namespace xdb
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
|
||||
#include "bench.h"
|
||||
|
||||
namespace xdb {
|
||||
|
||||
bench_t::bench_t(const std::string &file_name, int version, int policy)
|
||||
: search(file_name, version, policy) {
|
||||
}
|
||||
|
||||
void bench_t::test_one(const ip_t &ip, const string region) {
|
||||
if (search.search(ip.to_string()) != region)
|
||||
xdb::log_exit("failed: " + ip.to_string() + " " + region);
|
||||
sum_io_count += search.get_io_count();
|
||||
sum_cost_time += search.get_cost_time();
|
||||
sum_count++;
|
||||
}
|
||||
|
||||
void bench_t::test_line(char *buf) {
|
||||
size_t buf_len = strlen(buf);
|
||||
if (buf_len == 0)
|
||||
return;
|
||||
buf[buf_len - 1] = '\0'; // 去掉换行符
|
||||
|
||||
node_t node(buf);
|
||||
|
||||
// 只测五个
|
||||
for (int i = 0; i < 5 && node.ip1 < node.ip2; ++i) {
|
||||
test_one(node.ip1, node.region);
|
||||
node.ip1 = node.ip1 + 1;
|
||||
}
|
||||
test_one(node.ip2, node.region);
|
||||
}
|
||||
|
||||
void bench_t::test_file(const std::string &file_name) {
|
||||
FILE *f = fopen(file_name.data(), "r");
|
||||
if (f == NULL)
|
||||
xdb::log_exit("can't open " + file_name);
|
||||
char buf[1024];
|
||||
while (fgets(buf, sizeof(buf), f) != NULL)
|
||||
test_line(buf);
|
||||
}
|
||||
|
||||
void bench_t::test(const string &file_name) {
|
||||
sum_io_count = 0;
|
||||
sum_cost_time = 0;
|
||||
sum_count = 0;
|
||||
|
||||
unsigned long long tv1 = xdb::get_time();
|
||||
test_file(file_name);
|
||||
unsigned long long tv2 = xdb::get_time();
|
||||
|
||||
double took = (tv2 - tv1) * 1.0 / 1000 / 1000;
|
||||
double cost = sum_cost_time * 1.0 / sum_count;
|
||||
|
||||
printf(
|
||||
"total: %llu, took: %8.2fs, cost: %6.2fμs/op, io "
|
||||
"count: "
|
||||
"%llu\n",
|
||||
sum_count,
|
||||
took,
|
||||
cost,
|
||||
sum_io_count);
|
||||
}
|
||||
|
||||
} // namespace xdb
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
#ifndef BENCH_H
|
||||
#define BENCH_H
|
||||
|
||||
#include "search.h"
|
||||
|
||||
namespace xdb {
|
||||
|
||||
class bench_t {
|
||||
public:
|
||||
bench_t(const string &file_name, int version, int policy);
|
||||
|
||||
void test(const string &file_name);
|
||||
|
||||
private:
|
||||
void test_one(const ip_t &ip, const string region);
|
||||
void test_line(char *buf);
|
||||
void test_file(const std::string &file_name);
|
||||
|
||||
search_t search;
|
||||
|
||||
unsigned long long sum_io_count;
|
||||
unsigned long long sum_cost_time;
|
||||
unsigned long long sum_count;
|
||||
};
|
||||
|
||||
} // namespace xdb
|
||||
#endif
|
||||
|
|
@ -0,0 +1,170 @@
|
|||
|
||||
#include "edit.h"
|
||||
|
||||
namespace xdb {
|
||||
|
||||
void handle_ip_txt(const string& name, std::list<node_t>& regions) {
|
||||
FILE* f = fopen(name.data(), "r");
|
||||
if (f == NULL)
|
||||
log_exit("can't open " + name);
|
||||
|
||||
char buf[1024];
|
||||
while (fgets(buf, sizeof(buf), f) != NULL) {
|
||||
unsigned int buf_len = strlen(buf);
|
||||
// 去掉多余的空
|
||||
while (buf_len > 0 && isspace(buf[buf_len - 1]))
|
||||
--buf_len;
|
||||
if (buf_len == 0)
|
||||
continue;
|
||||
buf[buf_len] = '\0';
|
||||
regions.push_back(node_t(buf));
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
void edit_t::handle_new_file(const std::string& file_name) {
|
||||
handle_ip_txt(file_name, new_regions); // 输入
|
||||
new_regions.sort(); // 排序
|
||||
|
||||
// 检验及其去重
|
||||
auto it = new_regions.begin();
|
||||
|
||||
for (;;) {
|
||||
if (it == new_regions.end())
|
||||
break;
|
||||
auto next = it;
|
||||
++next;
|
||||
if (next == new_regions.end())
|
||||
break;
|
||||
if (it->ip1 > it->ip2)
|
||||
it = new_regions.erase(it); // 非法, 直接跳过
|
||||
else if (it->ip1 == next->ip1 || next->ip1 <= it->ip2) {
|
||||
// 数据重叠
|
||||
if (it->region != next->region)
|
||||
log_exit("数据有二义性: " + it->to_string() + ", " +
|
||||
next->to_string());
|
||||
it->ip2 = std::max(it->ip2, next->ip2);
|
||||
new_regions.erase(next);
|
||||
} else if (it->ip2 + 1 == next->ip1 && it->region == next->region) {
|
||||
// 数据连接
|
||||
it->ip2 = next->ip2;
|
||||
new_regions.erase(next);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void edit_t::handle_old_file(const std::string& file_name) {
|
||||
handle_ip_txt(file_name, old_regions);
|
||||
}
|
||||
|
||||
void edit_t::merge() {
|
||||
auto it1 = old_regions.begin();
|
||||
auto it2 = new_regions.begin();
|
||||
|
||||
for (;;) {
|
||||
if (it2 == new_regions.end())
|
||||
break;
|
||||
if (it2->ip1 > it2->ip2) {
|
||||
++it2;
|
||||
continue;
|
||||
}
|
||||
// it1->ip1 it1->ip2 it2->ip1 it2->ip2
|
||||
while (it1->ip2 < it2->ip1)
|
||||
++it1;
|
||||
if (it1->ip2 <= it2->ip2) {
|
||||
// it1->ip1 it2->ip1 it1->ip2 it2->ip2
|
||||
node_t node;
|
||||
node.ip1 = it2->ip1;
|
||||
node.ip2 = it1->ip2;
|
||||
node.region = it2->region;
|
||||
|
||||
it1->ip2 = node.ip1 - 1;
|
||||
it2->ip1 = node.ip2 + 1;
|
||||
|
||||
++it1;
|
||||
it1 = old_regions.insert(it1, node);
|
||||
++it1;
|
||||
} else {
|
||||
// it1->ip1 it2->ip1 it2->ip2 it1->ip2
|
||||
node_t node;
|
||||
node.ip1 = it2->ip2 + 1;
|
||||
node.ip2 = it1->ip2;
|
||||
node.region = it1->region;
|
||||
|
||||
it1->ip2 = it2->ip1 - 1;
|
||||
|
||||
++it1;
|
||||
it1 = old_regions.insert(it1, *it2);
|
||||
|
||||
++it1;
|
||||
it1 = old_regions.insert(it1, node);
|
||||
|
||||
++it2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void edit_t::write_old_file(const std::string& file_name) {
|
||||
FILE* f = fopen(file_name.data(), "w");
|
||||
if (f == NULL)
|
||||
log_exit("can't open " + file_name);
|
||||
|
||||
auto it = old_regions.begin();
|
||||
|
||||
// 删除非法的数据
|
||||
for (;;) {
|
||||
if (it == old_regions.end())
|
||||
break;
|
||||
if (it->ip1 > it->ip2)
|
||||
it = old_regions.erase(it);
|
||||
else
|
||||
++it;
|
||||
}
|
||||
|
||||
// 合并数据域相同的相邻数据
|
||||
it = old_regions.begin();
|
||||
for (;;) {
|
||||
if (it == old_regions.end())
|
||||
break;
|
||||
auto next = it;
|
||||
++next;
|
||||
if (next == old_regions.end())
|
||||
break;
|
||||
if (it->region == next->region) {
|
||||
it->ip2 = next->ip2;
|
||||
old_regions.erase(next);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& d : old_regions) {
|
||||
string res =
|
||||
d.ip1.to_string() + "|" + d.ip2.to_string() + "|" + d.region + "\n";
|
||||
fputs(res.data(), f);
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
edit_t::edit_t(const string& name_old, const string& name_new, int version) {
|
||||
unsigned long long tv1 = get_time();
|
||||
|
||||
init_xdb(version);
|
||||
|
||||
handle_new_file(name_new);
|
||||
handle_old_file(name_old);
|
||||
merge();
|
||||
write_old_file(name_old);
|
||||
|
||||
unsigned long long tv2 = get_time();
|
||||
|
||||
double took = (tv2 - tv1) * 1.0 / 1000 / 1000;
|
||||
|
||||
printf("took: %.2fs\n", took);
|
||||
}
|
||||
|
||||
} // namespace xdb
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
#ifndef EDIT_H
|
||||
#define EDIT_H
|
||||
|
||||
#include "ip.h"
|
||||
|
||||
namespace xdb {
|
||||
|
||||
class edit_t {
|
||||
public:
|
||||
edit_t(const string& old_name, const string& new_name, int version);
|
||||
|
||||
private:
|
||||
void handle_new_file(const string& file_name);
|
||||
void handle_old_file(const string& file_name);
|
||||
void merge();
|
||||
void write_old_file(const string& file_name);
|
||||
|
||||
std::list<node_t> old_regions;
|
||||
std::list<node_t> new_regions;
|
||||
};
|
||||
|
||||
} // namespace xdb
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
|
||||
#include "header.h"
|
||||
|
||||
namespace xdb {
|
||||
|
||||
header_t::header_t(FILE* db) {
|
||||
read_bin(0, header, sizeof(header), db);
|
||||
}
|
||||
|
||||
header_t::~header_t() {
|
||||
}
|
||||
|
||||
int header_t::version() {
|
||||
return to_int(header, 2); // 版本号(2)
|
||||
}
|
||||
|
||||
int header_t::index_policy() {
|
||||
return to_int(header + 2, 2); // 缓存策略(2)
|
||||
}
|
||||
|
||||
int header_t::create_at() {
|
||||
return to_int(header + 4, 4); // 文件生成时间(4)
|
||||
}
|
||||
|
||||
int header_t::index_start() {
|
||||
return to_int(header + 8, 4); // 索引起始地址(4)
|
||||
}
|
||||
|
||||
int header_t::index_end() {
|
||||
return to_int(header + 12, 4); // 索引结束地址(4)
|
||||
}
|
||||
|
||||
int header_t::ip_version() {
|
||||
return to_int(header + 16, 2); // IP 版本(2)
|
||||
}
|
||||
|
||||
int header_t::ptr() {
|
||||
return to_int(header + 18, 2); // 指针字节数(2)
|
||||
}
|
||||
|
||||
} // namespace xdb
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
#ifndef HEADER_H
|
||||
#define HEADER_H
|
||||
|
||||
#include "base.h"
|
||||
|
||||
namespace xdb {
|
||||
|
||||
class header_t {
|
||||
public:
|
||||
header_t(FILE* db);
|
||||
virtual ~header_t();
|
||||
|
||||
int version(); // 版本号
|
||||
int index_policy(); // 缓存策略
|
||||
int create_at(); // 文件生成时间
|
||||
int index_start(); // 索引起始地址
|
||||
int index_end(); // 索引结束地址
|
||||
int ip_version(); // IP 版本
|
||||
int ptr(); // 指针字节数
|
||||
|
||||
protected:
|
||||
char header[length_header];
|
||||
};
|
||||
|
||||
} // namespace xdb
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,158 @@
|
|||
|
||||
#include "ip.h"
|
||||
|
||||
namespace xdb {
|
||||
|
||||
ip_t::ip_t() {
|
||||
memset(p, '\0', sizeof(p));
|
||||
}
|
||||
|
||||
ip_t::ip_t(const ip_t& rhs, int val) {
|
||||
memcpy(p, rhs.p, ip_size);
|
||||
|
||||
if (val == 0 || val == 255)
|
||||
for (int i = 2; i < ip_size; ++i)
|
||||
p[i] = val;
|
||||
}
|
||||
|
||||
ip_t::ip_t(const char* p) {
|
||||
from_xdb(p);
|
||||
}
|
||||
|
||||
bool ip_t::from_str(const string& str) {
|
||||
int af_inet = ip_version == ipv4 ? AF_INET : AF_INET6;
|
||||
return inet_pton(af_inet, str.data(), p) == 1;
|
||||
}
|
||||
|
||||
void ip_t::from_xdb(const char str[16]) {
|
||||
for (int i = 0; i < ip_size; ++i)
|
||||
if (ip_version == ipv6)
|
||||
p[i] = str[i];
|
||||
else
|
||||
p[i] = str[ip_size - 1 - i];
|
||||
}
|
||||
|
||||
ip_t& ip_t::operator=(const ip_t& rhs) {
|
||||
memcpy(p, rhs.p, ip_size);
|
||||
return *this;
|
||||
}
|
||||
|
||||
int ip_t::compare(const ip_t& rhs) const {
|
||||
for (int i = 0; i < ip_size; ++i) {
|
||||
if ((unsigned char)p[i] > (unsigned char)rhs.p[i])
|
||||
return 1;
|
||||
if ((unsigned char)p[i] < (unsigned char)rhs.p[i])
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool ip_t::operator<(const ip_t& rhs) const {
|
||||
return compare(rhs) < 0;
|
||||
}
|
||||
|
||||
bool ip_t::operator<=(const ip_t& rhs) const {
|
||||
return compare(rhs) <= 0;
|
||||
}
|
||||
|
||||
bool ip_t::operator>(const ip_t& rhs) const {
|
||||
return compare(rhs) > 0;
|
||||
}
|
||||
|
||||
bool ip_t::operator>=(const ip_t& rhs) const {
|
||||
return compare(rhs) >= 0;
|
||||
}
|
||||
|
||||
bool ip_t::operator==(const ip_t& rhs) const {
|
||||
return compare(rhs) == 0;
|
||||
}
|
||||
|
||||
bool ip_t::operator!=(const ip_t& rhs) const {
|
||||
return compare(rhs) != 0;
|
||||
}
|
||||
|
||||
string ip_t::to_string() const {
|
||||
char buf[INET6_ADDRSTRLEN + 1];
|
||||
int af_inet = ip_version == ipv4 ? AF_INET : AF_INET6;
|
||||
inet_ntop(af_inet, p, buf, sizeof(buf));
|
||||
return string(buf);
|
||||
}
|
||||
|
||||
string ip_t::to_bit() const {
|
||||
string str;
|
||||
for (int i = 0; i < ip_size; ++i)
|
||||
if (ip_version == ipv6)
|
||||
str.push_back(p[i]);
|
||||
else
|
||||
str.push_back(p[ip_size - 1 - i]);
|
||||
return str;
|
||||
}
|
||||
|
||||
ip_t operator+(const ip_t& lhs, int v) {
|
||||
ip_t ip;
|
||||
|
||||
int i = ip_size;
|
||||
while (--i >= 0) {
|
||||
v += lhs.p[i];
|
||||
ip.p[i] = v % 256;
|
||||
v /= 256;
|
||||
}
|
||||
return ip;
|
||||
}
|
||||
|
||||
ip_t operator-(const ip_t& lhs, int v) {
|
||||
ip_t ip;
|
||||
|
||||
int i = ip_size;
|
||||
v = -v;
|
||||
while (--i >= 0) {
|
||||
v += lhs.p[i];
|
||||
if (v == -1)
|
||||
ip.p[i] = 255;
|
||||
else {
|
||||
ip.p[i] = v;
|
||||
v = 0;
|
||||
}
|
||||
}
|
||||
return ip;
|
||||
}
|
||||
|
||||
// node_t
|
||||
node_t::node_t() {
|
||||
}
|
||||
|
||||
node_t::node_t(char* buf) {
|
||||
char* pos1 = strchr(buf, '|');
|
||||
|
||||
if (pos1 == NULL)
|
||||
log_exit("invalid data: " + std::string(buf));
|
||||
char* pos2 = strchr(pos1 + 1, '|');
|
||||
if (pos2 == NULL)
|
||||
log_exit("invalid data: " + std::string(buf));
|
||||
*pos1 = '\0';
|
||||
*pos2 = '\0';
|
||||
|
||||
region = pos2 + 1;
|
||||
|
||||
if (!ip1.from_str(buf) || !ip2.from_str(pos1 + 1) || ip2 < ip1 ||
|
||||
region.empty()) {
|
||||
*pos1 = *pos2 = '|';
|
||||
log_exit(string("invalid data: ") + buf);
|
||||
}
|
||||
}
|
||||
|
||||
bool node_t::operator<(const node_t& rhs) const {
|
||||
if (ip1 < rhs.ip1)
|
||||
return true;
|
||||
return ip2 < rhs.ip2;
|
||||
}
|
||||
|
||||
string node_t::to_string() const {
|
||||
return ip1.to_string() + "|" + ip2.to_string() + "|" + region;
|
||||
}
|
||||
|
||||
string node_t::to_bit() const {
|
||||
return ip1.to_bit() + ip2.to_bit();
|
||||
}
|
||||
|
||||
} // namespace xdb
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
#ifndef IP_H
|
||||
#define IP_H
|
||||
|
||||
#include "base.h"
|
||||
|
||||
namespace xdb {
|
||||
|
||||
struct ip_t {
|
||||
unsigned char p[16];
|
||||
|
||||
ip_t();
|
||||
ip_t(const char* p);
|
||||
// val 为 0 或 255 时, 将 ip 的后几位置为 val
|
||||
ip_t(const ip_t& rhs, int val = -1);
|
||||
|
||||
bool from_str(const string& str);
|
||||
void from_xdb(const char str[16]);
|
||||
|
||||
ip_t& operator=(const ip_t& rhs);
|
||||
|
||||
int compare(const ip_t& rhs) const;
|
||||
bool operator<(const ip_t& rhs) const;
|
||||
bool operator<=(const ip_t& rhs) const;
|
||||
bool operator>(const ip_t& rhs) const;
|
||||
bool operator>=(const ip_t& rhs) const;
|
||||
bool operator==(const ip_t& rhs) const;
|
||||
bool operator!=(const ip_t& rhs) const;
|
||||
|
||||
string to_string() const;
|
||||
string to_bit() const;
|
||||
};
|
||||
|
||||
ip_t operator+(const ip_t& lhs, int v);
|
||||
ip_t operator-(const ip_t& lhs, int v);
|
||||
|
||||
struct node_t {
|
||||
ip_t ip1;
|
||||
ip_t ip2;
|
||||
string region;
|
||||
|
||||
node_t();
|
||||
node_t(char* buf);
|
||||
|
||||
bool operator<(const node_t& rhs) const;
|
||||
|
||||
string to_string() const;
|
||||
string to_bit() const;
|
||||
};
|
||||
|
||||
} // namespace xdb
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,167 @@
|
|||
|
||||
#include "make.h"
|
||||
|
||||
namespace xdb {
|
||||
|
||||
void make_t::vector_index_push_back(int row, int col, const node_t &node) {
|
||||
vector_index[row][col].push_back(
|
||||
std::make_pair<string, string>(node.to_bit(), string(node.region)));
|
||||
}
|
||||
|
||||
void make_t::vector_index_push_back(node_t &node) {
|
||||
ip_t ip1 = node.ip1;
|
||||
ip_t ip2 = node.ip2;
|
||||
|
||||
unsigned ip1_1 = ip1.p[0];
|
||||
unsigned ip1_2 = ip1.p[1];
|
||||
unsigned ip2_1 = ip2.p[0];
|
||||
unsigned ip2_2 = ip2.p[1];
|
||||
|
||||
if (ip1_1 == ip2_1 && ip1_2 == ip2_2) {
|
||||
vector_index_push_back(ip1_1, ip1_2, node);
|
||||
return;
|
||||
}
|
||||
|
||||
node.ip1 = ip1;
|
||||
node.ip2 = ip_t(ip1, 255);
|
||||
vector_index_push_back(ip1_1, ip1_2, node);
|
||||
|
||||
node.ip1 = ip_t(ip2, 0);
|
||||
node.ip2 = ip2;
|
||||
vector_index_push_back(ip2_1, ip2_2, node);
|
||||
|
||||
for (;;) {
|
||||
++ip1_2;
|
||||
if (ip1_2 == 256) {
|
||||
++ip1_1;
|
||||
ip1_2 = 0;
|
||||
}
|
||||
if (ip1_1 == ip2_1 && ip1_2 == ip2_2)
|
||||
break;
|
||||
ip1.p[0] = ip1_1;
|
||||
ip1.p[1] = ip1_2;
|
||||
node.ip1 = ip_t(ip1, 0);
|
||||
node.ip2 = ip_t(ip1, 255);
|
||||
vector_index_push_back(ip1_1, ip1_2, node);
|
||||
}
|
||||
}
|
||||
|
||||
void make_t::handle_input_help(char *buf) {
|
||||
// 去掉多余的空
|
||||
unsigned int buf_len = strlen(buf);
|
||||
while (buf_len > 0 && isspace(buf[buf_len - 1]))
|
||||
--buf_len;
|
||||
if (buf_len == 0)
|
||||
return;
|
||||
buf[buf_len] = '\0';
|
||||
|
||||
node_t node(buf);
|
||||
|
||||
if (node.ip1 < next_ip) {
|
||||
log_exit("ip 未排序: " + node.ip1.to_string() + ", " +
|
||||
next_ip.to_string());
|
||||
}
|
||||
|
||||
next_ip = node.ip2 + 1;
|
||||
|
||||
if (region.find(node.region) == region.end()) {
|
||||
region[node.region] = region_index;
|
||||
region_index += node.region.size();
|
||||
}
|
||||
|
||||
vector_index_push_back(node);
|
||||
}
|
||||
|
||||
void make_t::handle_input(const std::string &file_name) {
|
||||
FILE *src = fopen(file_name.data(), "r");
|
||||
if (src == NULL)
|
||||
log_exit("can't open " + file_name);
|
||||
|
||||
char buf[1024];
|
||||
while (fgets(buf, sizeof(buf), src) != NULL)
|
||||
handle_input_help(buf);
|
||||
fclose(src);
|
||||
}
|
||||
|
||||
void make_t::handle_header() {
|
||||
char buf[length_header];
|
||||
memset(buf, 0, length_header);
|
||||
write_ushort(3, buf); // 版本号
|
||||
write_ushort(1, buf + 2); // 缓存策略
|
||||
write_uint(time(NULL), buf + 4); // 时间
|
||||
// 索引
|
||||
unsigned int content_left = length_header + length_vector;
|
||||
for (auto &d : region)
|
||||
content_left += d.first.size();
|
||||
unsigned int content_right = content_left;
|
||||
|
||||
for (int i = 0; i < 256; ++i)
|
||||
for (int j = 0; j < 256; ++j)
|
||||
content_right += vector_index[i][j].size() * content_size;
|
||||
content_right -= content_size;
|
||||
write_uint(content_left, buf + 8);
|
||||
write_uint(content_right, buf + 12);
|
||||
write_ushort(ip_version, buf + 16); // IP
|
||||
write_ushort(4, buf + 18); // 指针数
|
||||
|
||||
write_string(buf, length_header, db);
|
||||
}
|
||||
|
||||
void make_t::handle_vector_index() {
|
||||
unsigned index = length_header + length_vector;
|
||||
for (auto &d : region)
|
||||
index += d.first.size();
|
||||
for (unsigned i = 0; i < 256; ++i)
|
||||
for (unsigned j = 0; j < 256; ++j)
|
||||
if (vector_index[i][j].size() == 0) {
|
||||
write_uint(0, db);
|
||||
write_uint(0, db);
|
||||
} else {
|
||||
write_uint(index, db);
|
||||
index += content_size * vector_index[i][j].size();
|
||||
write_uint(index, db);
|
||||
}
|
||||
}
|
||||
|
||||
void make_t::handle_region() {
|
||||
for (auto &d : region) {
|
||||
fseek(db, d.second, SEEK_SET);
|
||||
write_string(d.first.data(), d.first.size(), db);
|
||||
}
|
||||
}
|
||||
|
||||
void make_t::handle_content() {
|
||||
fseek(db, 0, SEEK_END);
|
||||
for (unsigned i = 0; i < 256; ++i)
|
||||
for (unsigned j = 0; j < 256; ++j)
|
||||
for (auto d : vector_index[i][j]) {
|
||||
write_string(d.first.data(), d.first.size(), db);
|
||||
write_ushort(d.second.size(), db);
|
||||
write_uint(region[d.second], db);
|
||||
}
|
||||
}
|
||||
|
||||
make_t::make_t(const string &src, const string &dst, int version)
|
||||
: region_index(length_vector + length_header) {
|
||||
unsigned long long tv1 = get_time();
|
||||
|
||||
init_xdb(version);
|
||||
|
||||
handle_input(src);
|
||||
|
||||
db = fopen(dst.data(), "w");
|
||||
if (db == NULL)
|
||||
log_exit("can't open " + dst);
|
||||
|
||||
handle_header();
|
||||
handle_vector_index();
|
||||
handle_region();
|
||||
handle_content();
|
||||
|
||||
fclose(db);
|
||||
|
||||
unsigned long long tv2 = get_time();
|
||||
printf("took: %.2fs\n", (tv2 - tv1) * 1.0 / 1000 / 1000);
|
||||
}
|
||||
|
||||
} // namespace xdb
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
#ifndef MAKE_H
|
||||
#define MAKE_H
|
||||
|
||||
#include "ip.h"
|
||||
|
||||
namespace xdb {
|
||||
|
||||
class make_t {
|
||||
public:
|
||||
make_t(const string &src, const string &dst, int version);
|
||||
|
||||
private:
|
||||
void vector_index_push_back(int row, int col, const node_t &node);
|
||||
void vector_index_push_back(node_t &node);
|
||||
void handle_input_help(char buf[]);
|
||||
void handle_input(const std::string &file_name);
|
||||
|
||||
void handle_header();
|
||||
void handle_vector_index();
|
||||
void handle_region();
|
||||
void handle_content();
|
||||
|
||||
FILE *db = NULL;
|
||||
|
||||
std::vector<std::pair<string, string>> vector_index[256][256];
|
||||
|
||||
std::unordered_map<string, unsigned> region;
|
||||
|
||||
unsigned region_index;
|
||||
ip_t next_ip;
|
||||
};
|
||||
|
||||
} // namespace xdb
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,137 @@
|
|||
|
||||
#include "search.h"
|
||||
|
||||
namespace xdb {
|
||||
|
||||
search_t::search_t(const string &file, int version, int p)
|
||||
: db(fopen(file.data(), "r")), header(db), policy(p) {
|
||||
init_xdb(version);
|
||||
|
||||
if (db == NULL)
|
||||
log_exit("can't open " + file);
|
||||
if (header.ip_version() != version)
|
||||
log_exit("ip 版本不匹配");
|
||||
|
||||
if (policy != policy_file) {
|
||||
read_bin(length_header, vector, length_vector, db);
|
||||
if (policy == policy_content) {
|
||||
fseek(db, 0, SEEK_END);
|
||||
int size = ftell(db) - length_vector - length_header;
|
||||
content = (char *)malloc(size);
|
||||
read_bin(length_vector + length_header, content, size, db);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
search_t::~search_t() {
|
||||
fclose(db);
|
||||
if (policy == policy_content)
|
||||
free(content);
|
||||
}
|
||||
|
||||
int search_t::get_io_count() {
|
||||
return io_count;
|
||||
}
|
||||
|
||||
int search_t::get_cost_time() {
|
||||
return cost_time;
|
||||
}
|
||||
|
||||
char const *search_t::get_content_index_help(int index) {
|
||||
if (policy != policy_file)
|
||||
return vector + index;
|
||||
|
||||
++io_count;
|
||||
static char v[8];
|
||||
read_bin(length_header + index, v, sizeof(v), db);
|
||||
return v;
|
||||
}
|
||||
|
||||
void search_t::get_content_index(const ip_t &ip, int &left, int &right) {
|
||||
int index = ((unsigned char)ip.p[0] * 256 + (unsigned char)ip.p[1]) * 8;
|
||||
|
||||
const char *p = get_content_index_help(index);
|
||||
left = to_uint(p);
|
||||
right = to_uint(p + 4);
|
||||
}
|
||||
|
||||
char const *search_t::get_content_help(int index) {
|
||||
if (policy == policy_content)
|
||||
return content + index - length_header - length_vector;
|
||||
++io_count;
|
||||
static char v[16 + 16 + 2 + 4];
|
||||
read_bin(index, v, content_size, db);
|
||||
return v;
|
||||
}
|
||||
|
||||
string search_t::get_region(int index, int len) {
|
||||
if (policy == policy_content)
|
||||
return string(content + index - length_header - length_vector, len);
|
||||
++io_count;
|
||||
char *p = (char *)malloc(sizeof(char) * len);
|
||||
read_bin(index, p, len, db);
|
||||
string res(p, len);
|
||||
free(p);
|
||||
return res;
|
||||
}
|
||||
|
||||
void search_t::get_content(int index,
|
||||
ip_t &ip_left,
|
||||
ip_t &ip_right,
|
||||
int ®ion_len,
|
||||
int ®ion_index) {
|
||||
const char *p = get_content_help(index);
|
||||
|
||||
ip_left.from_xdb(p);
|
||||
ip_right.from_xdb(p + ip_size);
|
||||
|
||||
region_len = to_ushort(p + ip_size * 2);
|
||||
region_index = to_uint(p + ip_size * 2 + 2);
|
||||
}
|
||||
|
||||
string search_t::search(const ip_t &ip) {
|
||||
io_count = 0;
|
||||
|
||||
int content_left, content_right;
|
||||
get_content_index(ip, content_left, content_right);
|
||||
|
||||
if (content_left == 0 || content_right == 0)
|
||||
return "";
|
||||
|
||||
ip_t ip_left, ip_right;
|
||||
int region_len;
|
||||
int region_index;
|
||||
|
||||
int left = 0;
|
||||
int right = (content_right - content_left) / content_size;
|
||||
|
||||
for (;;) {
|
||||
int mid = left + (right - left) / 2;
|
||||
int mid_index = content_left + mid * content_size;
|
||||
get_content(mid_index, ip_left, ip_right, region_len, region_index);
|
||||
|
||||
// ip ip_left ip_right
|
||||
if (ip < ip_left)
|
||||
right = mid - 1;
|
||||
// ip_left ip_right ip
|
||||
else if (ip_right < ip)
|
||||
left = mid + 1;
|
||||
else
|
||||
return get_region(region_index, region_len);
|
||||
}
|
||||
}
|
||||
|
||||
string search_t::search(const string &str) {
|
||||
unsigned long long t1 = get_time();
|
||||
|
||||
ip_t ip;
|
||||
if (ip.from_str(str) == false)
|
||||
return "invalid ipv" + std::to_string(ip_version) + ": " + str;
|
||||
string region = search(ip);
|
||||
|
||||
unsigned long long t2 = get_time();
|
||||
cost_time = t2 - t1;
|
||||
return region;
|
||||
}
|
||||
|
||||
} // namespace xdb
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
#ifndef SEARCH_H
|
||||
#define SEARCH_H
|
||||
|
||||
#include "header.h"
|
||||
#include "ip.h"
|
||||
|
||||
namespace xdb {
|
||||
|
||||
class search_t {
|
||||
protected:
|
||||
FILE *db;
|
||||
|
||||
header_t header;
|
||||
|
||||
int policy;
|
||||
|
||||
int io_count;
|
||||
int cost_time;
|
||||
|
||||
char vector[length_vector];
|
||||
char *content;
|
||||
|
||||
public:
|
||||
search_t(const string &file_name, int version, int policy);
|
||||
virtual ~search_t();
|
||||
|
||||
int get_io_count();
|
||||
int get_cost_time();
|
||||
|
||||
string search(const string &str);
|
||||
|
||||
protected:
|
||||
string search(const ip_t &ip);
|
||||
|
||||
void get_content_index(const ip_t &ip1, int &left, int &right);
|
||||
void get_content(int index,
|
||||
ip_t &left,
|
||||
ip_t &right,
|
||||
int ®ion_len,
|
||||
int ®ion_index);
|
||||
|
||||
char const *get_content_index_help(int index);
|
||||
char const *get_content_help(int index);
|
||||
string get_region(int index, int len);
|
||||
};
|
||||
|
||||
} // namespace xdb
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
|
||||
#include "../src/bench.h"
|
||||
|
||||
std::map<int, std::string> prompt;
|
||||
|
||||
void test_ipv4(int policy) {
|
||||
std::cout << "测试 IPv4, " << prompt[policy];
|
||||
xdb::bench_t("../../data/ip2region_v4.xdb", xdb::ipv4, policy)
|
||||
.test("../../data/ipv4_source.txt");
|
||||
}
|
||||
|
||||
void test_ipv6(int policy) {
|
||||
std::cout << "测试 IPv6, " << prompt[policy];
|
||||
xdb::bench_t("../../data/ip2region_v6.xdb", xdb::ipv6, policy)
|
||||
.test("../../data/ipv6_source.txt");
|
||||
}
|
||||
|
||||
int main() {
|
||||
prompt[xdb::policy_file] = " 不缓存, ";
|
||||
prompt[xdb::policy_vector] = "部分缓存, ";
|
||||
prompt[xdb::policy_content] = "全部缓存, ";
|
||||
|
||||
test_ipv4(xdb::policy_file);
|
||||
test_ipv4(xdb::policy_vector);
|
||||
test_ipv4(xdb::policy_content);
|
||||
|
||||
test_ipv6(xdb::policy_file);
|
||||
test_ipv6(xdb::policy_vector);
|
||||
test_ipv6(xdb::policy_content);
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
|
||||
#include "../src/edit.h"
|
||||
|
||||
int main() {
|
||||
std::string file_name_old = "../../data/ipv4_source.txt";
|
||||
std::string file_name_new = "./1.txt";
|
||||
xdb::edit_t xdb(file_name_old, file_name_new, xdb::ipv4);
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
|
||||
#include "../src/edit.h"
|
||||
|
||||
int main() {
|
||||
std::string file_name_old = "../../data/ipv6_source.txt";
|
||||
std::string file_name_new = "./1.txt";
|
||||
xdb::edit_t xdb(file_name_old, file_name_new, xdb::ipv6);
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
|
||||
#include "../src/header.h"
|
||||
|
||||
void test(const std::string& prompt, const std::string& file_name) {
|
||||
std::cout << prompt << std::endl;
|
||||
|
||||
xdb::header_t head(fopen(file_name.data(), "r"));
|
||||
|
||||
std::cout << "版本号: " << head.version() << std::endl;
|
||||
std::cout << "缓存策略: " << head.index_policy() << std::endl;
|
||||
|
||||
time_t rawtime = head.create_at();
|
||||
struct tm* info = localtime(&rawtime);
|
||||
char buf[80];
|
||||
strftime(buf, 80, "%Y-%m-%d %H:%M:%S", info);
|
||||
|
||||
std::cout << "文件生成时间: " << buf << std::endl;
|
||||
std::cout << "索引起始地址: " << head.index_start() << std::endl;
|
||||
std::cout << "索引结束地址: " << head.index_end() << std::endl;
|
||||
std::cout << "IP版本: " << head.ip_version() << std::endl;
|
||||
std::cout << "指针字节数: " << head.ptr() << std::endl;
|
||||
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
int main() {
|
||||
test("测试 IPv4", "../../data/ip2region_v4.xdb");
|
||||
test("测试 IPv6", "../../data/ip2region_v6.xdb");
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
|
||||
#include "../src/make.h"
|
||||
|
||||
void test(const std::string& prompt,
|
||||
const std::string& filename_xdb,
|
||||
const std::string& filename_src,
|
||||
int version
|
||||
|
||||
) {
|
||||
std::cout << prompt;
|
||||
xdb::make_t(filename_xdb, filename_src, version);
|
||||
}
|
||||
|
||||
int main() {
|
||||
test("生成 ipv4 的 xdb 文件, ",
|
||||
"../../data/ipv4_source.txt",
|
||||
"./ip2region_v4.xdb",
|
||||
xdb::ipv4);
|
||||
|
||||
test("生成 ipv6 的 xdb 文件, ",
|
||||
"../../data/ipv6_source.txt",
|
||||
"./ip2region_v6.xdb",
|
||||
xdb::ipv6);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
|
||||
#include "../src/search.h"
|
||||
|
||||
std::map<int, std::string> prompt;
|
||||
|
||||
void test(xdb::search_t& s, const std::string& ip, const std::string& region) {
|
||||
if (s.search(ip) != region)
|
||||
xdb::log_exit("测试失败, ip " + ip + ", region " + region);
|
||||
}
|
||||
|
||||
void test_ipv4(int policy) {
|
||||
std::cout << "测试 IPv4 " << prompt[policy];
|
||||
|
||||
xdb::search_t s("../../data/ip2region_v4.xdb", xdb::ipv4, policy);
|
||||
test(s, "0.0.0.0", "0|0|内网IP|内网IP");
|
||||
test(s, "1.2.3.4", "美国|华盛顿|0|谷歌");
|
||||
test(s, "255.255.255.255", "0|0|内网IP|内网IP");
|
||||
|
||||
std::cout << " 成功" << std::endl;
|
||||
}
|
||||
|
||||
void test_ipv6(int policy) {
|
||||
std::cout << "测试 IPv6 " << prompt[policy];
|
||||
|
||||
xdb::search_t s("../../data/ip2region_v6.xdb", xdb::ipv6, policy);
|
||||
test(s, "::1", "");
|
||||
test(s, "2001:200:124::", "日本|东京都|千代田区|专线用户");
|
||||
|
||||
std::cout << " 成功" << std::endl;
|
||||
}
|
||||
|
||||
int main() {
|
||||
prompt[xdb::policy_file] = " 不缓存:";
|
||||
prompt[xdb::policy_vector] = "部分缓存:";
|
||||
prompt[xdb::policy_content] = "全部缓存:";
|
||||
|
||||
test_ipv4(xdb::policy_file);
|
||||
test_ipv4(xdb::policy_vector);
|
||||
test_ipv4(xdb::policy_content);
|
||||
|
||||
test_ipv6(xdb::policy_file);
|
||||
test_ipv6(xdb::policy_vector);
|
||||
test_ipv6(xdb::policy_content);
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
root = true
|
||||
|
||||
# To learn more about .editorconfig see https://aka.ms/editorconfigdocs
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
spelling_exclusion_path = .\exclusion.dic
|
||||
|
||||
[*.csproj]
|
||||
charset = utf-8
|
||||
insert_final_newline = true
|
||||
|
||||
[*.{xml,config,csproj,nuspec,props,resx,targets,yml,tasks,json}]
|
||||
indent_size = 2
|
||||
|
||||
[*.sh]
|
||||
end_of_line = lf
|
||||
|
||||
[*.cs]
|
||||
csharp_style_namespace_declarations = file_scoped:silent
|
||||
|
||||
[*.cs]
|
||||
file_header_template = Copyright 2025 The Ip2Region Authors. All rights reserved.\nUse of this source code is governed by a Apache2.0-style\nlicense that can be found in the LICENSE file.\n@Author Alan <lzh.shap@gmail.com>\n@Date 2023/07/25\nUpdated by Argo Zhang <argo@live.ca> at 2025/11/21
|
||||
|
|
@ -0,0 +1,337 @@
|
|||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
##
|
||||
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
|
||||
|
||||
# User-specific files
|
||||
*.rsuser
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
[Aa][Rr][Mm]/
|
||||
[Aa][Rr][Mm]64/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
|
||||
# Visual Studio 2015/2017 cache/options directory
|
||||
.vs/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
# Visual Studio 2017 auto generated files
|
||||
Generated\ Files/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# NUNIT
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
# Benchmark Results
|
||||
BenchmarkDotNet.Artifacts/
|
||||
|
||||
# .NET Core
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
|
||||
# StyleCop
|
||||
StyleCopReport.xml
|
||||
|
||||
# Files built by Visual Studio
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_h.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.iobj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.ipdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*_wpftmp.csproj
|
||||
*.log
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# Visual Studio Trace Files
|
||||
*.e2e
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# JustCode is a .NET coding add-in
|
||||
.JustCode
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# AxoCover is a Code Coverage Tool
|
||||
.axoCover/*
|
||||
!.axoCover/settings.json
|
||||
|
||||
# Visual Studio code coverage results
|
||||
*.coverage
|
||||
*.coveragexml
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||
# in these scripts will be unencrypted
|
||||
PublishScripts/
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/[Pp]ackages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/[Pp]ackages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/[Pp]ackages/repositories.config
|
||||
# NuGet v3's project.json files produces more ignorable files
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Windows Store app package directories and files
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
*.appx
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!?*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.jfm
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
orleans.codegen.cs
|
||||
|
||||
# Including strong name files can present a security risk
|
||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||
#*.snk
|
||||
|
||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||
#bower_components/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
ServiceFabricBackup/
|
||||
*.rptproj.bak
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
*.ndf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
*.rptproj.rsuser
|
||||
*- Backup*.rdl
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
node_modules/
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||
*.vbw
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
|
||||
# Paket dependency manager
|
||||
.paket/paket.exe
|
||||
paket-files/
|
||||
|
||||
# FAKE - F# Make
|
||||
.fake/
|
||||
|
||||
# JetBrains Rider
|
||||
.idea/
|
||||
*.sln.iml
|
||||
|
||||
# CodeRush personal settings
|
||||
.cr/personal
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Cake - Uncomment if you are using it
|
||||
# tools/**
|
||||
# !tools/packages.config
|
||||
|
||||
# Tabs Studio
|
||||
*.tss
|
||||
|
||||
# Telerik's JustMock configuration file
|
||||
*.jmconfig
|
||||
|
||||
# BizTalk build output
|
||||
*.btp.cs
|
||||
*.btm.cs
|
||||
*.odx.cs
|
||||
*.xsd.cs
|
||||
|
||||
# OpenCover UI analysis results
|
||||
OpenCover/
|
||||
|
||||
# Azure Stream Analytics local run output
|
||||
ASALocalRun/
|
||||
|
||||
# MSBuild Binary and Structured Log
|
||||
*.binlog
|
||||
|
||||
# NVidia Nsight GPU debugger configuration file
|
||||
*.nvuser
|
||||
|
||||
# MFractors (Xamarin productivity tool) working folder
|
||||
.mfractor/
|
||||
|
||||
# Local History for Visual Studio
|
||||
.localhistory/
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [3.0.0] - 2025-11-22
|
||||
- 支持 .NET 10.0
|
||||
- 增加 IPv6 支持
|
||||
- 修复若干 bug
|
||||
|
||||
## [2.0.1] - 2023-07-30
|
||||
|
||||
### Added
|
||||
- Support netstandard2.0
|
||||
|
||||
## [2.0.0] - 2023-07-26
|
||||
|
||||
### Removed
|
||||
- Remove nuget include xdb file
|
||||
- Searcher cache policy default parameters
|
||||
- Searcher xdb file path default parameters
|
||||
|
||||
### Added
|
||||
- Dependent file query policies CachePolicy.VectorIndex, CachePolicy.File support thread-safe concurrent queries
|
||||
- Dramatically optimizes overall performance
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
// Copyright 2025 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
// @Author Alan <lzh.shap@gmail.com>
|
||||
// @Date 2023/07/25
|
||||
// Updated by Argo Zhang <argo@live.ca> at 2025/11/21
|
||||
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using IP2Region.Net.XDB;
|
||||
|
||||
namespace IP2Region.Net.BenchMark;
|
||||
|
||||
[MemoryDiagnoser]
|
||||
public class Benchmarks
|
||||
{
|
||||
private static readonly string XdbPathV4 = Path.Combine(AppContext.BaseDirectory, "IP2Region", "ip2region_v4.xdb");
|
||||
private static readonly string XdbPathV6 = Path.Combine(AppContext.BaseDirectory, "IP2Region", "ip2region_v6.xdb");
|
||||
private static readonly Searcher _contentV4Searcher = new(CachePolicy.Content, XdbPathV4);
|
||||
private static readonly Searcher _vectorV4Searcher = new(CachePolicy.VectorIndex, XdbPathV4);
|
||||
private static readonly Searcher _fileV4Searcher = new(CachePolicy.File, XdbPathV4);
|
||||
private static readonly Searcher _contentV6Searcher = new(CachePolicy.Content, XdbPathV6);
|
||||
private static readonly Searcher _vectorV6Searcher = new(CachePolicy.VectorIndex, XdbPathV6);
|
||||
private static readonly Searcher _fileV6Searcher = new(CachePolicy.File, XdbPathV6);
|
||||
|
||||
private readonly string _testIPv4Address = "114.114.114.114";
|
||||
private readonly string _testIPv6Address = "240e:3b7:3272:d8d0:db09:c067:8d59:539e";
|
||||
|
||||
public Benchmarks()
|
||||
{
|
||||
_contentV4Searcher.Search(_testIPv4Address);
|
||||
_vectorV4Searcher.Search(_testIPv4Address);
|
||||
_fileV4Searcher.Search(_testIPv4Address);
|
||||
|
||||
_contentV6Searcher.Search(_testIPv6Address);
|
||||
_vectorV6Searcher.Search(_testIPv6Address);
|
||||
_fileV6Searcher.Search(_testIPv6Address);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("IPv4")]
|
||||
public void ContentIPv4() => _contentV4Searcher.Search(_testIPv4Address);
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("IPv4")]
|
||||
public void VectorIPv4() => _vectorV4Searcher.Search(_testIPv4Address);
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("IPv4")]
|
||||
public void FileIPv4() => _fileV4Searcher.Search(_testIPv4Address);
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("IPv6")]
|
||||
public void ContentIPv6() => _contentV6Searcher.Search(_testIPv6Address);
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("IPv6")]
|
||||
public void VectorIPv6() => _vectorV6Searcher.Search(_testIPv6Address);
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("IPv6")]
|
||||
public void FileIPv6() => _fileV6Searcher.Search(_testIPv6Address);
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.15.6" />
|
||||
<PackageReference Include="BenchmarkDotNet.Annotations" Version="0.15.6" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\IP2Region.Net\IP2Region.Net.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="..\..\..\data\ip2region_v4.xdb">
|
||||
<Link>IP2Region/ip2region_v4.xdb</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="..\..\..\data\ip2region_v6.xdb">
|
||||
<Link>IP2Region/ip2region_v6.xdb</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
// Copyright 2025 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
// @Author Alan <lzh.shap@gmail.com>
|
||||
// @Date 2023/07/25
|
||||
// Updated by Argo Zhang <argo@live.ca> at 2025/11/21
|
||||
|
||||
using BenchmarkDotNet.Running;
|
||||
using IP2Region.Net.BenchMark;
|
||||
|
||||
BenchmarkRunner.Run<Benchmarks>();
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
|
||||
<PackageReference Include="xunit" Version="2.*" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.*">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\IP2Region.Net\IP2Region.Net.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="..\..\..\data\ipv4_source.txt">
|
||||
<Link>TestData/ipv4_source.txt</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="..\..\..\data\ip2region_v4.xdb">
|
||||
<Link>TestData/ip2region_v4.xdb</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="..\..\..\data\ipv6_source.txt">
|
||||
<Link>TestData/ipv6_source.txt</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="..\..\..\data\ip2region_v6.xdb">
|
||||
<Link>TestData/ip2region_v6.xdb</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,163 @@
|
|||
// Copyright 2025 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
// @Author Alan <lzh.shap@gmail.com>
|
||||
// @Date 2023/07/25
|
||||
// Updated by Argo Zhang <argo@live.ca> at 2025/11/21
|
||||
|
||||
using IP2Region.Net.Abstractions;
|
||||
using IP2Region.Net.XDB;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System.Net;
|
||||
using Xunit;
|
||||
|
||||
namespace IP2Region.Net.Test;
|
||||
|
||||
public class SearcherTest
|
||||
{
|
||||
private readonly string _xdbPathV4 = Path.Combine(AppContext.BaseDirectory, "TestData", "ip2region_v4.xdb");
|
||||
private readonly string _xdbPathV6 = Path.Combine(AppContext.BaseDirectory, "TestData", "ip2region_v6.xdb");
|
||||
|
||||
[Theory]
|
||||
[InlineData("58.251.27.201", "中国|广东省|深圳市|联通", "v4")]
|
||||
[InlineData("114.114.114.114", "中国|江苏省|南京市|0", "v4")]
|
||||
[InlineData("119.29.29.29", "中国|北京|北京市|腾讯", "v4")]
|
||||
[InlineData("223.5.5.5", "中国|浙江省|杭州市|阿里云", "v4")]
|
||||
[InlineData("180.76.76.76", "中国|北京|北京市|百度", "v4")]
|
||||
[InlineData("8.8.8.8", "美国|0|0|Level3", "v4")]
|
||||
[InlineData("240e:3b7:3272:d8d0:db09:c067:8d59:539e", "中国|广东省|深圳市|家庭宽带", "v6")]
|
||||
public void TestSearchCacheContent(string ip, string expected, string version)
|
||||
{
|
||||
var _xdbPath = version == "v4" ? _xdbPathV4 : _xdbPathV6;
|
||||
var contentSearcher = new Searcher(CachePolicy.Content, _xdbPath);
|
||||
var region = contentSearcher.Search(ip);
|
||||
Assert.Equal(expected, region);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("58.251.27.201", "中国|广东省|深圳市|联通", "v4")]
|
||||
[InlineData("114.114.114.114", "中国|江苏省|南京市|0", "v4")]
|
||||
[InlineData("119.29.29.29", "中国|北京|北京市|腾讯", "v4")]
|
||||
[InlineData("223.5.5.5", "中国|浙江省|杭州市|阿里云", "v4")]
|
||||
[InlineData("180.76.76.76", "中国|北京|北京市|百度", "v4")]
|
||||
[InlineData("8.8.8.8", "美国|0|0|Level3", "v4")]
|
||||
[InlineData("240e:3b7:3272:d8d0:db09:c067:8d59:539e", "中国|广东省|深圳市|家庭宽带", "v6")]
|
||||
public void TestSearchCacheVector(string ip, string expected, string version)
|
||||
{
|
||||
var _xdbPath = version == "v4" ? _xdbPathV4 : _xdbPathV6;
|
||||
var vectorSearcher = new Searcher(CachePolicy.VectorIndex, _xdbPath);
|
||||
var region = vectorSearcher.Search(ip);
|
||||
Assert.Equal(expected, region);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("58.251.0.0", "中国|广东省|深圳市|联通", "v4")]
|
||||
[InlineData("58.251.255.255", "中国|广东省|深圳市|联通", "v4")]
|
||||
[InlineData("58.251.27.201", "中国|广东省|深圳市|联通", "v4")]
|
||||
[InlineData("114.114.114.114", "中国|江苏省|南京市|0", "v4")]
|
||||
[InlineData("119.29.29.29", "中国|北京|北京市|腾讯", "v4")]
|
||||
[InlineData("223.5.5.5", "中国|浙江省|杭州市|阿里云", "v4")]
|
||||
[InlineData("180.76.76.76", "中国|北京|北京市|百度", "v4")]
|
||||
[InlineData("8.8.8.8", "美国|0|0|Level3", "v4")]
|
||||
[InlineData("240e:3b7:3272:d8d0:db09:c067:8d59:539e", "中国|广东省|深圳市|家庭宽带", "v6")]
|
||||
[InlineData("240e:044d:2d00:0000:0000:0000:0000:0000", "美国|加利福尼亚州|洛杉矶|移动网络", "v6")]
|
||||
public void TestSearchCacheFile(string ip, string expected, string version)
|
||||
{
|
||||
var _xdbPath = version == "v4" ? _xdbPathV4 : _xdbPathV6;
|
||||
var fileSearcher = new Searcher(CachePolicy.File, _xdbPath);
|
||||
var region = fileSearcher.Search(ip);
|
||||
Assert.Equal(expected, region);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IoCount_File_Ok()
|
||||
{
|
||||
var searcher = new Searcher(CachePolicy.File, _xdbPathV4);
|
||||
searcher.Search("58.251.27.201");
|
||||
Assert.Equal(3, searcher.IoCount);
|
||||
|
||||
searcher.Search("58.251.27.201");
|
||||
Assert.Equal(3, searcher.IoCount);
|
||||
|
||||
searcher.Dispose();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IoCount_Vector_Ok()
|
||||
{
|
||||
var searcher = new Searcher(CachePolicy.VectorIndex, _xdbPathV4);
|
||||
searcher.Search("58.251.27.201");
|
||||
Assert.Equal(2, searcher.IoCount);
|
||||
|
||||
searcher.Search("58.251.27.201");
|
||||
Assert.Equal(2, searcher.IoCount);
|
||||
|
||||
searcher.Dispose();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IoCount_Content_Ok()
|
||||
{
|
||||
var searcher = new Searcher(CachePolicy.Content, _xdbPathV4);
|
||||
searcher.Search("58.251.27.201");
|
||||
Assert.Equal(0, searcher.IoCount);
|
||||
|
||||
searcher.Search("58.251.27.201");
|
||||
Assert.Equal(0, searcher.IoCount);
|
||||
|
||||
searcher.Dispose();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("58.251.255.255", "中国|广东省|深圳市|联通")]
|
||||
public void Search_Ip_Ok(string ipStr, string expected)
|
||||
{
|
||||
var fileSearcher = new Searcher(CachePolicy.File, _xdbPathV4);
|
||||
var ipAddress = IPAddress.Parse(ipStr);
|
||||
var region = fileSearcher.Search(ipAddress);
|
||||
Assert.Equal(expected, region);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("58.251.255.255", "中国|广东省|深圳市|联通")]
|
||||
public void AddIP2RegionService_Ok(string ipStr, string expected)
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
services.AddIP2RegionService(_xdbPathV4, CachePolicy.File);
|
||||
|
||||
var provider = services.BuildServiceProvider();
|
||||
var searcher = provider.GetRequiredService<ISearcher>();
|
||||
var region = searcher.Search(ipStr);
|
||||
Assert.Equal(expected, region);
|
||||
|
||||
searcher = provider.GetRequiredKeyedService<ISearcher>("IP2Region.Net");
|
||||
region = searcher.Search(ipStr);
|
||||
Assert.Equal(expected, region);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(CachePolicy.Content, "v4")]
|
||||
[InlineData(CachePolicy.VectorIndex, "v4")]
|
||||
[InlineData(CachePolicy.File, "v4")]
|
||||
[InlineData(CachePolicy.Content, "v6")]
|
||||
[InlineData(CachePolicy.VectorIndex, "v6")]
|
||||
[InlineData(CachePolicy.File, "v6")]
|
||||
public void TestBenchSearch(CachePolicy cachePolicy, string version)
|
||||
{
|
||||
var _xdbPath = version == "v4" ? _xdbPathV4 : _xdbPathV6;
|
||||
var searcher = new Searcher(cachePolicy, _xdbPath);
|
||||
var srcPath = Path.Combine(AppContext.BaseDirectory, "TestData", $"ip{version}_source.txt");
|
||||
|
||||
foreach (var line in File.ReadLines(srcPath))
|
||||
{
|
||||
var ps = line.Trim().Split("|", 3);
|
||||
var sip = ps[0];
|
||||
var eip = ps[1];
|
||||
|
||||
var s1 = searcher.Search(sip);
|
||||
var s2 = searcher.Search(eip);
|
||||
Assert.Equal(s1, ps[2]);
|
||||
Assert.Equal(s2, ps[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
// Copyright 2025 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
// @Author Alan <lzh.shap@gmail.com>
|
||||
// @Date 2023/07/25
|
||||
// Updated by Argo Zhang <argo@live.ca> at 2025/11/21
|
||||
|
||||
using Xunit;
|
||||
|
||||
namespace IP2Region.Net.Test;
|
||||
|
||||
public class UtilTest
|
||||
{
|
||||
[Fact]
|
||||
public void IpAddressToUInt32_Ok()
|
||||
{
|
||||
var uintIp = XDB.Util.IpAddressToUInt32("114.114.114.114");
|
||||
Assert.Equal((uint)1920103026, uintIp);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetMidIp_Ok()
|
||||
{
|
||||
var uintIp = XDB.Util.GetMidIp(1, 10);
|
||||
Assert.Equal((uint)5, uintIp);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
// Copyright 2025 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
// @Author Alan <lzh.shap@gmail.com>
|
||||
// @Date 2023/07/25
|
||||
// Updated by Argo Zhang <argo@live.ca> at 2025/11/21
|
||||
|
||||
using Xunit;
|
||||
|
||||
namespace IP2Region.Net.Test;
|
||||
|
||||
public class XdbTest
|
||||
{
|
||||
[Fact]
|
||||
public async Task VersionIPV4_Ok()
|
||||
{
|
||||
var db = Path.Combine(AppContext.BaseDirectory, "TestData", $"ip2region_v4.xdb");
|
||||
|
||||
var version = await XDB.Util.GetVersionAsync(db);
|
||||
Assert.Equal(3, version.Ver);
|
||||
Assert.Equal(1, version.CachePolice);
|
||||
Assert.Equal("2025-09-06 02:24:16", version.CreatedTime.ToString("yyyy-MM-dd HH:mm:ss"));
|
||||
Assert.Equal((uint)955933, version.StartIndex);
|
||||
Assert.Equal((uint)11042415, version.EndIndex);
|
||||
Assert.Equal(4, version.IPVer);
|
||||
Assert.Equal(4, version.BytesCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task VersionIPV6_Ok()
|
||||
{
|
||||
var db = Path.Combine(AppContext.BaseDirectory, "TestData", $"ip2region_v6.xdb");
|
||||
var version = await XDB.Util.GetVersionAsync(db);
|
||||
Assert.Equal(3, version.Ver);
|
||||
Assert.Equal(1, version.CachePolice);
|
||||
Assert.Equal("2025-10-17 04:41:04", version.CreatedTime.ToString("yyyy-MM-dd HH:mm:ss"));
|
||||
Assert.Equal((uint)3094259, version.StartIndex);
|
||||
Assert.Equal((uint)36258303, version.EndIndex);
|
||||
Assert.Equal(6, version.IPVer);
|
||||
Assert.Equal(4, version.BytesCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetVersionAsync_Error()
|
||||
{
|
||||
await Assert.ThrowsAsync<ArgumentNullException>(async () => await XDB.Util.GetVersionAsync(null!));
|
||||
await Assert.ThrowsAsync<FileNotFoundException>(async () => await XDB.Util.GetVersionAsync(Path.Combine(AppContext.BaseDirectory, "test.xdb")));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<Solution>
|
||||
<Folder Name="/Solution Items/">
|
||||
<File Path=".editorconfig" />
|
||||
<File Path="README.md" />
|
||||
</Folder>
|
||||
<Project Path="IP2Region.Net.BenchMark/IP2Region.Net.BenchMark.csproj" />
|
||||
<Project Path="IP2Region.Net.Test/IP2Region.Net.Test.csproj" />
|
||||
<Project Path="IP2Region.Net/IP2Region.Net.csproj" />
|
||||
</Solution>
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
// Copyright 2025 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
// @Author Alan <lzh.shap@gmail.com>
|
||||
// @Date 2023/07/25
|
||||
// Updated by Argo Zhang <argo@live.ca> at 2025/11/21
|
||||
|
||||
using System.Net;
|
||||
|
||||
namespace IP2Region.Net.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// IP 转化为地理位置搜索器接口
|
||||
/// </summary>
|
||||
public interface ISearcher : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// 搜索方法
|
||||
/// </summary>
|
||||
/// <param name="ipStr">IP 地址字符串 如 192.168.0.1</param>
|
||||
/// <returns></returns>
|
||||
string? Search(string ipStr);
|
||||
|
||||
/// <summary>
|
||||
/// 搜索方法
|
||||
/// </summary>
|
||||
string? Search(IPAddress ipAddress);
|
||||
|
||||
/// <summary>
|
||||
/// 搜索方法 仅限 IPv4 使用
|
||||
/// </summary>
|
||||
/// <param name="ipAddress">IPv4 地址字节数组小端读取 uint 数值</param>
|
||||
[Obsolete("已弃用,请改用其他方法;Deprecated; please use Search(string) or Search(IPAddress) method.")]
|
||||
string? Search(uint ipAddress);
|
||||
|
||||
/// <summary>
|
||||
/// 获得 内部 IO 访问次数
|
||||
/// </summary>
|
||||
int IoCount { get; }
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
// Copyright 2025 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
// @Author Alan <lzh.shap@gmail.com>
|
||||
// @Date 2023/07/25
|
||||
// Updated by Argo Zhang <argo@live.ca> at 2025/11/21
|
||||
|
||||
using IP2Region.Net.Abstractions;
|
||||
using IP2Region.Net.XDB;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
|
||||
namespace Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
/// <summary>
|
||||
/// IP2Region 服务扩展类
|
||||
/// </summary>
|
||||
public static class IP2RegionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 添加 IP2RegionService 服务。
|
||||
/// </summary>
|
||||
/// <param name="services"><see cref="IServiceCollection"/> 集合</param>
|
||||
/// <param name="path">IP2Region 数据库文件的路径。</param>
|
||||
/// <param name="cachePolicy">缓存策略,默认为 <see cref="CachePolicy.Content"/>。</param>
|
||||
public static IServiceCollection AddIP2RegionService(this IServiceCollection services, string path, CachePolicy cachePolicy = CachePolicy.Content)
|
||||
{
|
||||
services.TryAddSingleton<ISearcher>(provider =>
|
||||
{
|
||||
return new Searcher(cachePolicy, path);
|
||||
});
|
||||
#if NET8_0_OR_GREATER
|
||||
services.TryAddKeyedSingleton("IP2Region.Net", (provider, _) =>
|
||||
{
|
||||
return provider.GetRequiredService<ISearcher>();
|
||||
});
|
||||
#endif
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<id>IP2Region.Net</id>
|
||||
<version>3.0.0</version>
|
||||
<title>IP2Region.Net</title>
|
||||
<authors>Alan Lee;Argo Zhang(argo@live.ca)</authors>
|
||||
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
<PackageProjectUrl>https://github.com/lionsoul2014/ip2region/tree/master/binding/csharp</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/lionsoul2014/ip2region/tree/master/binding/csharp</RepositoryUrl>
|
||||
<PackageReleaseNotes>Please refer to CHANGELOG.md for details</PackageReleaseNotes>
|
||||
<Description>.NET client library for ip2region</Description>
|
||||
<PackageTags>IP2Region GeoIP IPSearch</PackageTags>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<TargetFrameworks>netstandard2.0;netstandard2.1;net6.0;net7.0;net8.0;net9.0;net10.0</TargetFrameworks>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<UserSecretsId>c2f07fe1-a300-4de3-8200-1278ed8cb5b7</UserSecretsId>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="..\README.md" Pack="true" PackagePath="\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
|
||||
<PackageReference Include="System.Buffers" Version="4.5.1" />
|
||||
<PackageReference Include="System.Memory" Version="4.5.5" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.*" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.1'">
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net6.0'">
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.*" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.*" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.*" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net9.0'">
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.*" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net10.0'">
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.*" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="..\CHANGELOG.md">
|
||||
<Link>CHANGELOG.md</Link>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
// Copyright 2025 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
// @Author Alan <lzh.shap@gmail.com>
|
||||
// @Date 2023/07/25
|
||||
// Updated by Argo Zhang <argo@live.ca> at 2025/11/21
|
||||
|
||||
using IP2Region.Net.XDB;
|
||||
|
||||
namespace IP2Region.Net.Internal;
|
||||
|
||||
static class CacheStrategyFactory
|
||||
{
|
||||
public static ICacheStrategy CreateCacheStrategy(CachePolicy cachePolicy, string xdbPath) => cachePolicy switch
|
||||
{
|
||||
CachePolicy.Content => new ContentCacheStrategy(xdbPath),
|
||||
CachePolicy.VectorIndex => new VectorIndexCacheStrategy(xdbPath),
|
||||
_ => new FileCacheStrategy(xdbPath),
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
// Copyright 2025 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
// @Author Alan <lzh.shap@gmail.com>
|
||||
// @Date 2023/07/25
|
||||
// Updated by Argo Zhang <argo@live.ca> at 2025/11/21
|
||||
|
||||
namespace IP2Region.Net.Internal;
|
||||
|
||||
class ContentCacheStrategy : FileCacheStrategy
|
||||
{
|
||||
private readonly ReadOnlyMemory<byte> _cacheData;
|
||||
|
||||
public ContentCacheStrategy(string xdbPath) : base(xdbPath)
|
||||
{
|
||||
_cacheData = base.GetData(0, (int)XdbFileStream.Length);
|
||||
XdbFileStream.Close();
|
||||
XdbFileStream.Dispose();
|
||||
}
|
||||
|
||||
public override ReadOnlyMemory<byte> GetData(long offset, int length) => _cacheData.Slice((int)offset, length);
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(false);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
// Copyright 2025 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
// @Author Alan <lzh.shap@gmail.com>
|
||||
// @Date 2023/07/25
|
||||
// Updated by Argo Zhang <argo@live.ca> at 2025/11/21
|
||||
|
||||
using System.Buffers;
|
||||
|
||||
namespace IP2Region.Net.Internal;
|
||||
|
||||
class FileCacheStrategy(string xdbPath) : ICacheStrategy
|
||||
{
|
||||
protected const int HeaderInfoLength = 256;
|
||||
protected const int VectorIndexSize = 8;
|
||||
|
||||
protected const int BufferSize = 64 * 1024;
|
||||
|
||||
protected FileStream XdbFileStream = new(xdbPath, FileMode.Open, FileAccess.Read, FileShare.Read, BufferSize, FileOptions.RandomAccess);
|
||||
|
||||
public int IoCount { get; set; }
|
||||
|
||||
public void ResetIoCount()
|
||||
{
|
||||
IoCount = 0;
|
||||
}
|
||||
|
||||
public virtual ReadOnlyMemory<byte> GetVectorIndex(int offset) => GetData(HeaderInfoLength + offset, VectorIndexSize);
|
||||
|
||||
public virtual ReadOnlyMemory<byte> GetData(long offset, int length)
|
||||
{
|
||||
var buffer = ArrayPool<byte>.Shared.Rent(length);
|
||||
try
|
||||
{
|
||||
int totalBytesRead = 0;
|
||||
XdbFileStream.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
int bytesRead;
|
||||
while (totalBytesRead < length)
|
||||
{
|
||||
bytesRead = XdbFileStream.Read(buffer, totalBytesRead, length - totalBytesRead);
|
||||
if (bytesRead == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
totalBytesRead += bytesRead;
|
||||
IoCount++;
|
||||
}
|
||||
|
||||
var ret = new byte[totalBytesRead];
|
||||
if (totalBytesRead > 0)
|
||||
{
|
||||
Array.Copy(buffer, 0, ret, 0, totalBytesRead);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放文件句柄
|
||||
/// </summary>
|
||||
/// <param name="disposing"></param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
XdbFileStream.Close();
|
||||
XdbFileStream.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
// Copyright 2025 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
// @Author Alan <lzh.shap@gmail.com>
|
||||
// @Date 2023/07/25
|
||||
// Updated by Argo Zhang <argo@live.ca> at 2025/11/21
|
||||
|
||||
namespace IP2Region.Net.Internal;
|
||||
|
||||
internal interface ICacheStrategy : IDisposable
|
||||
{
|
||||
int IoCount { get; }
|
||||
|
||||
void ResetIoCount();
|
||||
|
||||
ReadOnlyMemory<byte> GetVectorIndex(int offset);
|
||||
|
||||
ReadOnlyMemory<byte> GetData(long offset, int length);
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
// Copyright 2025 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
// @Author Alan <lzh.shap@gmail.com>
|
||||
// @Date 2023/07/25
|
||||
// Updated by Argo Zhang <argo@live.ca> at 2025/11/21
|
||||
|
||||
namespace IP2Region.Net.Internal;
|
||||
|
||||
class VectorIndexCacheStrategy : FileCacheStrategy
|
||||
{
|
||||
private const int VectorIndexRows = 256;
|
||||
private const int VectorIndexCols = 256;
|
||||
|
||||
private readonly ReadOnlyMemory<byte> _vectorCache;
|
||||
|
||||
public VectorIndexCacheStrategy(string xdbPath) : base(xdbPath)
|
||||
{
|
||||
_vectorCache = GetData(HeaderInfoLength, VectorIndexRows * VectorIndexCols * VectorIndexSize);
|
||||
}
|
||||
|
||||
public override ReadOnlyMemory<byte> GetVectorIndex(int offset) => _vectorCache.Slice(offset, VectorIndexSize);
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
// Copyright 2025 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
// @Author Alan <lzh.shap@gmail.com>
|
||||
// @Date 2023/07/25
|
||||
// Updated by Argo Zhang <argo@live.ca> at 2025/11/21
|
||||
|
||||
namespace IP2Region.Net.XDB;
|
||||
|
||||
/// <summary>
|
||||
/// 缓存策略枚举
|
||||
/// </summary>
|
||||
public enum CachePolicy
|
||||
{
|
||||
/// <summary>
|
||||
/// no cache
|
||||
/// </summary>
|
||||
File,
|
||||
|
||||
/// <summary>
|
||||
/// cache vector index , reduce the number of IO operations
|
||||
/// </summary>
|
||||
VectorIndex,
|
||||
|
||||
/// <summary>
|
||||
/// default cache policy , cache whole xdb file
|
||||
/// </summary>
|
||||
Content
|
||||
}
|
||||
|
|
@ -0,0 +1,159 @@
|
|||
// Copyright 2025 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
// @Author Alan <lzh.shap@gmail.com>
|
||||
// @Date 2023/07/25
|
||||
// Updated by Argo Zhang <argo@live.ca> at 2025/11/21
|
||||
|
||||
using IP2Region.Net.Abstractions;
|
||||
using IP2Region.Net.Internal;
|
||||
using System.Buffers.Binary;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
|
||||
namespace IP2Region.Net.XDB;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="ISearcher"/> 实现类
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <inheritdoc/>
|
||||
/// </remarks>
|
||||
public class Searcher(CachePolicy cachePolicy, string xdbPath) : ISearcher
|
||||
{
|
||||
private readonly ICacheStrategy _cacheStrategy = CacheStrategyFactory.CreateCacheStrategy(cachePolicy, xdbPath);
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
public int IoCount => _cacheStrategy.IoCount;
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
public string? Search(string ipStr)
|
||||
{
|
||||
var ipAddress = IPAddress.Parse(ipStr);
|
||||
return SearchCore(ipAddress.GetAddressBytes());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
public string? Search(IPAddress ipAddress) => SearchCore(ipAddress.GetAddressBytes());
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
[Obsolete("已弃用,请改用其他方法;Deprecated; please use Search(string) or Search(IPAddress) method.")]
|
||||
[ExcludeFromCodeCoverage]
|
||||
public string? Search(uint ipAddress)
|
||||
{
|
||||
var bytes = BitConverter.GetBytes(ipAddress);
|
||||
Array.Reverse(bytes);
|
||||
return SearchCore(bytes);
|
||||
}
|
||||
|
||||
string? SearchCore(byte[] ipBytes)
|
||||
{
|
||||
// 重置 IO 计数器
|
||||
_cacheStrategy.ResetIoCount();
|
||||
|
||||
// 每个 vector 索引项的字节数
|
||||
var vectorIndexSize = 8;
|
||||
|
||||
// vector 索引的列数
|
||||
var vectorIndexCols = 256;
|
||||
|
||||
// 计算得到 vector 索引项的开始地址。
|
||||
var il0 = ipBytes[0];
|
||||
var il1 = ipBytes[1];
|
||||
var idx = il0 * vectorIndexCols * vectorIndexSize + il1 * vectorIndexSize;
|
||||
|
||||
var vector = _cacheStrategy.GetVectorIndex(idx);
|
||||
var sPtr = BinaryPrimitives.ReadUInt32LittleEndian(vector.Span);
|
||||
var ePtr = BinaryPrimitives.ReadUInt32LittleEndian(vector.Span.Slice(4));
|
||||
|
||||
var length = ipBytes.Length;
|
||||
var indexSize = length * 2 + 6;
|
||||
var l = 0;
|
||||
var h = (ePtr - sPtr) / indexSize;
|
||||
var dataLen = 0;
|
||||
long dataPtr = 0;
|
||||
|
||||
while (l <= h)
|
||||
{
|
||||
int m = (int)(l + h) >> 1;
|
||||
|
||||
var p = sPtr + m * indexSize;
|
||||
var buff = _cacheStrategy.GetData(p, indexSize);
|
||||
|
||||
var s = buff.Span.Slice(0, length);
|
||||
var e = buff.Span.Slice(length, length);
|
||||
if (ByteCompare(ipBytes, s) < 0)
|
||||
{
|
||||
h = m - 1;
|
||||
}
|
||||
else if (ByteCompare(ipBytes, e) > 0)
|
||||
{
|
||||
l = m + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
dataLen = BinaryPrimitives.ReadUInt16LittleEndian(buff.Span.Slice(length * 2, 2));
|
||||
dataPtr = BinaryPrimitives.ReadUInt32LittleEndian(buff.Span.Slice(length * 2 + 2, 4));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var regionBuff = _cacheStrategy.GetData(dataPtr, dataLen);
|
||||
return Encoding.UTF8.GetString(regionBuff.Span.ToArray());
|
||||
}
|
||||
|
||||
static int ByteCompare(byte[] ip1, ReadOnlySpan<byte> ip2) => ip1.Length == 4 ? IPv4Compare(ip1, ip2) : IPv6Compare(ip1, ip2);
|
||||
|
||||
static int IPv4Compare(byte[] ip1, ReadOnlySpan<byte> ip2)
|
||||
{
|
||||
var ret = 0;
|
||||
for (int i = 0; i < ip1.Length; i++)
|
||||
{
|
||||
var ip2Index = ip1.Length - 1 - i;
|
||||
if (ip1[i] < ip2[ip2Index])
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else if (ip1[i] > ip2[ip2Index])
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int IPv6Compare(byte[] ip1, ReadOnlySpan<byte> ip2)
|
||||
{
|
||||
var ret = 0;
|
||||
for (int i = 0; i < ip1.Length; i++)
|
||||
{
|
||||
if (ip1[i] < ip2[i])
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else if (ip1[i] > ip2[i])
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
_cacheStrategy.Dispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
// Copyright 2025 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
// @Author Alan <lzh.shap@gmail.com>
|
||||
// @Date 2023/07/25
|
||||
// Updated by Argo Zhang <argo@live.ca> at 2025/11/21
|
||||
|
||||
using System.Buffers;
|
||||
using System.Buffers.Binary;
|
||||
using System.Net;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace IP2Region.Net.XDB;
|
||||
|
||||
/// <summary>
|
||||
/// 工具类
|
||||
/// </summary>
|
||||
public static class Util
|
||||
{
|
||||
public static uint IpAddressToUInt32(string ipAddress)
|
||||
{
|
||||
var address = IPAddress.Parse(ipAddress);
|
||||
return IpAddressToUInt32(address);
|
||||
}
|
||||
|
||||
public static uint IpAddressToUInt32(IPAddress ipAddress)
|
||||
{
|
||||
byte[] bytes = ipAddress.GetAddressBytes();
|
||||
Array.Reverse(bytes);
|
||||
return MemoryMarshal.Read<uint>(bytes);
|
||||
}
|
||||
|
||||
public static uint GetMidIp(uint x, uint y)
|
||||
=> (x & y) + ((x ^ y) >> 1);
|
||||
|
||||
public static async Task<XdbVersion> GetVersionAsync(string dbPath, CancellationToken token = default)
|
||||
{
|
||||
if (string.IsNullOrEmpty(dbPath))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(dbPath));
|
||||
}
|
||||
|
||||
if (!File.Exists(dbPath))
|
||||
{
|
||||
throw new FileNotFoundException("xdb file not found.", dbPath);
|
||||
}
|
||||
|
||||
using var reader = File.OpenRead(dbPath);
|
||||
return await GetVersionAsync(reader, token);
|
||||
}
|
||||
|
||||
internal static async Task<XdbVersion> GetVersionAsync(FileStream reader, CancellationToken token = default)
|
||||
{
|
||||
XdbVersion ret = default;
|
||||
var buffer = ArrayPool<byte>.Shared.Rent(256);
|
||||
|
||||
try
|
||||
{
|
||||
var length = await reader.ReadAsync(buffer, 0, 256, token);
|
||||
if (length == 256)
|
||||
{
|
||||
ret = Parse(buffer);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(buffer);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static XdbVersion Parse(ReadOnlySpan<byte> buffer)
|
||||
{
|
||||
var ret = new XdbVersion
|
||||
{
|
||||
Ver = BinaryPrimitives.ReadUInt16LittleEndian(buffer.Slice(0, 2)),
|
||||
CachePolice = BinaryPrimitives.ReadUInt16LittleEndian(buffer.Slice(2, 2)),
|
||||
StartIndex = BinaryPrimitives.ReadUInt32LittleEndian(buffer.Slice(8, 4)),
|
||||
EndIndex = BinaryPrimitives.ReadUInt32LittleEndian(buffer.Slice(12, 4)),
|
||||
IPVer = BinaryPrimitives.ReadUInt16LittleEndian(buffer.Slice(16, 2)),
|
||||
BytesCount = BinaryPrimitives.ReadUInt16LittleEndian(buffer.Slice(18, 2))
|
||||
};
|
||||
|
||||
var createdAt = BinaryPrimitives.ReadUInt32LittleEndian(buffer.Slice(4, 4));
|
||||
var dtm = new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.FromHours(0));
|
||||
ret.CreatedTime = dtm.AddSeconds(createdAt);
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
// Copyright 2025 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
// @Author Alan <lzh.shap@gmail.com>
|
||||
// @Date 2023/07/25
|
||||
// Updated by Argo Zhang <argo@live.ca> at 2025/11/21
|
||||
|
||||
namespace IP2Region.Net.XDB;
|
||||
|
||||
/// <summary>
|
||||
/// XdbVersion 结构体
|
||||
/// </summary>
|
||||
public struct XdbVersion
|
||||
{
|
||||
/// <summary>
|
||||
/// 获得/设置 版本号
|
||||
/// </summary>
|
||||
public ushort Ver { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 缓存策略
|
||||
/// </summary>
|
||||
public ushort CachePolice { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 文件生成时间
|
||||
/// </summary>
|
||||
public DateTimeOffset CreatedTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 索引起始地址
|
||||
/// </summary>
|
||||
public uint StartIndex { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 索引结束地址
|
||||
/// </summary>
|
||||
public uint EndIndex { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 IP版本
|
||||
/// </summary>
|
||||
public ushort IPVer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 指针字节数
|
||||
/// </summary>
|
||||
public ushort BytesCount { get; set; }
|
||||
}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
# IP2Region.Net
|
||||
|
||||
.NET client library for IP2Region
|
||||
|
||||
## Installation
|
||||
|
||||
Install the package with [NuGet](https://www.nuget.org/packages/IP2Region.Net)
|
||||
|
||||
```bash
|
||||
Install-Package IP2Region.Net
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```csharp
|
||||
using IP2Region.Net.Abstractions;
|
||||
using IP2Region.Net.XDB;
|
||||
|
||||
ISearcher searcher = new Searcher(CachePolicy , "your xdb file path");
|
||||
```
|
||||
### Cache Policy Description
|
||||
| Cache Policy | Description | Thread Safe |
|
||||
|-------------------------|------------------------------------------------------------------------------------------------------------|-------------|
|
||||
| CachePolicy.Content | Cache the entire `xdb` data. | Yes |
|
||||
| CachePolicy.VectorIndex | Cache `vecotorIndex` to speed up queries and reduce system io pressure by reducing one fixed IO operation. | Yes |
|
||||
| CachePolicy.File | Completely file-based queries | Yes |
|
||||
|
||||
### XDB File Description
|
||||
Generate using [maker](https://github.com/lionsoul2014/ip2region/tree/master/maker/csharp), or [download](https://github.com/lionsoul2014/ip2region/blob/master/data/ip2region.xdb) pre-generated xdb files
|
||||
|
||||
## ASP.NET Core Usage
|
||||
|
||||
```csharp
|
||||
services.AddIP2RegionService("your xdb file path", cachePolicy: CachePolicy.Content);
|
||||
```
|
||||
|
||||
NET6/7
|
||||
```csharp
|
||||
provider.GetRequiredService<ISearcher>()
|
||||
```
|
||||
|
||||
NET8+ support keyed service
|
||||
```csharp
|
||||
provider.GetRequiredKeyedService<ISearcher>("IP2Region.Net");
|
||||
```
|
||||
|
||||
## TargetFrameworks
|
||||
netstandard2.0;netstandard2.1;net6.0;net7.0;net8.0;net9.0;net10.0
|
||||
|
||||
## Performance
|
||||
// * Summary *
|
||||
|
||||
BenchmarkDotNet v0.15.6, Windows 11 (10.0.26200.7171)
|
||||
13th Gen Intel Core i7-13700 2.10GHz, 1 CPU, 24 logical and 16 physical cores
|
||||
.NET SDK 10.0.100
|
||||
[Host] : .NET 10.0.0 (10.0.0, 10.0.25.52411), X64 RyuJIT x86-64-v3
|
||||
DefaultJob : .NET 10.0.0 (10.0.0, 10.0.25.52411), X64 RyuJIT x86-64-v3
|
||||
|
||||
|
||||
| Method | Mean | Error | StdDev | Gen0 | Allocated |
|
||||
|------------ |-------------:|-----------:|-----------:|-------:|----------:|
|
||||
| ContentIPv4 | 53.70 ns | 0.296 ns | 0.277 ns | 0.0086 | 136 B |
|
||||
| VectorIPv4 | 4,446.04 ns | 18.673 ns | 15.593 ns | 0.0076 | 232 B |
|
||||
| FileIPv4 | 6,712.40 ns | 15.718 ns | 13.934 ns | 0.0153 | 264 B |
|
||||
| ContentIPv6 | 145.53 ns | 0.331 ns | 0.277 ns | 0.0126 | 200 B |
|
||||
| VectorIPv6 | 7,058.39 ns | 125.505 ns | 117.398 ns | 0.0381 | 712 B |
|
||||
| FileIPv6 | 10,657.97 ns | 53.907 ns | 50.425 ns | 0.0458 | 744 B |
|
||||
|
||||
// * Hints *
|
||||
Outliers
|
||||
Benchmarks.VectorIPv4: Default -> 2 outliers were removed (4.55 us, 4.58 us)
|
||||
Benchmarks.FileIPv4: Default -> 1 outlier was removed (6.79 us)
|
||||
Benchmarks.ContentIPv6: Default -> 2 outliers were removed (148.08 ns, 152.27 ns)
|
||||
|
||||
// * Legends *
|
||||
Mean : Arithmetic mean of all measurements
|
||||
Error : Half of 99.9% confidence interval
|
||||
StdDev : Standard deviation of all measurements
|
||||
Gen0 : GC Generation 0 collects per 1000 operations
|
||||
Allocated : Allocated memory per single operation (managed only, inclusive, 1KB = 1024B)
|
||||
1 ns : 1 Nanosecond (0.000000001 sec)
|
||||
|
||||
// * Diagnostic Output - MemoryDiagnoser *
|
||||
|
||||
|
||||
// ***** BenchmarkRunner: End *****
|
||||
Run time: 00:02:06 (126.09 sec), executed benchmarks: 6
|
||||
|
||||
Global total time: 00:02:13 (133.47 sec), executed benchmarks: 6
|
||||
// * Artifacts cleanup *
|
||||
Artifacts cleanup is finished
|
||||
|
||||
## Contributing
|
||||
Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
|
||||
|
||||
Please make sure to update tests as appropriate.
|
||||
|
||||
## License
|
||||
[Apache License 2.0](https://github.com/lionsoul2014/ip2region/blob/master/LICENSE.md)
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
# ip2region xdb erlang 查询客户端
|
||||
|
||||
### 简介
|
||||
该bingding以erlang语言实现xdb查询客户端,基于Erlang OTP Application,查询逻辑由ip2region_worker工作进程实现,支持配多个工作进程来进行负载均衡。
|
||||
|
||||
### 应用配置
|
||||
该应用可配置的参数在ip2region.app.src中,如下:
|
||||
``` erlang
|
||||
{env,[
|
||||
{poolargs, [
|
||||
{size, 1}, %% 工作进程默认数量
|
||||
{max_overflow, 5} %% 工作进程最大数量
|
||||
]}
|
||||
]}
|
||||
```
|
||||
|
||||
### 编译
|
||||
|
||||
```
|
||||
$ rebar3 compile
|
||||
```
|
||||
### 运行
|
||||
将xdb文件放到priv目录下,然后启动erlang节点:
|
||||
```
|
||||
$ rebar3 shell
|
||||
|
||||
```
|
||||
在erlang shell中调用xdb:search/1接口查询Ip地址信息, 该接口支持以list格式字符串、binary格式字符串、tuple和整数表示的IP地址,如下:
|
||||
```
|
||||
1> xdb:search("1.0.8.0").
|
||||
[20013,22269,124,48,124,24191,19996,30465,124,24191,24030,
|
||||
24066,124,30005,20449]
|
||||
2>
|
||||
3> io:format("~ts~n", [xdb:search("1.0.8.0")]).
|
||||
中国|0|广东省|广州市|电信
|
||||
io:format("~ts~n", [xdb:search(<<"1.0.8.0">>)]).
|
||||
中国|0|广东省|广州市|电信
|
||||
4> io:format("~ts~n", [xdb:search({1,0,8,0})]).
|
||||
中国|0|广东省|广州市|电信
|
||||
6> io:format("~ts~n", [xdb:search(16779264)]).
|
||||
中国|0|广东省|广州市|电信
|
||||
```
|
||||
|
||||
### 使用方法
|
||||
* 在rebar.config中引入依赖
|
||||
```
|
||||
{deps, [
|
||||
ip2region
|
||||
]}.
|
||||
|
||||
```
|
||||
* 启动ip2region Application
|
||||
```
|
||||
......
|
||||
|
||||
application:ensure_started(ip2region),
|
||||
|
||||
......
|
||||
```
|
||||
|
||||
* 调用xdb:search/1接口查询IP信息
|
||||
```
|
||||
......
|
||||
|
||||
ip2region:search("1.0.8.0"),
|
||||
|
||||
......
|
||||
```
|
||||
|
||||
### 单元测试
|
||||
|
||||
```
|
||||
$ rebar3 eunit
|
||||
===> Verifying dependencies...
|
||||
===> Analyzing applications...
|
||||
===> Compiling ip2region
|
||||
===> Performing EUnit tests...
|
||||
=INFO REPORT==== 17-Jan-2023::11:52:59.920155 ===
|
||||
XdbFile:/home/admin/erl-workspace/ip2region/binding/erlang/_build/test/lib/ip2region/priv/ip2region.xdb
|
||||
|
||||
....
|
||||
Finished in 0.074 seconds
|
||||
4 tests, 0 failures
|
||||
|
||||
```
|
||||
|
||||
### 基准测试
|
||||
```
|
||||
$ cd benchmarks/
|
||||
$ sh xdb-benchmark.sh
|
||||
===> Verifying dependencies...
|
||||
===> Analyzing applications...
|
||||
===> Compiling ip2region
|
||||
Erlang/OTP 24 [erts-12.3.2.2] [source] [64-bit] [smp:2:2] [ds:2:2:10] [async-threads:1] [jit]
|
||||
|
||||
Eshell V12.3.2.2 (abort with ^G)
|
||||
1> =INFO REPORT==== 17-Jan-2023::11:37:35.631095 ===
|
||||
XdbFile:/home/admin/erl-workspace/ip2region/binding/erlang/_build/default/lib/ip2region/priv/ip2region.xdb
|
||||
|
||||
===> Booted ip2region
|
||||
===> Evaluating: "xdb_benchmark:main(\"../../data/ip.merge.txt\"), init:stop()."
|
||||
CPU info:
|
||||
model name : AMD EPYC 7K62 48-Core Processor
|
||||
cache size : 512 KB
|
||||
cpu MHz : 2595.124
|
||||
bogomips : 5190.24
|
||||
cores/threads : 2
|
||||
|
||||
Erlang info:
|
||||
system_version:Erlang/OTP 24 [erts-12.3.2.2] [source] [64-bit] [smp:2:2] [ds:2:2:10] [async-threads:1] [jit]
|
||||
load test data use 4.835593s
|
||||
|
||||
start run benchmark tests
|
||||
|
||||
search from file:
|
||||
ip count:683844,
|
||||
total time: 28.201699s,
|
||||
search 24248.326315375536 times per second,
|
||||
use 41.23995969841075 micro second per search
|
||||
|
||||
search from cache:
|
||||
ip count:683844,
|
||||
total time: 0.671801s,
|
||||
search 1017926.4395259906 times per second,
|
||||
use 0.9823892583688677 micro second per search
|
||||
|
||||
benchmark test finish
|
||||
|
||||
```
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
#!/bin/bash
|
||||
|
||||
cd ..
|
||||
|
||||
rebar3 shell --eval="xdb_benchmark:main(\"../../data/ip.merge.txt\"), init:stop()."
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
-ifndef(IP2REGION_HRL).
|
||||
-define(IP2REGION_HRL, true).
|
||||
|
||||
-define(NONE, none).
|
||||
-define(APP_NAME, ip2region).
|
||||
|
||||
-define(XDB_VECTOR_INDEX, ets_xdb_vector_index).
|
||||
-define(XDB_SEGMENT_INDEX, ets_xdb_segement_index).
|
||||
-define(IP2REGION_CACHE, ets_ip2region_cache).
|
||||
|
||||
|
||||
-define(XDB_HEADER_SIZE, 256).
|
||||
-define(XDB_VECTOR_COLS, 256).
|
||||
-define(XDB_VECTOR_INDEX_SIZE, 8).
|
||||
-define(XDB_VECTOR_INDEX_COUNT, (16#10000)). %% 256*256
|
||||
|
||||
-define(XDB_SEGMENT_INDEX_SIZE, 14).
|
||||
|
||||
-define(IP2REGION_POOL, ip2region_pool).
|
||||
|
||||
-ifndef(IF).
|
||||
-define(IF(C, T, F), case (C) of true -> (T); false -> (F) end).
|
||||
-define(IF(C, T), ?IF(C, T, skip)).
|
||||
-endif.
|
||||
|
||||
-endif.
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
{erl_opts, [
|
||||
debug_info,
|
||||
export_all,
|
||||
nowarn_export_all
|
||||
]}.
|
||||
|
||||
{plugins, [rebar3_hex, rebar3_ex_doc]}.
|
||||
|
||||
{deps, [
|
||||
poolboy
|
||||
]}.
|
||||
|
||||
{shell, [
|
||||
% {config, "config/sys.config"},
|
||||
{apps, [ip2region]}
|
||||
]}.
|
||||
|
||||
{ex_doc, [
|
||||
{extras, ["README.md"]},
|
||||
{main, "README.md"},
|
||||
{source_url, "https://github.com/leihua996/ip2region/tree/master/binding/erlang"}
|
||||
]}.
|
||||
|
||||
{hex, [{doc, ex_doc}]}.
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
{"1.2.0",
|
||||
[{<<"poolboy">>,{pkg,<<"poolboy">>,<<"1.5.2">>},0}]}.
|
||||
[
|
||||
{pkg_hash,[
|
||||
{<<"poolboy">>, <<"392B007A1693A64540CEAD79830443ABF5762F5D30CF50BC95CB2C1AAAFA006B">>}]},
|
||||
{pkg_hash_ext,[
|
||||
{<<"poolboy">>, <<"DAD79704CE5440F3D5A3681C8590B9DC25D1A561E8F5A9C995281012860901E3">>}]}
|
||||
].
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
{application, ip2region,
|
||||
[{description, "ip2region xdb client application"},
|
||||
{vsn, "0.1.0"},
|
||||
{registered, []},
|
||||
{mod, {ip2region_app, []}},
|
||||
{applications,
|
||||
[kernel,
|
||||
stdlib
|
||||
]},
|
||||
{env,[
|
||||
{poolargs, [
|
||||
{size, 1},
|
||||
{max_overflow, 5}
|
||||
]}
|
||||
]},
|
||||
{modules, []},
|
||||
|
||||
{licenses, ["Apache-2.0"]},
|
||||
{links, [{"Github", "https://github.com/leihua996/ip2region/tree/master/binding/erlang"}]}
|
||||
]}.
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
%%%-------------------------------------------------------------------
|
||||
%% Copyright 2022 The Ip2Region Authors. All rights reserved.
|
||||
%% Use of this source code is governed by a Apache2.0-style
|
||||
%% license that can be found in the LICENSE file.
|
||||
%%
|
||||
%% @doc
|
||||
%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
-module(ip2region_app).
|
||||
|
||||
-behaviour(application).
|
||||
|
||||
-export([start/2, stop/1]).
|
||||
|
||||
start(_StartType, _StartArgs) ->
|
||||
ip2region_sup:start_link().
|
||||
|
||||
stop(_State) ->
|
||||
ok.
|
||||
|
||||
%% internal functions
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
%%%-------------------------------------------------------------------
|
||||
%% Copyright 2022 The Ip2Region Authors. All rights reserved.
|
||||
%% Use of this source code is governed by a Apache2.0-style
|
||||
%% license that can be found in the LICENSE file.
|
||||
%%
|
||||
%% @doc ip2region top level supervisor.
|
||||
%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(ip2region_sup).
|
||||
-behaviour(supervisor).
|
||||
-include("ip2region.hrl").
|
||||
|
||||
-export([start_link/0]).
|
||||
|
||||
-export([init/1, create_table/0]).
|
||||
|
||||
-define(SERVER, ?MODULE).
|
||||
|
||||
start_link() ->
|
||||
{ok, SupPid} = supervisor:start_link({local, ?SERVER}, ?MODULE, []),
|
||||
{ok, _PoolPid} = start_ip2region_pool(SupPid),
|
||||
{ok, SupPid}.
|
||||
|
||||
%% sup_flags() = #{strategy => strategy(), % optional
|
||||
%% intensity => non_neg_integer(), % optional
|
||||
%% period => pos_integer()} % optional
|
||||
%% child_spec() = #{id => child_id(), % mandatory
|
||||
%% start => mfargs(), % mandatory
|
||||
%% restart => restart(), % optional
|
||||
%% shutdown => shutdown(), % optional
|
||||
%% type => worker(), % optional
|
||||
%% modules => modules()} % optional
|
||||
init([]) ->
|
||||
create_table(),
|
||||
SupFlags = #{strategy => one_for_one,
|
||||
intensity => 10,
|
||||
period => 5},
|
||||
ChildSpecs = [],
|
||||
{ok, {SupFlags, ChildSpecs}}.
|
||||
|
||||
%% internal functions
|
||||
%%
|
||||
create_table() ->
|
||||
Opts = [named_table, set, public, {read_concurrency, true}, {keypos, 1}],
|
||||
ets:new(?XDB_VECTOR_INDEX, Opts),
|
||||
ets:new(?XDB_SEGMENT_INDEX, Opts),
|
||||
ets:new(?IP2REGION_CACHE, Opts).
|
||||
|
||||
start_ip2region_pool(Sup) ->
|
||||
{ok, PoolArgsCfg} = application:get_env(poolargs),
|
||||
PoolName = ?IP2REGION_POOL,
|
||||
PoolArgs = [{strategy, fifo}, {name, {local, PoolName}}, {worker_module, ip2region_worker} | PoolArgsCfg],
|
||||
WorkerArgs = [],
|
||||
ChildSpecs = poolboy:child_spec(PoolName, PoolArgs, WorkerArgs),
|
||||
supervisor:start_child(Sup, ChildSpecs).
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
%%%-------------------------------------------------------------------
|
||||
%% Copyright 2022 The Ip2Region Authors. All rights reserved.
|
||||
%% Use of this source code is governed by a Apache2.0-style
|
||||
%% license that can be found in the LICENSE file.
|
||||
%%
|
||||
%% @doc
|
||||
%% ip2region utils
|
||||
%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(ip2region_util).
|
||||
-export([ipv4_to_n/1]).
|
||||
|
||||
|
||||
ipv4_to_n(IntIp) when is_integer(IntIp) -> IntIp;
|
||||
ipv4_to_n({A, B, C, D}) ->
|
||||
<<N:32>> = <<A, B, C, D>>,
|
||||
N;
|
||||
ipv4_to_n(Ip) when is_binary(Ip) ->
|
||||
ipv4_to_n(binary_to_list(Ip));
|
||||
ipv4_to_n(Ip) when is_list(Ip) ->
|
||||
case inet_parse:address(Ip) of
|
||||
{ok, Addr} ->
|
||||
ipv4_to_n(Addr);
|
||||
_ ->
|
||||
{error, bad_ip_format}
|
||||
end.
|
||||
|
|
@ -0,0 +1,183 @@
|
|||
%%%-------------------------------------------------------------------
|
||||
%% Copyright 2022 The Ip2Region Authors. All rights reserved.
|
||||
%% Use of this source code is governed by a Apache2.0-style
|
||||
%% license that can be found in the LICENSE file.
|
||||
%%
|
||||
%% @doc
|
||||
%% ip2region xdb client worker
|
||||
%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(ip2region_worker).
|
||||
-behaviour(gen_server).
|
||||
-include("ip2region.hrl").
|
||||
|
||||
%% API
|
||||
-export([start/1, stop/1, start_link/1]).
|
||||
-export([search/2]).
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
|
||||
-record(state, {xdb_fd}).
|
||||
|
||||
%%==========================================
|
||||
%% API
|
||||
%% =========================================
|
||||
start(Args) ->
|
||||
Opts = [{spawn_opt, [{min_heap_size, 6000}]}],
|
||||
gen_server:start(?MODULE, Args, Opts).
|
||||
|
||||
start_link(Args) ->
|
||||
Opts = [{spawn_opt, [{min_heap_size, 6000}]}],
|
||||
gen_server:start_link(?MODULE, Args, Opts).
|
||||
|
||||
|
||||
stop(Pid) ->
|
||||
gen_server:call(Pid, stop).
|
||||
|
||||
search(Pid, Ip) ->
|
||||
gen_server:call(Pid, {search, Ip}).
|
||||
|
||||
%%==========================================
|
||||
%% gen_server callbacks
|
||||
%% =========================================
|
||||
init(_Args) ->
|
||||
process_flag(trap_exit, true),
|
||||
AppName =
|
||||
case application:get_application() of
|
||||
{ok, AName} -> AName;
|
||||
_ -> ?APP_NAME
|
||||
end,
|
||||
PrivDir = code:priv_dir(AppName),
|
||||
XdbFileName = filename:join([PrivDir, "ip2region.xdb"]),
|
||||
error_logger:info_report(io_lib:format("XdbFile:~s~n", [XdbFileName])),
|
||||
{ok, IoDevice} = file:open(XdbFileName, [read, binary]),
|
||||
load_vector_index(IoDevice),
|
||||
{ok, #state{xdb_fd = IoDevice}}.
|
||||
|
||||
handle_call(Request, From, State) ->
|
||||
try
|
||||
do_call(Request, From, State)
|
||||
catch
|
||||
Class:Error:Stacktrace ->
|
||||
error_logger:error_report(io_lib:format("~p handle call error, Req:~p ~p, stacktrace:~p~n",
|
||||
[?MODULE, Request, {Class, Error}, Stacktrace])),
|
||||
{reply, {error, {Class, Error}}, State}
|
||||
end.
|
||||
|
||||
handle_cast(Msg, State) ->
|
||||
try
|
||||
do_cast(Msg, State)
|
||||
catch
|
||||
Class:Error:Stacktrace ->
|
||||
error_logger:error_report(io_lib:format("~p handle cast error, Msg:~p, ~p, stacktrace:~w~n",
|
||||
[?MODULE, Msg, {Class, Error}, Stacktrace])),
|
||||
{noreply, State}
|
||||
end.
|
||||
|
||||
handle_info(Info, State) ->
|
||||
try
|
||||
do_info(Info, State)
|
||||
catch
|
||||
Class:Error:Stacktrace ->
|
||||
error_logger:error_report(io_lib:format("~p handle info error, Info:~p, ~p, stacktrace:~p~n",
|
||||
[?MODULE, Info, {Class, Error}, Stacktrace])),
|
||||
{noreply, State}
|
||||
end.
|
||||
|
||||
terminate(_Reason, State) ->
|
||||
#state{xdb_fd = XdbFd} = State,
|
||||
case is_pid(XdbFd) of
|
||||
true ->
|
||||
file:close(XdbFd);
|
||||
_ ->
|
||||
skip
|
||||
end,
|
||||
ok.
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
|
||||
%%==========================================
|
||||
%% Internal function
|
||||
%% =========================================
|
||||
do_call({search, Ip}, _From, #state{xdb_fd = IoDevice} = State) ->
|
||||
Reply = search_ip(IoDevice, Ip),
|
||||
{reply, Reply, State};
|
||||
|
||||
do_call(stop, _From, State) ->
|
||||
{stop, normal, stopped, State};
|
||||
|
||||
do_call(Request, From, State) ->
|
||||
error_logger:error_report(io_lib:format("unknown request: ~p, from:~p", [Request, From])),
|
||||
{noreply, State}.
|
||||
|
||||
do_cast(Msg, State) ->
|
||||
error_logger:error_report(io_lib:format("unknown msg: ~p", [Msg])),
|
||||
{noreply, State}.
|
||||
|
||||
do_info(Info, State) ->
|
||||
error_logger:error_report(io:format("unknown info: ~p", [Info])),
|
||||
{noreply, State}.
|
||||
|
||||
|
||||
load_vector_index(IoDevice) ->
|
||||
Key = ip2region_header_loaded,
|
||||
case persistent_term:get(Key, false) of
|
||||
true -> ok;
|
||||
_ ->
|
||||
{ok, <<_Header:?XDB_HEADER_SIZE/binary, VectorIndexBin/binary>> } =
|
||||
file:read(IoDevice, ?XDB_HEADER_SIZE + ?XDB_VECTOR_INDEX_COUNT*8),
|
||||
load_vector_index_aux(VectorIndexBin, 0),
|
||||
persistent_term:put(Key, true)
|
||||
end.
|
||||
|
||||
load_vector_index_aux(<<>>, _Index) -> ok;
|
||||
load_vector_index_aux(<<SPtr:32/little, EPtr:32/little, VectorIndexBin/binary>>, Index) ->
|
||||
Term = {Index, SPtr, EPtr},
|
||||
ets:insert(?XDB_VECTOR_INDEX, Term),
|
||||
load_vector_index_aux(VectorIndexBin, Index + 1).
|
||||
|
||||
|
||||
search_ip(IoDevice, Ip) ->
|
||||
IntIp = ip2region_util:ipv4_to_n(Ip),
|
||||
case ets:lookup(?IP2REGION_CACHE, IntIp) of
|
||||
[{_IntIp, RegionInfo}] ->
|
||||
RegionInfo;
|
||||
_ ->
|
||||
<<A:8, B:8, _Rest/binary>> = <<IntIp:32>>,
|
||||
VectorIdx = A * ?XDB_VECTOR_COLS + B,
|
||||
[{_, SPtr, EPtr}] = ets:lookup(?XDB_VECTOR_INDEX, VectorIdx),
|
||||
RegionInfo = search_ip(IoDevice, IntIp, SPtr, EPtr, 0, (EPtr - SPtr) div ?XDB_SEGMENT_INDEX_SIZE),
|
||||
ets:insert_new(?IP2REGION_CACHE, {IntIp, RegionInfo}),
|
||||
RegionInfo
|
||||
end.
|
||||
|
||||
search_ip(IoDevice, IntIp, SPtr, EPtr, Low, High) when Low =< High ->
|
||||
Middle = (Low + High) bsr 1,
|
||||
SPtr2 = SPtr + Middle * ?XDB_SEGMENT_INDEX_SIZE,
|
||||
{SIp, EIp, DataLen, DataPtr} = read_segement_index(IoDevice, SPtr2),
|
||||
if
|
||||
IntIp < SIp ->
|
||||
search_ip(IoDevice, IntIp, SPtr, EPtr, Low, Middle - 1);
|
||||
IntIp > EIp ->
|
||||
search_ip(IoDevice, IntIp, SPtr, EPtr, Middle + 1, High);
|
||||
true ->
|
||||
{ok, DataBin} = read_file(IoDevice, DataPtr, DataLen),
|
||||
unicode:characters_to_nfc_list(DataBin)
|
||||
end;
|
||||
search_ip(_IoDevice, _IntIp, _SPtr, _EPtr, _Low, _High) ->
|
||||
{error, unknown}.
|
||||
|
||||
read_file(IoDevice, Position, DataLength) ->
|
||||
file:position(IoDevice, {bof, Position}),
|
||||
file:read(IoDevice, DataLength).
|
||||
|
||||
read_segement_index(IoDevice, SPtr) ->
|
||||
case ets:lookup(?XDB_SEGMENT_INDEX, SPtr) of
|
||||
[{_SPtr, SIp, EIp, DataLen, DataPtr}] ->
|
||||
{SIp, EIp, DataLen, DataPtr};
|
||||
_ ->
|
||||
{ok, <<SIp:32/little, EIp:32/little, DataLen:16/little, DataPtr:32/little>>} =
|
||||
read_file(IoDevice, SPtr, ?XDB_SEGMENT_INDEX_SIZE),
|
||||
ets:insert_new(?XDB_SEGMENT_INDEX, {SPtr, SIp, EIp, DataLen, DataPtr}),
|
||||
{SIp, EIp, DataLen, DataPtr}
|
||||
end.
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
|
||||
%%%-------------------------------------------------------------------
|
||||
%% Copyright 2022 The Ip2Region Authors. All rights reserved.
|
||||
%% Use of this source code is governed by a Apache2.0-style
|
||||
%% license that can be found in the LICENSE file.
|
||||
%%
|
||||
%% @doc
|
||||
%% ip2region xdb client search api
|
||||
%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(xdb).
|
||||
-include("ip2region.hrl").
|
||||
|
||||
-export([search/1]).
|
||||
|
||||
-spec search(Ip :: tuple() | list() | binary()) -> Result :: binary | {error, Reason::atom()}.
|
||||
search(Ip) when is_integer(Ip); is_list(Ip); is_tuple(Ip); is_binary(Ip) ->
|
||||
case ip2region_util:ipv4_to_n(Ip) of
|
||||
IntIp when is_integer(IntIp) ->
|
||||
case ets:lookup(?IP2REGION_CACHE, IntIp) of
|
||||
[{_IntIp, Region}] -> Region;
|
||||
_ ->
|
||||
Worker = poolboy:checkout(?IP2REGION_POOL, true, infinity),
|
||||
try
|
||||
ip2region_worker:search(Worker, IntIp)
|
||||
after
|
||||
poolboy:checkin(?IP2REGION_POOL, Worker)
|
||||
end
|
||||
end;
|
||||
Ret ->
|
||||
Ret
|
||||
end.
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
%%%-------------------------------------------------------------------
|
||||
%% Copyright 2022 The Ip2Region Authors. All rights reserved.
|
||||
%% Use of this source code is governed by a Apache2.0-style
|
||||
%% license that can be found in the LICENSE file.
|
||||
%%
|
||||
%% @doc
|
||||
%% ip2region xdb client benchmark test
|
||||
%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(xdb_benchmark).
|
||||
-export([main/1]).
|
||||
|
||||
main(DataFile) ->
|
||||
application:ensure_started(ip2region),
|
||||
show_hw_sw_info(),
|
||||
IpList = load_test_data(DataFile),
|
||||
run(IpList).
|
||||
|
||||
show_hw_sw_info() ->
|
||||
io:format("CPU info:~n", []),
|
||||
io:format("~s", [os:cmd("egrep '^model name' /proc/cpuinfo | head -1")]),
|
||||
io:format("~s", [os:cmd("egrep '^cache' /proc/cpuinfo | head -1")]),
|
||||
io:format("~s", [os:cmd("egrep '^cpu MHz' /proc/cpuinfo | head -1")]),
|
||||
io:format("~s", [os:cmd("egrep '^bogomips' /proc/cpuinfo | head -1")]),
|
||||
io:format("cores/threads : ~s~n", [os:cmd("egrep -c '^processor' /proc/cpuinfo")]),
|
||||
io:format("Erlang info:~n", []),
|
||||
io:format("system_version:~s", [erlang:system_info(system_version)]),
|
||||
ok.
|
||||
|
||||
load_test_data(DataFile) ->
|
||||
{ok, Fd} = file:open(DataFile, [read]),
|
||||
T0 = os:timestamp(),
|
||||
IpList = load_test_data(Fd, []),
|
||||
T1 = os:timestamp(),
|
||||
Sec = timer:now_diff(T1, T0) / 1000000,
|
||||
io:format("load test data use ~ps~n", [Sec]),
|
||||
IpList.
|
||||
|
||||
load_test_data(Fd, IpList) ->
|
||||
case file:read_line(Fd) of
|
||||
{ok, Ip} ->
|
||||
case string:tokens(unicode:characters_to_list(Ip), "|") of
|
||||
[SIp | _Tail] ->
|
||||
load_test_data(Fd, [string:trim(SIp)| IpList]);
|
||||
_ ->
|
||||
load_test_data(Fd, IpList)
|
||||
end;
|
||||
_ ->
|
||||
file:close(Fd),
|
||||
IpList
|
||||
end.
|
||||
|
||||
run(IpList) ->
|
||||
garbage_collect(),
|
||||
io:format("~nstart run benchmark tests~n", []),
|
||||
io:format("~nsearch from file:~n", []),
|
||||
run_test(IpList),
|
||||
io:format("~nsearch from cache:~n", []),
|
||||
run_test(IpList),
|
||||
io:format("~nbenchmark test finish~n", []).
|
||||
|
||||
run_test(IpList) ->
|
||||
T0 = os:timestamp(),
|
||||
run_test_aux(IpList),
|
||||
T1 = os:timestamp(),
|
||||
Sec = timer:now_diff(T1, T0) / 1000000,
|
||||
IpCount = length(IpList),
|
||||
io:format("ip count:~p,~ntotal time: ~ps,~nsearch ~p times per second,~nuse ~p micro second per search~n",
|
||||
[IpCount, Sec, IpCount / Sec, Sec * 1000000/IpCount]).
|
||||
|
||||
run_test_aux([]) -> ok;
|
||||
run_test_aux([Ip | Tail]) ->
|
||||
xdb:search(Ip),
|
||||
run_test_aux(Tail).
|
||||
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
-module(xdb_test).
|
||||
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
search_test_() ->
|
||||
application:ensure_started(ip2region),
|
||||
A = "中国|0|广东省|广州市|电信",
|
||||
Region0 = xdb:search("1.0.8.0"),
|
||||
Region1 = xdb:search(<<"1.0.8.0">>),
|
||||
Region2 = xdb:search({1,0,8,0}),
|
||||
Region3 = xdb:search("xxx.0.8.0"),
|
||||
[
|
||||
?_assert(A =:= Region0),
|
||||
?_assert(A =:= Region1),
|
||||
?_assert(A =:= Region2),
|
||||
?_assert({error, bad_ip_format} =:= Region3)
|
||||
|
||||
].
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
# ip2region golang binding makefile
|
||||
all: build
|
||||
.PHONY: all
|
||||
|
||||
build:
|
||||
go build -o xdb_searcher
|
||||
test:
|
||||
go test -v ./...
|
||||
clean:
|
||||
find ./ -name xdb_searcher | xargs rm -f
|
||||
|
|
@ -0,0 +1,245 @@
|
|||
# ip2region xdb golang 查询客户端实现
|
||||
|
||||
# 使用方式
|
||||
|
||||
### package 获取
|
||||
```bash
|
||||
go get github.com/lionsoul2014/ip2region/binding/golang
|
||||
```
|
||||
|
||||
### 关于查询服务
|
||||
从 `3.11.0` 版本开始提供了一个双协议兼容且并发安全的 `Ip2Region` 查询服务,**建议优先使用该方式来进行查询调用**,具体使用方式如下:
|
||||
```go
|
||||
import "github.com/lionsoul2014/ip2region/binding/golang/service"
|
||||
|
||||
// 1, 创建 v4 的配置:指定缓存策略和 v4 的 xdb 文件路径
|
||||
// 参数1: 缓存策略, options: service.NoCache / service.VIndexCache / service.BufferCache
|
||||
// 参数2: xdb 文件路径
|
||||
// 参数3: 初始化的查询器数量
|
||||
v4Config, err := service.NewV4Config(service.VIndexCache, "ip2region v4 xdb path", 20)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create v4 config: %s", err)
|
||||
}
|
||||
|
||||
// 2, 创建 v6 的配置:指定缓存策略和 v6 的 xdb 文件路径
|
||||
v6Config, err := service.NewV6Config(service.VIndexCache, "ip2region v6 xdb path", 20)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create v6 config: %s", err)
|
||||
}
|
||||
|
||||
// 3,通过上述配置创建 Ip2Region 查询服务
|
||||
ip2region, err := service.NewIp2Region(v4Config, v6Config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create ip2region service: %s", err)
|
||||
}
|
||||
|
||||
// 4,导出 ip2region 服务进行双版本的IP地址的并发查询,例如:
|
||||
v4Region, err := ip2region.SearchByStr("113.92.157.29") // 进行 IPv4 查询
|
||||
v6Region, err := ip2region.SearchByStr("240e:3b7:3272:d8d0:db09:c067:8d59:539e") // 进行 IPv6 查询
|
||||
|
||||
|
||||
// 5,在服务需要关闭的时候,同时关闭 ip2region 查询服务
|
||||
ip2region.Close()
|
||||
```
|
||||
|
||||
##### `Ip2Region` 查询备注:
|
||||
1. 该查询服务的 API 并发安全且同时支持 IPv4 和 Ipv6 的地址,内部实现会自动判断。
|
||||
2. v4 和 v6 的配置需要单独创建,可以给 v4 和 v6 设置使用不同的缓存策略,也可以指定其中一个为 `nil` 则该版本的 IP 地址查询都会返回 `""`。
|
||||
3. 请结合您的项目的并发数设置一个合适的查询器数量,这个值在运行过程中是固定的,每次查询会从池子里租借一个查询器来完成查询操作,查询完成后再归还回去,如果租借的时候池子已经空了则等待直到有可用的查询器来完成查询服务。
|
||||
4. 如果配置设置的缓存策略为 `service.BufferCache` 即 `全内存缓存` 则默认会使用单实例的内存查询器,该实现天生并发安全,此时指定的查询器数量无效。
|
||||
5. 如果 `Ip2Region` 查询器在提供服务期间,调用 Close 默认会最大等待 10 秒钟来等待尽量多的查询器归还,也可以调用 `CloseTimeout` 来自定义最长等待时间。
|
||||
|
||||
|
||||
### 关于查询 API
|
||||
定位信息查询 API 原型为:
|
||||
```go
|
||||
SearchByStr(string) (string, error)
|
||||
Search([]byte) (string, error)
|
||||
```
|
||||
查询出错则 error 会包含具体的错误信息,查询成功会返回字符串的 `region` 信息,如果指定的 IP 查询不到则会返回空字符串 `""`。
|
||||
|
||||
### 关于 IPv4 / IPv6
|
||||
该 xdb 查询客户端实现同时支持对 IPv4 和 IPv6 的查询,使用方式如下:
|
||||
```go
|
||||
// 如果是 IPv4: 设置 xdb 路径为 v4 的 xdb 文件,IP版本指定为 xdb.IPv4
|
||||
dbPath := "../../data/ip2region_v4.xdb" // 或者你的 ipv4 xdb 的路径
|
||||
version := xdb.IPv4
|
||||
|
||||
// 如果是 IPv6: 设置 xdb 路径为 v6 的 xdb 文件,IP版本指定为 xdb.IPv6
|
||||
dbPath = "../../data/ip2region_v6.xdb" // 或者你的 ipv6 xdb 路径
|
||||
version = xdb.IPv6
|
||||
|
||||
// dbPath 指定的 xdb 的 IP 版本必须和 version 指定的一致,不然查询执行的时候会报错
|
||||
// 备注:以下演示直接使用 dbPath 和 version 变量
|
||||
```
|
||||
|
||||
### 文件验证
|
||||
建议您主动去验证 xdb 文件的适用性,因为后期的一些新功能可能会导致目前的 Searcher 版本无法适用你使用的 xdb 文件,验证可以避免运行过程中的一些不可预测的错误。
|
||||
你不需要每次都去验证,例如在服务启动的时候,或者手动调用命令验证确认版本匹配即可,不要在每次创建的 Searcher 的时候运行验证,这样会影响查询的响应速度,尤其是高并发的使用场景。
|
||||
```go
|
||||
err := xdb.VerifyFromFile(dbPath)
|
||||
if err != nil {
|
||||
// err 包含的验证的错误
|
||||
return fmt.Errorf("xdb file verify: %w", err)
|
||||
}
|
||||
|
||||
// 当前使用的 Searcher 可以安全的用于对 dbPath 指向的 xdb 的查询操作
|
||||
```
|
||||
|
||||
### 完全基于文件的查询
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/lionsoul2014/ip2region/binding/golang/xdb"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 通过 version 和 dbPath 创建完全基于文件的查询对象
|
||||
searcher, err := xdb.NewWithFileOnly(version, dbPath)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to create searcher: %s\n", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
defer searcher.Close()
|
||||
|
||||
// 定位信息查询:IPv4 或者 IPv6 的地址都支持
|
||||
var ip = "1.2.3.4" // IPv4
|
||||
// ip = "2001:4:112:ffff:ffff:ffff:ffff:ffff" // IPv6
|
||||
var tStart = time.Now()
|
||||
region, err := searcher.SearchByStr(ip)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to SearchIP(%s): %s\n", ip, err)
|
||||
return
|
||||
}
|
||||
|
||||
// IPv4 或者 IPv6 的定位信息
|
||||
fmt.Printf("{region: %s, took: %s}\n", region, time.Since(tStart))
|
||||
|
||||
// 备注:并发使用,每个 goroutine 需要创建一个独立的 searcher 对象。
|
||||
}
|
||||
```
|
||||
|
||||
### 缓存 `VectorIndex` 索引
|
||||
|
||||
可以预先加载 `vectorIndex` 缓存,然后做成全局变量,每次创建 searcher 的时候使用全局的 `vectorIndex`,可以减少一次固定的 IO 操作从而加速查询,减少系统 io 压力。
|
||||
```go
|
||||
// 1、从 dbPath 加载 VectorIndex 缓存,把下述 vIndex 变量全局到内存里面。
|
||||
vIndex, err := xdb.LoadVectorIndexFromFile(dbPath)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to load vector index from `%s`: %s\n", dbPath, err)
|
||||
return
|
||||
}
|
||||
|
||||
// 2、用全局的 vIndex 创建带 VectorIndex 缓存的查询对象。
|
||||
searcher, err := xdb.NewWithVectorIndex(version, dbPath, vIndex)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to create searcher with vector index: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 备注:并发使用,全部 goroutine 共享全局的只读 vIndex 缓存,每个 goroutine 创建一个独立的 searcher 对象
|
||||
```
|
||||
|
||||
### 缓存整个 `xdb` 数据
|
||||
|
||||
可以预先加载整个 ip2region.xdb 到内存,完全基于内存查询,类似于之前的 memory search 查询。
|
||||
```go
|
||||
// 1、从 dbPath 加载整个 xdb 到内存
|
||||
cBuff, err := xdb.LoadContentFromFile(dbPath)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to load content from `%s`: %s\n", dbPath, err)
|
||||
return
|
||||
}
|
||||
|
||||
// 2、用全局的 cBuff 创建完全基于内存的查询对象。
|
||||
searcher, err := xdb.NewWithBuffer(version, cBuff)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to create searcher with content: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 备注:并发使用,用整个 xdb 缓存创建的 searcher 对象可以安全用于并发。
|
||||
```
|
||||
|
||||
|
||||
|
||||
# 编译测试程序
|
||||
|
||||
通过如下方式编译得到 xdb_searcher 可执行程序:
|
||||
```bash
|
||||
# 切换到 golang binding 根目录
|
||||
make
|
||||
```
|
||||
|
||||
|
||||
# 查询测试
|
||||
|
||||
### 查询命令
|
||||
通过 `./xdb_searcher search` 命令来测试 xdb 的查询:
|
||||
```bash
|
||||
➜ golang git:(master) ✗ ./xdb_searcher search --help
|
||||
./xdb_searcher search [command options]
|
||||
options:
|
||||
--v4-db string ip2region v4 binary xdb file path
|
||||
--v4-cache-policy string v4 cache policy, default vectorIndex, options: file/vectorIndex/content
|
||||
--v6-db string ip2region v6 binary xdb file path
|
||||
--v6-cache-policy string v6 cache policy, default vectorIndex, options: file/vectorIndex/content
|
||||
--help print this help menu
|
||||
```
|
||||
|
||||
### 参数解析
|
||||
1. `v4-xdb`: IPv4 的 xdb 文件路径,默认为仓库中的 data/ip2region_v4.xdb
|
||||
2. `v6-xdb`: IPv6 的 xdb 文件路径,默认为仓库中的 data/ip2region_v6.xdb
|
||||
3. `v4-cache-policy`: v4 查询使用的缓存策略,默认为 `vectorIndex`,可选:file/vectorIndex/content
|
||||
4. `v6-cache-policy`: v6 查询使用的缓存策略,默认为 `vectorIndex`,可选:file/vectorIndex/content
|
||||
|
||||
### 测试 Demo
|
||||
例如:使用默认的 data/ip2region_v4.xdb 和 data/ip2region_v6.xdb 进行查询测试:
|
||||
```bash
|
||||
➜ golang git:(master) ✗ ./xdb_searcher search
|
||||
ip2region search service test program
|
||||
+-v4 db: /data01/code/c/ip2region/data/ip2region_v4.xdb (vectorIndex)
|
||||
+-v6 db: /data01/code/c/ip2region/data/ip2region_v6.xdb (vectorIndex)
|
||||
type 'quit' to exit
|
||||
ip2region>> 1.2.3.4
|
||||
{region: 美国|华盛顿|0|谷歌, took: 69.088µs}
|
||||
ip2region>> 240e:3b7:3272:d8d0:db09:c067:8d59:539e
|
||||
{region: 中国|广东省|深圳市|家庭宽带, took: 67.756µs}
|
||||
ip2region>>
|
||||
```
|
||||
输入 v4 或者 v6 的 IP 地址即可进行查询测试,也可以分别设置 `cache-policy` 为 file/vectorIndex/content 来测试三种不同缓存实现的查询效果。
|
||||
|
||||
|
||||
# bench 测试
|
||||
|
||||
### 测试命令
|
||||
通过 `xdb_searcher bench` 命令来进行自动 bench 测试,一方面确保程序和 `xdb` 文件都没有错误,另一方面通过大量的查询得到平均查询性能:
|
||||
```bash
|
||||
➜ golang git:(fr_xdb_ipv6) ./xdb_searcher bench
|
||||
./xdb_searcher bench [command options]
|
||||
options:
|
||||
--db string ip2region binary xdb file path
|
||||
--src string source ip text file path
|
||||
--cache-policy string cache policy: file/vectorIndex/content
|
||||
```
|
||||
|
||||
### v4 bench
|
||||
例如:通过 data/ip2region_v4.xdb 和 data/ipv4_source.txt 进行 ipv4 的 bench 测试:
|
||||
```bash
|
||||
./xdb_searcher bench --db=../../data/ip2region_v4.xdb --src=../../data/ipv4_source.txt
|
||||
```
|
||||
|
||||
### v6 bench
|
||||
例如:通过 data/ip2region_v6.xdb 和 data/ipv6_source.txt 进行 ipv6 的 bench 测试:
|
||||
```bash
|
||||
./xdb_searcher bench --db=../../data/ip2region_v6.xdb --src=../../data/ipv6_source.txt
|
||||
```
|
||||
|
||||
可以设置 `cache-policy` 参数来分别测试 file/vectorIndex/content 不同缓存实现机制的效率。
|
||||
|
||||
*请注意 bench 使用的 src 文件需要是生成对应的 xdb 文件的相同的源文件*。
|
||||
|
||||
bench 程序会逐行读取 `src` 指定的源IP文件,然后每个 IP 段选取 5 个固定位置的 IP 进行测试,以确保查询的 region 信息和原始的 region 信息是相同。测试途中没有调试信息的输出,有错误会打印错误信息并且终止运行,所以看到 `Bench finished` 就表示 bench 成功了,cost 是表示每次查询操作的平均时间(ns)。
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
module github.com/lionsoul2014/ip2region/binding/golang
|
||||
|
||||
go 1.17
|
||||
|
||||
require github.com/mitchellh/go-homedir v1.1.0
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
|
|
@ -0,0 +1,383 @@
|
|||
// Copyright 2022 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// ---
|
||||
// @Author Lion <chenxin619315@gmail.com>
|
||||
// @Date 2022/06/16
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/lionsoul2014/ip2region/binding/golang/service"
|
||||
"github.com/lionsoul2014/ip2region/binding/golang/xdb"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
)
|
||||
|
||||
func getXdbPath(fileName string) (string, error) {
|
||||
binPath, err := os.Executable()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get executale: %w", err)
|
||||
}
|
||||
|
||||
xdbPath := filepath.Join(filepath.Dir(filepath.Dir(filepath.Dir(binPath))), "/data/", fileName)
|
||||
_, err = os.Stat(xdbPath)
|
||||
if err != nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// fmt.Printf("xdbPath=%s\n", xdbPath)
|
||||
return xdbPath, nil
|
||||
}
|
||||
|
||||
func createService(v4XdbPath string, v4CachePolicy string, v6XdbPath string, v6CachePolicy string) (*service.Ip2Region, error) {
|
||||
// try to create v4 config
|
||||
v4CPolicy, err := service.CachePolicyFromName(v4CachePolicy)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse v4 cache policy: %w", err)
|
||||
}
|
||||
|
||||
v4DbPath, err := homedir.Expand(v4XdbPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Expand(`%s`): %s", v4XdbPath, err)
|
||||
}
|
||||
v4Config, err := service.NewV4Config(v4CPolicy, v4DbPath, 1)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("NewV4Config: %w", err)
|
||||
}
|
||||
|
||||
// try to create v6 config
|
||||
v6Policy, err := service.CachePolicyFromName(v6CachePolicy)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse v6 cache policy: %w", err)
|
||||
}
|
||||
|
||||
v6DbPath, err := homedir.Expand(v6XdbPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Expand(`%s`): %s", v6XdbPath, err)
|
||||
}
|
||||
v6Config, err := service.NewV6Config(v6Policy, v6DbPath, 1)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("NewV6Config: %w", err)
|
||||
}
|
||||
|
||||
return service.NewIp2Region(v4Config, v6Config)
|
||||
}
|
||||
|
||||
func createSearcher(dbPath string, cachePolicy string) (*xdb.Searcher, error) {
|
||||
handle, err := os.OpenFile(dbPath, os.O_RDONLY, 0600)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("open xdb file `%s`: %w", dbPath, err)
|
||||
}
|
||||
|
||||
defer handle.Close()
|
||||
|
||||
// verify the xdb file
|
||||
// @Note: do NOT call it every time you create a searcher since this will slow down the search response.
|
||||
// @see the util.Verify function for details.
|
||||
err = xdb.Verify(handle)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("xdb verify: %w", err)
|
||||
}
|
||||
|
||||
// auto-detect the ip version from the xdb header
|
||||
header, err := xdb.LoadHeader(handle)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load header from `%s`: %s", dbPath, err)
|
||||
}
|
||||
|
||||
version, err := xdb.VersionFromHeader(header)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to detect IP version from `%s`: %s", dbPath, err)
|
||||
}
|
||||
|
||||
switch cachePolicy {
|
||||
case "nil", "file":
|
||||
return xdb.NewWithFileOnly(version, dbPath)
|
||||
case "vectorIndex":
|
||||
vIndex, err := xdb.LoadVectorIndexFromFile(dbPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load vector index from `%s`: %w", dbPath, err)
|
||||
}
|
||||
|
||||
return xdb.NewWithVectorIndex(version, dbPath, vIndex)
|
||||
case "content":
|
||||
cBuff, err := xdb.LoadContentFromFile(dbPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load content from '%s': %w", dbPath, err)
|
||||
}
|
||||
|
||||
return xdb.NewWithBuffer(version, cBuff)
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid cache policy `%s`, options: file/vectorIndex/content", cachePolicy)
|
||||
}
|
||||
}
|
||||
|
||||
func printHelp() {
|
||||
fmt.Printf("ip2region xdb searcher\n")
|
||||
fmt.Printf("%s [command] [command options]\n", os.Args[0])
|
||||
fmt.Printf("Command: \n")
|
||||
fmt.Printf(" search search input test\n")
|
||||
fmt.Printf(" bench search bench test\n")
|
||||
}
|
||||
|
||||
func testSearch() {
|
||||
var err error
|
||||
var help = ""
|
||||
var v4DbPath, v4CachePolicy = "", "vectorIndex"
|
||||
var v6DbPath, v6CachePolicy = "", "vectorIndex"
|
||||
for i := 2; i < len(os.Args); i++ {
|
||||
r := os.Args[i]
|
||||
if len(r) < 5 {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.Index(r, "--") != 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
var key, val = "", ""
|
||||
var sIdx = strings.Index(r, "=")
|
||||
if sIdx < 0 {
|
||||
// fmt.Printf("missing = for args pair '%s'\n", r)
|
||||
// return
|
||||
key = r[2:]
|
||||
} else {
|
||||
key = r[2:sIdx]
|
||||
val = r[sIdx+1:]
|
||||
}
|
||||
|
||||
switch key {
|
||||
case "help":
|
||||
if val == "" {
|
||||
help = "true"
|
||||
} else {
|
||||
help = val
|
||||
}
|
||||
case "v4-db":
|
||||
v4DbPath = val
|
||||
case "v4-cache-policy":
|
||||
v4CachePolicy = val
|
||||
case "v6-db":
|
||||
v6DbPath = val
|
||||
case "v6-cache-policy":
|
||||
v6CachePolicy = val
|
||||
default:
|
||||
fmt.Printf("undefined option `%s`\n", r)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// check and get the get the default v4 xdb path
|
||||
if v4DbPath == "" {
|
||||
v4DbPath, err = getXdbPath("ip2region_v4.xdb")
|
||||
if err != nil {
|
||||
fmt.Printf("failed to get v4 xdb path: %s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// check and get the get the default v6 xdb path
|
||||
if v6DbPath == "" {
|
||||
v6DbPath, err = getXdbPath("ip2region_v6.xdb")
|
||||
if err != nil {
|
||||
fmt.Printf("failed to get v6 xdb path: %s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if v4DbPath == "" || v6DbPath == "" || help == "true" {
|
||||
fmt.Printf("%s search [command options]\n", os.Args[0])
|
||||
fmt.Printf("options:\n")
|
||||
fmt.Printf(" --v4-db string ip2region v4 binary xdb file path\n")
|
||||
fmt.Printf(" --v4-cache-policy string v4 cache policy, default vectorIndex, options: file/vectorIndex/content\n")
|
||||
fmt.Printf(" --v6-db string ip2region v6 binary xdb file path\n")
|
||||
fmt.Printf(" --v6-cache-policy string v6 cache policy, default vectorIndex, options: file/vectorIndex/content\n")
|
||||
fmt.Printf(" --help print this help menu\n")
|
||||
return
|
||||
}
|
||||
|
||||
// create the search service with the xdb paths and cache policies
|
||||
ip2region, err := createService(v4DbPath, v4CachePolicy, v6DbPath, v6CachePolicy)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to create ip2region service: %s\n", err.Error())
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
ip2region.Close()
|
||||
fmt.Printf("searcher test program exited, thanks for trying\n")
|
||||
}()
|
||||
|
||||
fmt.Printf(`ip2region search service test program
|
||||
+-v4 db: %s (%s)
|
||||
+-v6 db: %s (%s)
|
||||
type 'quit' to exit
|
||||
`, v4DbPath, v4CachePolicy, v6DbPath, v6CachePolicy)
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
for {
|
||||
fmt.Print("ip2region>> ")
|
||||
str, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
log.Fatalf("failed to read string: %s", err)
|
||||
}
|
||||
|
||||
line := strings.TrimSpace(strings.TrimSuffix(str, "\n"))
|
||||
if len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if line == "quit" {
|
||||
break
|
||||
}
|
||||
|
||||
tStart := time.Now()
|
||||
region, err := ip2region.SearchByStr(line)
|
||||
if err != nil {
|
||||
fmt.Printf("\x1b[0;31m{err: %s}\x1b[0m\n", err.Error())
|
||||
} else {
|
||||
fmt.Printf("\x1b[0;32m{region: %s, took: %s}\x1b[0m\n", region, time.Since(tStart))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testBench() {
|
||||
var err error
|
||||
var dbFile, srcFile, cachePolicy = "", "", "vectorIndex"
|
||||
for i := 2; i < len(os.Args); i++ {
|
||||
r := os.Args[i]
|
||||
if len(r) < 5 {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.Index(r, "--") != 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
var sIdx = strings.Index(r, "=")
|
||||
if sIdx < 0 {
|
||||
fmt.Printf("missing = for args pair '%s'\n", r)
|
||||
return
|
||||
}
|
||||
|
||||
switch r[2:sIdx] {
|
||||
case "db":
|
||||
dbFile = r[sIdx+1:]
|
||||
case "src":
|
||||
srcFile = r[sIdx+1:]
|
||||
case "cache-policy":
|
||||
cachePolicy = r[sIdx+1:]
|
||||
default:
|
||||
fmt.Printf("undefined option `%s`\n", r)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if dbFile == "" || srcFile == "" {
|
||||
fmt.Printf("%s bench [command options]\n", os.Args[0])
|
||||
fmt.Printf("options:\n")
|
||||
fmt.Printf(" --db string ip2region binary xdb file path\n")
|
||||
fmt.Printf(" --src string source ip text file path\n")
|
||||
fmt.Printf(" --cache-policy string cache policy: file/vectorIndex/content\n")
|
||||
return
|
||||
}
|
||||
|
||||
dbPath, err := homedir.Expand(dbFile)
|
||||
if err != nil {
|
||||
fmt.Printf("invalid xdb file path `%s`: %s", dbFile, err)
|
||||
return
|
||||
}
|
||||
|
||||
searcher, err := createSearcher(dbPath, cachePolicy)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to create searcher: %s\n", err.Error())
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
searcher.Close()
|
||||
}()
|
||||
|
||||
handle, err := os.OpenFile(srcFile, os.O_RDONLY, 0600)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to open source text file: %s\n", err)
|
||||
return
|
||||
}
|
||||
defer handle.Close()
|
||||
|
||||
var count, tStart, costs = int64(0), time.Now(), int64(0)
|
||||
var scanner = bufio.NewScanner(handle)
|
||||
scanner.Split(bufio.ScanLines)
|
||||
for scanner.Scan() {
|
||||
var l = strings.TrimSpace(strings.TrimSuffix(scanner.Text(), "\n"))
|
||||
var ps = strings.SplitN(l, "|", 3)
|
||||
if len(ps) != 3 {
|
||||
fmt.Printf("invalid ip segment line `%s`\n", l)
|
||||
return
|
||||
}
|
||||
|
||||
sip, err := xdb.ParseIP(ps[0])
|
||||
if err != nil {
|
||||
fmt.Printf("check start ip `%s`: %s\n", ps[0], err)
|
||||
return
|
||||
}
|
||||
|
||||
eip, err := xdb.ParseIP(ps[1])
|
||||
if err != nil {
|
||||
fmt.Printf("check end ip `%s`: %s\n", ps[1], err)
|
||||
return
|
||||
}
|
||||
|
||||
if xdb.IPCompare(sip, eip) > 0 {
|
||||
fmt.Printf("start ip(%s) should not be greater than end ip(%s)\n", ps[0], ps[1])
|
||||
return
|
||||
}
|
||||
|
||||
for _, ip := range [][]byte{sip, eip} {
|
||||
sTime := time.Now()
|
||||
region, err := searcher.Search(ip)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to search ip '%s': %s\n", xdb.IP2String(ip), err)
|
||||
return
|
||||
}
|
||||
|
||||
costs += time.Since(sTime).Nanoseconds()
|
||||
|
||||
// check the region info
|
||||
if region != ps[2] {
|
||||
fmt.Printf("failed Search(%s) with (%s != %s)\n", xdb.IP2String(ip), region, ps[2])
|
||||
return
|
||||
}
|
||||
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
||||
cost := time.Since(tStart)
|
||||
fmt.Printf("Bench finished, {cachePolicy: %s, total: %d, took: %s, cost: %d μs/op}\n",
|
||||
cachePolicy, count, cost, costs/count/1000)
|
||||
}
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
printHelp()
|
||||
return
|
||||
}
|
||||
|
||||
// set the log flag
|
||||
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
|
||||
switch strings.ToLower(os.Args[1]) {
|
||||
case "search":
|
||||
testSearch()
|
||||
case "bench":
|
||||
testBench()
|
||||
default:
|
||||
printHelp()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,167 @@
|
|||
// Copyright 2022 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/lionsoul2014/ip2region/binding/golang/xdb"
|
||||
)
|
||||
|
||||
// ---
|
||||
// Ip2Region service config
|
||||
//
|
||||
// @Author Lion <chenxin619315@gmail.com>
|
||||
// @Date 2025/12/03
|
||||
|
||||
const (
|
||||
NoCache = 0
|
||||
VIndexCache = 1
|
||||
BufferCache = 2
|
||||
)
|
||||
|
||||
func CachePolicyFromName(name string) (int, error) {
|
||||
switch strings.ToLower(name) {
|
||||
case "file", "nocache":
|
||||
return NoCache, nil
|
||||
case "vectorindex", "vindex", "vindexcache":
|
||||
return VIndexCache, nil
|
||||
case "content", "buffercache":
|
||||
return BufferCache, nil
|
||||
default:
|
||||
return NoCache, fmt.Errorf("invalid cache policy name `%s`", name)
|
||||
}
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
cachePolicy int
|
||||
ipVersion *xdb.Version
|
||||
|
||||
// xdb file path
|
||||
xdbPath string
|
||||
header *xdb.Header
|
||||
|
||||
// buffers
|
||||
vIndex []byte
|
||||
cBuffer []byte
|
||||
|
||||
searchers int
|
||||
}
|
||||
|
||||
func NewV4Config(cachePolicy int, xdbPath string, searchers int) (*Config, error) {
|
||||
return newConfig(cachePolicy, xdb.IPv4, xdbPath, searchers)
|
||||
}
|
||||
|
||||
func NewV6Config(cachePolicy int, xdbPath string, searchers int) (*Config, error) {
|
||||
return newConfig(cachePolicy, xdb.IPv6, xdbPath, searchers)
|
||||
}
|
||||
|
||||
func newConfig(cachePolicy int, ipVersion *xdb.Version, xdbPath string, searchers int) (*Config, error) {
|
||||
if searchers < 1 {
|
||||
return nil, fmt.Errorf("searchers=%d, > 0 expected", searchers)
|
||||
}
|
||||
|
||||
// open the xdb binary file
|
||||
handle, err := os.OpenFile(xdbPath, os.O_RDONLY, 0600)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 1, verify the xdb
|
||||
err = xdb.Verify(handle)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 2, load the header
|
||||
header, err := xdb.LoadHeader(handle)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// verify the ip version
|
||||
xIpVersion, err := xdb.VersionFromHeader(header)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if xIpVersion.Id != ipVersion.Id {
|
||||
return nil, fmt.Errorf("ip verison not match: xdb file %s with ip version=%s, as %s expected", xdbPath, xIpVersion.Name, ipVersion.Name)
|
||||
}
|
||||
|
||||
// 3, check and load the vector index buffer
|
||||
var vIndex []byte = nil
|
||||
if cachePolicy == VIndexCache {
|
||||
vIndex, err = xdb.LoadVectorIndex(handle)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// 4, check and load the content buffer
|
||||
var cBuffer []byte = nil
|
||||
if cachePolicy == BufferCache {
|
||||
cBuffer, err = xdb.LoadContent(handle)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &Config{
|
||||
cachePolicy: cachePolicy,
|
||||
ipVersion: ipVersion,
|
||||
|
||||
xdbPath: xdbPath,
|
||||
header: header,
|
||||
|
||||
vIndex: vIndex,
|
||||
cBuffer: cBuffer,
|
||||
|
||||
searchers: searchers,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Config) String() string {
|
||||
vIndex := "null"
|
||||
if c.vIndex != nil {
|
||||
vIndex = fmt.Sprintf("{bytes:%d}", len(c.vIndex))
|
||||
}
|
||||
|
||||
cBuffer := "null"
|
||||
if c.cBuffer != nil {
|
||||
cBuffer = fmt.Sprintf("{bytes:%d}", len(c.cBuffer))
|
||||
}
|
||||
|
||||
return fmt.Sprintf(
|
||||
"{cache_policy:%d, version:%s, xdb_path:%s, header:%s, v_index:%s, c_buffer:%s}",
|
||||
c.cachePolicy, c.ipVersion.String(), c.xdbPath, c.header.String(), vIndex, cBuffer,
|
||||
)
|
||||
}
|
||||
|
||||
func (c *Config) CachePolicy() int {
|
||||
return c.cachePolicy
|
||||
}
|
||||
|
||||
func (c *Config) IPVersion() *xdb.Version {
|
||||
return c.ipVersion
|
||||
}
|
||||
|
||||
func (c *Config) Header() *xdb.Header {
|
||||
return c.header
|
||||
}
|
||||
|
||||
func (c *Config) VIndex() []byte {
|
||||
return c.vIndex
|
||||
}
|
||||
|
||||
func (c *Config) CBuffer() []byte {
|
||||
return c.cBuffer
|
||||
}
|
||||
|
||||
func (c *Config) Searchers() int {
|
||||
return c.searchers
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
// Copyright 2022 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestV4Config(t *testing.T) {
|
||||
v4Config, err := NewV4Config(VIndexCache, "../../../data/ip2region_v4.xdb", 10)
|
||||
if err != nil {
|
||||
t.Errorf("failed to new v4 config: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
v4BufferConfig, err := NewV4Config(BufferCache, "../../../data/ip2region_v4.xdb", 10)
|
||||
if err != nil {
|
||||
t.Errorf("failed to new v4 config: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("v4Config: %s\n", v4Config)
|
||||
fmt.Printf("v4BufferConfig: %s\n", v4BufferConfig)
|
||||
}
|
||||
|
||||
func TestV6Config(t *testing.T) {
|
||||
v6Config, err := NewV6Config(NoCache, "../../../data/ip2region_v6.xdb", 10)
|
||||
if err != nil {
|
||||
t.Errorf("failed to new v6 config: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("v6Config: %s\n", v6Config)
|
||||
}
|
||||
|
|
@ -0,0 +1,182 @@
|
|||
// Copyright 2022 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/lionsoul2014/ip2region/binding/golang/xdb"
|
||||
)
|
||||
|
||||
// ---
|
||||
// Ip2Region service
|
||||
// 1. Unified query interface to IPv4 and IPv6 address.
|
||||
// 2. Concurrency search support.
|
||||
//
|
||||
// @Author Lion <chenxin619315@gmail.com>
|
||||
// @Date 2025/12/05
|
||||
|
||||
type Ip2Region struct {
|
||||
// v4 pool for cache policy vIndex or NoCache
|
||||
v4Pool *SearcherPool
|
||||
|
||||
// v4 xdb searcher for full in-memeory search
|
||||
v4InMemSearcher *xdb.Searcher
|
||||
|
||||
// v6 pool for cache policy vIndex or NoCache:w
|
||||
v6Pool *SearcherPool
|
||||
|
||||
// v6 xdb searcher for full in-memeory search
|
||||
v6InMemSearcher *xdb.Searcher
|
||||
}
|
||||
|
||||
// create a new Ip2Region service with specified v4 and v6 config.
|
||||
// set it to nil to disabled the specified search for the specified version.
|
||||
func NewIp2Region(v4Config *Config, v6Config *Config) (*Ip2Region, error) {
|
||||
var err error
|
||||
|
||||
// check and init the v4 pool or in-memory searcher
|
||||
var v4Pool *SearcherPool
|
||||
var v4InMemSearcher *xdb.Searcher
|
||||
if v4Config == nil {
|
||||
// with IPv4 disabled ?
|
||||
v4Pool = nil
|
||||
v4InMemSearcher = nil
|
||||
} else if v4Config.cachePolicy == BufferCache {
|
||||
v4Pool = nil
|
||||
v4InMemSearcher, err = xdb.NewWithBuffer(v4Config.ipVersion, v4Config.cBuffer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create v4 in-memory searcher: %w", err)
|
||||
}
|
||||
} else {
|
||||
v4InMemSearcher = nil
|
||||
v4Pool, err = NewSearcherPool(v4Config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create v4 searcher pool: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// check and init the v6 pool or in-memory searcher
|
||||
var v6Pool *SearcherPool
|
||||
var v6InMemSearcher *xdb.Searcher
|
||||
if v6Config == nil {
|
||||
v6Pool = nil
|
||||
v6InMemSearcher = nil
|
||||
} else if v6Config.cachePolicy == BufferCache {
|
||||
v6Pool = nil
|
||||
v6InMemSearcher, err = xdb.NewWithBuffer(v6Config.ipVersion, v6Config.cBuffer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create v6 in-memory searcher: %w", err)
|
||||
}
|
||||
} else {
|
||||
v6InMemSearcher = nil
|
||||
v6Pool, err = NewSearcherPool(v6Config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create v6 in-memeory searcher pool: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return &Ip2Region{
|
||||
v4Pool: v4Pool,
|
||||
v4InMemSearcher: v4InMemSearcher,
|
||||
|
||||
v6Pool: v6Pool,
|
||||
v6InMemSearcher: v6InMemSearcher,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// create the ip2region search service with the specified v4 & v6 xdb path.
|
||||
// with default cache policy VIndexCache and default searchers = 20
|
||||
func NewIp2RegionWithPath(v4XdbPath string, v6XdbPath string) (*Ip2Region, error) {
|
||||
var err error
|
||||
|
||||
// create v4 config with default config items
|
||||
var v4Config *Config
|
||||
if v4XdbPath == "" {
|
||||
v4Config = nil
|
||||
} else {
|
||||
v4Config, err = NewV4Config(VIndexCache, v4XdbPath, 20)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create v4 config: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// create v6 config with default config items
|
||||
var v6Config *Config
|
||||
if v6XdbPath == "" {
|
||||
v6Config = nil
|
||||
} else {
|
||||
v6Config, err = NewV6Config(VIndexCache, v6XdbPath, 20)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create v6 config: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return NewIp2Region(v4Config, v6Config)
|
||||
}
|
||||
|
||||
func (ip2r *Ip2Region) SearchByStr(ipStr string) (string, error) {
|
||||
ipBytes, err := xdb.ParseIP(ipStr)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return ip2r.Search(ipBytes)
|
||||
}
|
||||
|
||||
func (ip2r *Ip2Region) Search(ipBytes []byte) (string, error) {
|
||||
if l := len(ipBytes); l == 4 {
|
||||
return ip2r.v4Search(ipBytes)
|
||||
} else if l == 16 {
|
||||
return ip2r.v6Search(ipBytes)
|
||||
} else {
|
||||
return "", fmt.Errorf("invalid byte ip address with len=%d", l)
|
||||
}
|
||||
}
|
||||
|
||||
func (ip2r *Ip2Region) v4Search(ipBytes []byte) (string, error) {
|
||||
if ip2r.v4InMemSearcher != nil {
|
||||
return ip2r.v4InMemSearcher.Search(ipBytes)
|
||||
}
|
||||
|
||||
// v4 search is disabled
|
||||
if ip2r.v4Pool == nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
v4Searcher := ip2r.v4Pool.BorrowSearcher()
|
||||
defer ip2r.v4Pool.ReturnSearcher(v4Searcher)
|
||||
return v4Searcher.Search(ipBytes)
|
||||
}
|
||||
|
||||
func (ip2r *Ip2Region) v6Search(ipBytes []byte) (string, error) {
|
||||
if ip2r.v6InMemSearcher != nil {
|
||||
return ip2r.v6InMemSearcher.Search(ipBytes)
|
||||
}
|
||||
|
||||
// v6 search is disabled
|
||||
if ip2r.v6Pool == nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
v6Searcher := ip2r.v6Pool.BorrowSearcher()
|
||||
defer ip2r.v6Pool.ReturnSearcher(v6Searcher)
|
||||
return v6Searcher.Search(ipBytes)
|
||||
}
|
||||
|
||||
func (ip2r *Ip2Region) Close() {
|
||||
ip2r.CloseTimeout(time.Second * 10)
|
||||
}
|
||||
|
||||
func (ip2r *Ip2Region) CloseTimeout(d time.Duration) {
|
||||
if ip2r.v4Pool != nil {
|
||||
ip2r.v4Pool.CloseTimeout(d)
|
||||
}
|
||||
|
||||
if ip2r.v6Pool != nil {
|
||||
ip2r.v6Pool.CloseTimeout(d)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,273 @@
|
|||
// Copyright 2022 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/lionsoul2014/ip2region/binding/golang/xdb"
|
||||
)
|
||||
|
||||
func TestConfigCreate(t *testing.T) {
|
||||
v4Config, err := NewV4Config(VIndexCache, "../../../data/ip2region_v4.xdb", 10)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create v4 config: %s", err)
|
||||
}
|
||||
|
||||
v6Config, err := NewV6Config(VIndexCache, "../../../data/ip2region_v6.xdb", 10)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create v6 config: %s", err)
|
||||
}
|
||||
|
||||
ip2region, err := NewIp2Region(v4Config, v6Config)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create ip2region service: %s", err)
|
||||
}
|
||||
|
||||
v4Bytes, err := xdb.ParseIP("219.133.110.197")
|
||||
if err != nil {
|
||||
t.Fatal("invalid ipv4 address")
|
||||
}
|
||||
|
||||
v6Bytes, err := xdb.ParseIP("240e:3b7:3275:f090:d2a3:7d1a:dd90:c3b6")
|
||||
if err != nil {
|
||||
t.Fatalf("invalid ipv6 address")
|
||||
}
|
||||
|
||||
for i := 0; i < 20; i++ {
|
||||
v4Bytes = xdb.IPAddOne(v4Bytes)
|
||||
v6Bytes = xdb.IPAddOne(v6Bytes)
|
||||
v4Region, err := ip2region.Search(v4Bytes)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to search(%s): %s", xdb.IP2String(v4Bytes), err)
|
||||
}
|
||||
|
||||
v6Region, err := ip2region.Search(v6Bytes)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to search(%s): %s", xdb.IP2String(v6Bytes), err)
|
||||
}
|
||||
|
||||
fmt.Printf(
|
||||
"%2d->search(%s)=%s, search(%s)=%s\n",
|
||||
i, xdb.IP2String(v4Bytes), v4Region, xdb.IP2String(v6Bytes), v6Region,
|
||||
)
|
||||
}
|
||||
|
||||
ip2region.Close()
|
||||
fmt.Print("ip2region closed gracefully")
|
||||
}
|
||||
|
||||
func TestPathCreate(t *testing.T) {
|
||||
ip2region, err := NewIp2RegionWithPath("../../../data/ip2region_v4.xdb", "../../../data/ip2region_v6.xdb")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create ip2region with path: %s", err)
|
||||
}
|
||||
|
||||
v4Bytes, err := xdb.ParseIP("219.133.110.197")
|
||||
if err != nil {
|
||||
t.Fatal("invalid ipv4 address")
|
||||
}
|
||||
|
||||
v6Bytes, err := xdb.ParseIP("240e:3b7:3275:f090:d2a3:7d1a:dd90:c3b6")
|
||||
if err != nil {
|
||||
t.Fatalf("invalid ipv6 address")
|
||||
}
|
||||
|
||||
for i := 0; i < 20; i++ {
|
||||
v4Bytes = xdb.IPAddOne(v4Bytes)
|
||||
v6Bytes = xdb.IPAddOne(v6Bytes)
|
||||
v4Region, err := ip2region.Search(v4Bytes)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to search(%s): %s", xdb.IP2String(v4Bytes), err)
|
||||
}
|
||||
|
||||
v6Region, err := ip2region.Search(v6Bytes)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to search(%s): %s", xdb.IP2String(v6Bytes), err)
|
||||
}
|
||||
|
||||
fmt.Printf(
|
||||
"%2d->search(%s)=%s, search(%s)=%s\n",
|
||||
i, xdb.IP2String(v4Bytes), v4Region, xdb.IP2String(v6Bytes), v6Region,
|
||||
)
|
||||
}
|
||||
|
||||
ip2region.Close()
|
||||
fmt.Print("ip2region closed gracefully")
|
||||
}
|
||||
|
||||
func TestInMemSearch(t *testing.T) {
|
||||
v4Config, err := NewV4Config(BufferCache, "../../../data/ip2region_v4.xdb", 10)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create v4 config: %s", err)
|
||||
}
|
||||
|
||||
v6Config, err := NewV6Config(BufferCache, "../../../data/ip2region_v6.xdb", 10)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create v6 config: %s", err)
|
||||
}
|
||||
|
||||
ip2region, err := NewIp2Region(v4Config, v6Config)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create ip2region service: %s", err)
|
||||
}
|
||||
|
||||
v4Bytes, err := xdb.ParseIP("219.133.110.197")
|
||||
if err != nil {
|
||||
t.Fatal("invalid ipv4 address")
|
||||
}
|
||||
|
||||
v6Bytes, err := xdb.ParseIP("240e:3b7:3275:f090:d2a3:7d1a:dd90:c3b6")
|
||||
if err != nil {
|
||||
t.Fatalf("invalid ipv6 address")
|
||||
}
|
||||
|
||||
for i := 0; i < 20; i++ {
|
||||
v4Bytes = xdb.IPAddOne(v4Bytes)
|
||||
v6Bytes = xdb.IPAddOne(v6Bytes)
|
||||
v4Region, err := ip2region.Search(v4Bytes)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to search(%s): %s", xdb.IP2String(v4Bytes), err)
|
||||
}
|
||||
|
||||
v6Region, err := ip2region.Search(v6Bytes)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to search(%s): %s", xdb.IP2String(v6Bytes), err)
|
||||
}
|
||||
|
||||
fmt.Printf(
|
||||
"%2d->search(%s)=%s, search(%s)=%s\n",
|
||||
i, xdb.IP2String(v4Bytes), v4Region, xdb.IP2String(v6Bytes), v6Region,
|
||||
)
|
||||
}
|
||||
|
||||
ip2region.Close()
|
||||
fmt.Print("ip2region closed gracefully")
|
||||
}
|
||||
|
||||
func TestV4Only(t *testing.T) {
|
||||
v4Config, err := NewV4Config(NoCache, "../../../data/ip2region_v4.xdb", 10)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create v4 config: %s", err)
|
||||
}
|
||||
|
||||
ip2region, err := NewIp2Region(v4Config, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create ip2region service: %s", err)
|
||||
}
|
||||
|
||||
v4Bytes, err := xdb.ParseIP("219.133.110.197")
|
||||
if err != nil {
|
||||
t.Fatal("invalid ipv4 address")
|
||||
}
|
||||
|
||||
v6Bytes, err := xdb.ParseIP("240e:3b7:3275:f090:d2a3:7d1a:dd90:c3b6")
|
||||
if err != nil {
|
||||
t.Fatalf("invalid ipv6 address")
|
||||
}
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
v4Bytes = xdb.IPAddOne(v4Bytes)
|
||||
v6Bytes = xdb.IPAddOne(v6Bytes)
|
||||
v4Region, err := ip2region.Search(v4Bytes)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to search(%s): %s", xdb.IP2String(v4Bytes), err)
|
||||
}
|
||||
|
||||
v6Region, err := ip2region.Search(v6Bytes)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to search(%s): %s", xdb.IP2String(v6Bytes), err)
|
||||
}
|
||||
|
||||
fmt.Printf(
|
||||
"%2d->search(%s)=%s, search(%s)=%s\n",
|
||||
i, xdb.IP2String(v4Bytes), v4Region, xdb.IP2String(v6Bytes), v6Region,
|
||||
)
|
||||
}
|
||||
|
||||
ip2region.Close()
|
||||
fmt.Print("ip2region closed gracefully")
|
||||
}
|
||||
|
||||
func TestConcurrentCall(t *testing.T) {
|
||||
v4Config, err := NewV4Config(VIndexCache, "../../../data/ip2region_v4.xdb", 15)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create v4 config: %s", err)
|
||||
}
|
||||
|
||||
v6Config, err := NewV6Config(VIndexCache, "../../../data/ip2region_v6.xdb", 15)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create v6 config: %s", err)
|
||||
}
|
||||
|
||||
v4Bytes, err := xdb.ParseIP("219.133.110.197")
|
||||
if err != nil {
|
||||
t.Fatal("invalid ipv4 address")
|
||||
}
|
||||
|
||||
v6Bytes, err := xdb.ParseIP("240e:3b7:3275:f090:d2a3:7d1a:dd90:c3b6")
|
||||
if err != nil {
|
||||
t.Fatalf("invalid ipv6 address")
|
||||
}
|
||||
|
||||
fmt.Printf("v4Config: %s\n", v4Config)
|
||||
fmt.Printf("v6Config: %s\n", v6Config)
|
||||
ip2region, err := NewIp2Region(v4Config, v6Config)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create ip2region service: %s", err)
|
||||
}
|
||||
|
||||
coroutines := 100
|
||||
var wg sync.WaitGroup
|
||||
var count int64 = 0
|
||||
tStart := time.Now()
|
||||
for i := 0; i < coroutines; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
var ipBytes []byte
|
||||
for i := 0; i < 5000; i++ {
|
||||
if i%2 == 0 {
|
||||
ipBytes = v4Bytes
|
||||
} else {
|
||||
ipBytes = v6Bytes
|
||||
}
|
||||
|
||||
region, err := ip2region.Search(ipBytes)
|
||||
if err != nil {
|
||||
fmt.Printf("Error: failed to search(%s): %s", xdb.IP2String(ipBytes), err)
|
||||
break
|
||||
}
|
||||
|
||||
if l := len(ipBytes); l == 4 {
|
||||
if region != "中国|广东省|深圳市|电信" {
|
||||
fmt.Print("Error: region not equals")
|
||||
break
|
||||
}
|
||||
} else {
|
||||
if region != "中国|广东省|深圳市|家庭宽带" {
|
||||
fmt.Print("Error: region not equals")
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
atomic.AddInt64(&count, 1)
|
||||
}
|
||||
|
||||
// mark all searches finished for this coroutine
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
|
||||
// wait for all the searches to finished
|
||||
wg.Wait()
|
||||
costs := time.Since(tStart)
|
||||
fmt.Printf("%d searches finished in %s, avg took: %s\n", count, costs, costs/time.Duration(count))
|
||||
ip2region.Close()
|
||||
fmt.Print("ip2region closed gracefully")
|
||||
}
|
||||
|
|
@ -0,0 +1,113 @@
|
|||
// Copyright 2022 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/lionsoul2014/ip2region/binding/golang/xdb"
|
||||
)
|
||||
|
||||
// ---
|
||||
// ip2region searcher pool
|
||||
//
|
||||
// @Author Lion <chenxin619315@gmail.com>
|
||||
// @Date 2025/12/03
|
||||
|
||||
type SearcherPool struct {
|
||||
// config
|
||||
config *Config
|
||||
|
||||
// searcher pool
|
||||
pool chan *xdb.Searcher
|
||||
|
||||
// for pool close
|
||||
closing chan struct{}
|
||||
|
||||
// searcher number that was loaned out
|
||||
loanCount int32
|
||||
}
|
||||
|
||||
func NewSearcherPool(config *Config) (*SearcherPool, error) {
|
||||
if config.searchers < 1 {
|
||||
return nil, fmt.Errorf("config.searchers must > 0")
|
||||
}
|
||||
|
||||
pool := make(chan *xdb.Searcher, config.searchers+1)
|
||||
// check and create all the searchers
|
||||
for i := 0; i < config.searchers; i++ {
|
||||
searcher, err := xdb.NewSearcher(config.ipVersion, config.xdbPath, config.vIndex, config.cBuffer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create the %dth searcher: %w", i+1, err)
|
||||
}
|
||||
|
||||
// push the search to the pool
|
||||
pool <- searcher
|
||||
}
|
||||
|
||||
return &SearcherPool{
|
||||
config: config,
|
||||
pool: pool,
|
||||
|
||||
closing: make(chan struct{}, 1),
|
||||
loanCount: 0,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// get the loaned count
|
||||
func (sp *SearcherPool) LoanCount() int {
|
||||
return int(atomic.LoadInt32(&sp.loanCount))
|
||||
}
|
||||
|
||||
func (sp *SearcherPool) BorrowSearcher() *xdb.Searcher {
|
||||
// @Note: still accept searcher borrow while closing
|
||||
s := <-sp.pool
|
||||
atomic.AddInt32(&sp.loanCount, 1)
|
||||
return s
|
||||
}
|
||||
|
||||
func (sp *SearcherPool) ReturnSearcher(searcher *xdb.Searcher) {
|
||||
select {
|
||||
case <-sp.closing:
|
||||
// manually close the searcher
|
||||
searcher.Close()
|
||||
|
||||
// decrease the loan count
|
||||
atomic.AddInt32(&sp.loanCount, -1)
|
||||
default:
|
||||
// return the searcher
|
||||
sp.pool <- searcher
|
||||
atomic.AddInt32(&sp.loanCount, -1)
|
||||
}
|
||||
}
|
||||
|
||||
func (sp *SearcherPool) Close() {
|
||||
sp.CloseTimeout(time.Second * 10)
|
||||
}
|
||||
|
||||
func (sp *SearcherPool) CloseTimeout(d time.Duration) {
|
||||
close(sp.closing)
|
||||
for {
|
||||
timeout := false
|
||||
select {
|
||||
case s := <-sp.pool:
|
||||
s.Close()
|
||||
case <-time.After(d):
|
||||
// check if all the loaned searchers was closed
|
||||
timeout = true
|
||||
}
|
||||
|
||||
lc, left := sp.LoanCount(), len(sp.pool)
|
||||
if left == 0 && lc == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
if timeout {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
// Copyright 2022 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestV4SearcherPool(t *testing.T) {
|
||||
v4Config, err := NewV4Config(VIndexCache, "../../../data/ip2region_v4.xdb", 5)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to new v4 config: %s", err)
|
||||
}
|
||||
|
||||
searcherPool, err := NewSearcherPool(v4Config)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create searcher pool: %s", err)
|
||||
}
|
||||
|
||||
ipString := "219.133.110.197"
|
||||
for i := 0; i < 20; i++ {
|
||||
searcher := searcherPool.BorrowSearcher()
|
||||
region, err := searcher.SearchByStr(ipString)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to search(%s): %s", ipString, err)
|
||||
}
|
||||
|
||||
fmt.Printf("%2d->search(%s)=%s\n", i, ipString, region)
|
||||
searcherPool.ReturnSearcher(searcher)
|
||||
}
|
||||
|
||||
// borrow one at last for Close timeout wait testing ONLY
|
||||
// searcherPool.BorrowSearcher()
|
||||
|
||||
// close the searcher pool
|
||||
searcherPool.Close()
|
||||
}
|
||||
|
||||
func TestV6SearcherPool(t *testing.T) {
|
||||
v6Config, err := NewV6Config(VIndexCache, "../../../data/ip2region_v6.xdb", 5)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to new v6 config: %s", err)
|
||||
}
|
||||
|
||||
searcherPool, err := NewSearcherPool(v6Config)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create searcher pool: %s", err)
|
||||
}
|
||||
|
||||
ipString := "240e:3b7:3275:f090:d2a3:7d1a:dd90:c3b6"
|
||||
for i := 0; i < 20; i++ {
|
||||
searcher := searcherPool.BorrowSearcher()
|
||||
region, err := searcher.SearchByStr(ipString)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to search(%s): %s", ipString, err)
|
||||
}
|
||||
|
||||
fmt.Printf("%2d->search(%s)=%s\n", i, ipString, region)
|
||||
searcherPool.ReturnSearcher(searcher)
|
||||
}
|
||||
|
||||
// borrow one at last for Close timeout wait testing ONLY
|
||||
// searcherPool.BorrowSearcher()
|
||||
|
||||
// close the searcher pool
|
||||
searcherPool.Close()
|
||||
}
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
// Copyright 2022 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// ---
|
||||
// Ip2Region database v2.0 searcher.
|
||||
// @Note this is a Not thread safe implementation.
|
||||
//
|
||||
// @Author Lion <chenxin619315@gmail.com>
|
||||
// @Date 2025/12/03
|
||||
|
||||
package xdb
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const (
|
||||
Structure20 = 2
|
||||
Structure30 = 3
|
||||
HeaderInfoLength = 256
|
||||
VectorIndexRows = 256
|
||||
VectorIndexCols = 256
|
||||
VectorIndexSize = 8
|
||||
)
|
||||
|
||||
// --- Index policy define
|
||||
|
||||
type IndexPolicy int
|
||||
|
||||
const (
|
||||
VectorIndexPolicy IndexPolicy = 1
|
||||
BTreeIndexPolicy IndexPolicy = 2
|
||||
)
|
||||
|
||||
func (i IndexPolicy) String() string {
|
||||
switch i {
|
||||
case VectorIndexPolicy:
|
||||
return "VectorIndex"
|
||||
case BTreeIndexPolicy:
|
||||
return "BtreeIndex"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
// --- Header define
|
||||
|
||||
type Header struct {
|
||||
// data []byte
|
||||
Version uint16
|
||||
IndexPolicy IndexPolicy
|
||||
CreatedAt uint32
|
||||
StartIndexPtr uint32
|
||||
EndIndexPtr uint32
|
||||
|
||||
// since IPv6 supporting
|
||||
IPVersion int
|
||||
RuntimePtrBytes int
|
||||
}
|
||||
|
||||
func NewHeader(input []byte) (*Header, error) {
|
||||
if len(input) < 16 {
|
||||
return nil, fmt.Errorf("invalid input buffer")
|
||||
}
|
||||
|
||||
return &Header{
|
||||
Version: binary.LittleEndian.Uint16(input[0:]),
|
||||
IndexPolicy: IndexPolicy(binary.LittleEndian.Uint16(input[2:])),
|
||||
CreatedAt: binary.LittleEndian.Uint32(input[4:]),
|
||||
StartIndexPtr: binary.LittleEndian.Uint32(input[8:]),
|
||||
EndIndexPtr: binary.LittleEndian.Uint32(input[12:]),
|
||||
|
||||
IPVersion: int(binary.LittleEndian.Uint16(input[16:])),
|
||||
RuntimePtrBytes: int(binary.LittleEndian.Uint16(input[18:])),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (h *Header) String() string {
|
||||
return fmt.Sprintf(
|
||||
"{version:%d, index_policy:%d, created_at:%d, start_index_ptr:%d, end_index_ptr:%d, ip_version:%d, runtime_ptr_bytes:%d}",
|
||||
h.Version, h.IndexPolicy, h.CreatedAt, h.StartIndexPtr, h.EndIndexPtr, h.IPVersion, h.RuntimePtrBytes,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,204 @@
|
|||
// Copyright 2022 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// ---
|
||||
// Ip2Region database v2.0 searcher.
|
||||
// @Note this is a Not thread safe implementation.
|
||||
//
|
||||
// @Author Lion <chenxin619315@gmail.com>
|
||||
// @Date 2022/06/16
|
||||
|
||||
package xdb
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
type Searcher struct {
|
||||
version *Version
|
||||
handle *os.File
|
||||
|
||||
ioCount int
|
||||
|
||||
// use it only when this feature enabled.
|
||||
// Preload the vector index will reduce the number of IO operations
|
||||
// thus speedup the search process
|
||||
vectorIndex []byte
|
||||
|
||||
// content buffer.
|
||||
// running with the whole xdb file cached
|
||||
contentBuff []byte
|
||||
}
|
||||
|
||||
func NewWithFileOnly(version *Version, dbFile string) (*Searcher, error) {
|
||||
return NewSearcher(version, dbFile, nil, nil)
|
||||
}
|
||||
|
||||
func NewWithVectorIndex(version *Version, dbFile string, vIndex []byte) (*Searcher, error) {
|
||||
return NewSearcher(version, dbFile, vIndex, nil)
|
||||
}
|
||||
|
||||
func NewWithBuffer(version *Version, cBuff []byte) (*Searcher, error) {
|
||||
return NewSearcher(version, "", nil, cBuff)
|
||||
}
|
||||
|
||||
func NewSearcher(version *Version, dbFile string, vIndex []byte, cBuff []byte) (*Searcher, error) {
|
||||
var err error
|
||||
|
||||
// content buff first
|
||||
if cBuff != nil {
|
||||
return &Searcher{
|
||||
version: version,
|
||||
vectorIndex: nil,
|
||||
contentBuff: cBuff,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// open the xdb binary file
|
||||
handle, err := os.OpenFile(dbFile, os.O_RDONLY, 0600)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Searcher{
|
||||
version: version,
|
||||
handle: handle,
|
||||
vectorIndex: vIndex,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Searcher) Close() {
|
||||
if s.handle != nil {
|
||||
err := s.handle.Close()
|
||||
if err != nil {
|
||||
// do error log here ?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// IPVersion return the ip version
|
||||
func (s *Searcher) IPVersion() *Version {
|
||||
return s.version
|
||||
}
|
||||
|
||||
// GetIOCount return the global io count for the last search
|
||||
func (s *Searcher) GetIOCount() int {
|
||||
return s.ioCount
|
||||
}
|
||||
|
||||
// SearchByStr find the region for the specified ip string
|
||||
func (s *Searcher) SearchByStr(str string) (string, error) {
|
||||
ip, err := ParseIP(str)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return s.Search(ip)
|
||||
}
|
||||
|
||||
// Search find the region for the specified long ip
|
||||
func (s *Searcher) Search(ip []byte) (string, error) {
|
||||
// ip version check
|
||||
if len(ip) != s.version.Bytes {
|
||||
return "", fmt.Errorf("invalid ip address(%s expected)", s.version.Name)
|
||||
}
|
||||
|
||||
// reset the global ioCount
|
||||
s.ioCount = 0
|
||||
|
||||
// locate the segment index block based on the vector index
|
||||
var il0, il1 = int(ip[0]), int(ip[1])
|
||||
var idx = il0*VectorIndexCols*VectorIndexSize + il1*VectorIndexSize
|
||||
var sPtr, ePtr = uint32(0), uint32(0)
|
||||
if s.vectorIndex != nil {
|
||||
sPtr = binary.LittleEndian.Uint32(s.vectorIndex[idx:])
|
||||
ePtr = binary.LittleEndian.Uint32(s.vectorIndex[idx+4:])
|
||||
} else if s.contentBuff != nil {
|
||||
sPtr = binary.LittleEndian.Uint32(s.contentBuff[HeaderInfoLength+idx:])
|
||||
ePtr = binary.LittleEndian.Uint32(s.contentBuff[HeaderInfoLength+idx+4:])
|
||||
} else {
|
||||
// read the vector index block
|
||||
var buff = make([]byte, VectorIndexSize)
|
||||
err := s.read(int64(HeaderInfoLength+idx), buff)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("read vector index block at %d: %w", HeaderInfoLength+idx, err)
|
||||
}
|
||||
|
||||
sPtr = binary.LittleEndian.Uint32(buff)
|
||||
ePtr = binary.LittleEndian.Uint32(buff[4:])
|
||||
}
|
||||
|
||||
// fmt.Printf("sPtr=%d, ePtr=%d\n", sPtr, ePtr)
|
||||
|
||||
// binary search the segment index to get the region
|
||||
var bytes, dBytes = len(ip), len(ip) << 1
|
||||
var segIndexSize = uint32(s.version.SegmentIndexSize)
|
||||
var dataLen, dataPtr = 0, uint32(0)
|
||||
var buff = make([]byte, segIndexSize)
|
||||
var l, h = 0, int((ePtr - sPtr) / segIndexSize)
|
||||
for l <= h {
|
||||
m := (l + h) >> 1
|
||||
p := sPtr + uint32(m)*segIndexSize
|
||||
err := s.read(int64(p), buff)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("read segment index at %d: %w", p, err)
|
||||
}
|
||||
|
||||
// decode the data step by step to reduce the unnecessary operations
|
||||
if s.version.IPCompare(ip, buff[0:bytes]) < 0 {
|
||||
h = m - 1
|
||||
} else if s.version.IPCompare(ip, buff[bytes:dBytes]) > 0 {
|
||||
l = m + 1
|
||||
} else {
|
||||
dataLen = int(binary.LittleEndian.Uint16(buff[dBytes:]))
|
||||
dataPtr = binary.LittleEndian.Uint32(buff[dBytes+2:])
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// fmt.Printf("dataLen: %d, dataPtr: %d\n", dataLen, dataPtr)
|
||||
if dataLen == 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// load and return the region data
|
||||
var regionBuff = make([]byte, dataLen)
|
||||
err := s.read(int64(dataPtr), regionBuff)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("read region at %d: %w", dataPtr, err)
|
||||
}
|
||||
|
||||
return string(regionBuff), nil
|
||||
}
|
||||
|
||||
// do the data read operation based on the setting.
|
||||
// content buffer first or will read from the file.
|
||||
// this operation will invoke the Seek for file based read.
|
||||
func (s *Searcher) read(offset int64, buff []byte) error {
|
||||
if s.contentBuff != nil {
|
||||
cLen := copy(buff, s.contentBuff[offset:])
|
||||
if cLen != len(buff) {
|
||||
return fmt.Errorf("incomplete read: readed bytes should be %d", len(buff))
|
||||
}
|
||||
} else {
|
||||
_, err := s.handle.Seek(offset, 0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("seek to %d: %w", offset, err)
|
||||
}
|
||||
|
||||
s.ioCount++
|
||||
rLen, err := s.handle.Read(buff)
|
||||
if err != nil {
|
||||
return fmt.Errorf("handle read: %w", err)
|
||||
}
|
||||
|
||||
if rLen != len(buff) {
|
||||
return fmt.Errorf("incomplete read: readed bytes should be %d", len(buff))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -0,0 +1,272 @@
|
|||
// Copyright 2022 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// ---
|
||||
// @Author Lion <chenxin619315@gmail.com>
|
||||
// @Date 2022/06/16
|
||||
|
||||
package xdb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"embed"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
)
|
||||
|
||||
func ParseIP(ip string) ([]byte, error) {
|
||||
parsedIP := net.ParseIP(ip)
|
||||
if parsedIP == nil {
|
||||
return nil, fmt.Errorf("invalid ip address: %s", ip)
|
||||
}
|
||||
|
||||
v4 := parsedIP.To4()
|
||||
if v4 != nil {
|
||||
return v4, nil
|
||||
}
|
||||
|
||||
v6 := parsedIP.To16()
|
||||
if v6 != nil {
|
||||
return v6, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("invalid ip address: %s", ip)
|
||||
}
|
||||
|
||||
func IP2String(ip []byte) string {
|
||||
return net.IP(ip[:]).String()
|
||||
}
|
||||
|
||||
// IPCompare compares two IP addresses
|
||||
// Returns: -1 if ip1 < ip2, 0 if ip1 == ip2, 1 if ip1 > ip2
|
||||
func IPCompare(ip1, ip2 []byte) int {
|
||||
// for i := 0; i < len(ip1); i++ {
|
||||
// if ip1[i] < ip2[i] {
|
||||
// return -1
|
||||
// }
|
||||
// if ip1[i] > ip2[i] {
|
||||
// return 1
|
||||
// }
|
||||
// }
|
||||
// return 0
|
||||
return bytes.Compare(ip1, ip2)
|
||||
}
|
||||
|
||||
func IPAddOne(ip []byte) []byte {
|
||||
var r = make([]byte, len(ip))
|
||||
copy(r, ip)
|
||||
for i := len(ip) - 1; i >= 0; i-- {
|
||||
r[i]++
|
||||
if r[i] != 0 { // No overflow
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func IPSubOne(ip []byte) []byte {
|
||||
var r = make([]byte, len(ip))
|
||||
copy(r, ip)
|
||||
for i := len(ip) - 1; i >= 0; i-- {
|
||||
if r[i] != 0 { // No borrow needed
|
||||
r[i]--
|
||||
break
|
||||
}
|
||||
r[i] = 0xFF // borrow from the next byte
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// Verify if the current Searcher could be used to search the specified xdb file.
|
||||
// Why do we need this check ?
|
||||
// The future features of the xdb impl may cause the current searcher not able to work properly.
|
||||
//
|
||||
// @Note: You Just need to check this ONCE when the service starts
|
||||
// Or use another process (eg, A command) to check once Just to confirm the suitability.
|
||||
func Verify(handle *os.File) error {
|
||||
header, err := LoadHeader(handle)
|
||||
if err != nil {
|
||||
return fmt.Errorf("loading header: %w", err)
|
||||
}
|
||||
|
||||
// get the runtime ptr bytes
|
||||
runtimePtrBytes := 0
|
||||
switch header.Version {
|
||||
case Structure20:
|
||||
runtimePtrBytes = 4
|
||||
case Structure30:
|
||||
runtimePtrBytes = header.RuntimePtrBytes
|
||||
default:
|
||||
return fmt.Errorf("invalid version: %d", header.Version)
|
||||
}
|
||||
|
||||
// 1, confirm the xdb file size.
|
||||
// to sure that the MaxFilePointer does no overflow
|
||||
stat, err := handle.Stat()
|
||||
if err != nil {
|
||||
return fmt.Errorf("file stat: %w", err)
|
||||
}
|
||||
|
||||
maxFilePtr := int64(1<<(runtimePtrBytes*8) - 1)
|
||||
if stat.Size() > maxFilePtr {
|
||||
return fmt.Errorf("xdb file exceeds the maximum supported bytes: %d", maxFilePtr)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// VerifyFromFile check Verify for details
|
||||
func VerifyFromFile(dbFile string) error {
|
||||
handle, err := os.OpenFile(dbFile, os.O_RDONLY, 0600)
|
||||
if err != nil {
|
||||
return fmt.Errorf("open xdb file `%s`: %w", dbFile, err)
|
||||
}
|
||||
defer handle.Close()
|
||||
|
||||
return Verify(handle)
|
||||
}
|
||||
|
||||
// LoadHeader load the header info from the specified handle
|
||||
func LoadHeader(handle *os.File) (*Header, error) {
|
||||
_, err := handle.Seek(0, 0)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("seek to the header: %w", err)
|
||||
}
|
||||
|
||||
var buff = make([]byte, HeaderInfoLength)
|
||||
rLen, err := handle.Read(buff)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if rLen != len(buff) {
|
||||
return nil, fmt.Errorf("incomplete read: readed bytes should be %d", len(buff))
|
||||
}
|
||||
|
||||
return NewHeader(buff)
|
||||
}
|
||||
|
||||
// LoadHeaderFromFile load header info from the specified db file path
|
||||
func LoadHeaderFromFile(dbFile string) (*Header, error) {
|
||||
handle, err := os.OpenFile(dbFile, os.O_RDONLY, 0600)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("open xdb file `%s`: %w", dbFile, err)
|
||||
}
|
||||
defer handle.Close()
|
||||
|
||||
header, err := LoadHeader(handle)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return header, nil
|
||||
}
|
||||
|
||||
// LoadHeaderFromBuff wrap the header info from the content buffer
|
||||
func LoadHeaderFromBuff(cBuff []byte) (*Header, error) {
|
||||
return NewHeader(cBuff[0:HeaderInfoLength])
|
||||
}
|
||||
|
||||
// LoadVectorIndex util function to load the vector index from the specified file handle
|
||||
func LoadVectorIndex(handle *os.File) ([]byte, error) {
|
||||
// load all the vector index block
|
||||
_, err := handle.Seek(HeaderInfoLength, 0)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("seek to vector index: %w", err)
|
||||
}
|
||||
|
||||
var buff = make([]byte, VectorIndexRows*VectorIndexCols*VectorIndexSize)
|
||||
rLen, err := handle.Read(buff)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if rLen != len(buff) {
|
||||
return nil, fmt.Errorf("incomplete read: readed bytes should be %d", len(buff))
|
||||
}
|
||||
|
||||
return buff, nil
|
||||
}
|
||||
|
||||
// LoadVectorIndexFromFile load vector index from a specified file path
|
||||
func LoadVectorIndexFromFile(dbFile string) ([]byte, error) {
|
||||
handle, err := os.OpenFile(dbFile, os.O_RDONLY, 0600)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("open xdb file `%s`: %w", dbFile, err)
|
||||
}
|
||||
defer handle.Close()
|
||||
|
||||
vIndex, err := LoadVectorIndex(handle)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return vIndex, nil
|
||||
}
|
||||
|
||||
// LoadContent load the whole xdb content from the specified file handle
|
||||
func LoadContent(handle *os.File) ([]byte, error) {
|
||||
// get file size
|
||||
fi, err := handle.Stat()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("stat: %w", err)
|
||||
}
|
||||
|
||||
size := fi.Size()
|
||||
|
||||
// seek to the head of the file
|
||||
_, err = handle.Seek(0, 0)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("seek to get xdb file length: %w", err)
|
||||
}
|
||||
|
||||
var buff = make([]byte, size)
|
||||
rLen, err := handle.Read(buff)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if rLen != len(buff) {
|
||||
return nil, fmt.Errorf("incomplete read: readed bytes should be %d", len(buff))
|
||||
}
|
||||
|
||||
return buff, nil
|
||||
}
|
||||
|
||||
// LoadContentFromFile load the whole xdb content from the specified db file path
|
||||
func LoadContentFromFile(dbFile string) ([]byte, error) {
|
||||
handle, err := os.OpenFile(dbFile, os.O_RDONLY, 0600)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("open xdb file `%s`: %w", dbFile, err)
|
||||
}
|
||||
defer handle.Close()
|
||||
|
||||
cBuff, err := LoadContent(handle)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cBuff, nil
|
||||
}
|
||||
|
||||
// LoadContentFromFS load the whole xdb binary from embed.FS
|
||||
func LoadContentFromFS(fs embed.FS, filePath string) ([]byte, error) {
|
||||
file, err := fs.Open(filePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open embedded file `%s`: %w", filePath, err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
var cBuff []byte
|
||||
cBuff, err = io.ReadAll(file)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read embedded file `%s`: %w", filePath, err)
|
||||
}
|
||||
|
||||
return cBuff, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
// Copyright 2022 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// ---
|
||||
// @Author Lion <chenxin619315@gmail.com>
|
||||
// @Date 2022/06/16
|
||||
|
||||
package xdb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestParseIP(t *testing.T) {
|
||||
var ips = []string{"29.34.191.255", "2c0f:fff0::", "2fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"}
|
||||
for _, ip := range ips {
|
||||
bytes, err := ParseIP(ip)
|
||||
if err != nil {
|
||||
t.Errorf("check ip `%s`: %s\n", IP2String(bytes), err)
|
||||
}
|
||||
|
||||
nip := IP2String(bytes)
|
||||
fmt.Printf("checkip: (%s / %s), isEqual: %v\n", ip, nip, ip == nip)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIPCompare(t *testing.T) {
|
||||
var ipPairs = [][]string{
|
||||
{"1.2.3.4", "1.2.3.5"},
|
||||
{"58.250.36.41", "58.250.30.41"},
|
||||
{"2c10::", "2e00::"},
|
||||
{"fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", "febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff"},
|
||||
{"fe7f:ffff:ffff:ffff:ffff:ffff:ffff:ffff", "fe00::"},
|
||||
}
|
||||
|
||||
for _, pairs := range ipPairs {
|
||||
fmt.Printf("IPCompare(%s, %s): %d\n", pairs[0], pairs[1], IPCompare([]byte(pairs[0]), []byte(pairs[1])))
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadVectorIndex(t *testing.T) {
|
||||
vIndex, err := LoadVectorIndexFromFile("../../../data/ip2region_v4.xdb")
|
||||
if err != nil {
|
||||
fmt.Printf("failed to load vector index: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("vIndex length: %d\n", len(vIndex))
|
||||
}
|
||||
|
||||
func TestLoadContent(t *testing.T) {
|
||||
buff, err := LoadContentFromFile("../../../data/ip2region_v4.xdb")
|
||||
if err != nil {
|
||||
fmt.Printf("failed to load xdb content: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("buff length: %d\n", len(buff))
|
||||
}
|
||||
|
||||
func TestLoadHeader(t *testing.T) {
|
||||
header, err := LoadHeaderFromFile("../../../data/ip2region_v4.xdb")
|
||||
if err != nil {
|
||||
fmt.Printf("failed to load xdb header info: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("Version : %d\n", header.Version)
|
||||
fmt.Printf("IndexPolicy : %s\n", header.IndexPolicy.String())
|
||||
fmt.Printf("CreatedAt : %d(%s)\n", header.CreatedAt, time.Unix(int64(header.CreatedAt), 0).Format(time.RFC3339))
|
||||
fmt.Printf("StartIndexPtr : %d\n", header.StartIndexPtr)
|
||||
fmt.Printf("EndIndexPtr : %d\n", header.EndIndexPtr)
|
||||
fmt.Printf("IPVersion : %d\n", header.IPVersion)
|
||||
fmt.Printf("RuntimePtrBytes : %d\n", header.RuntimePtrBytes)
|
||||
}
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
// Copyright 2022 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package xdb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Version struct {
|
||||
Id int
|
||||
Name string
|
||||
Bytes int
|
||||
SegmentIndexSize int
|
||||
|
||||
// function to compare two ips
|
||||
IPCompare func([]byte, []byte) int
|
||||
}
|
||||
|
||||
func (v *Version) String() string {
|
||||
return fmt.Sprintf(
|
||||
"{id:%d, name:%s, bytes:%d, segment_index_size:%d}",
|
||||
v.Id, v.Name, v.Bytes, v.SegmentIndexSize,
|
||||
)
|
||||
}
|
||||
|
||||
const (
|
||||
IPv4VersionNo = 4
|
||||
IPv6VersionNo = 6
|
||||
)
|
||||
|
||||
var (
|
||||
IPvx = &Version{}
|
||||
IPv4 = &Version{
|
||||
Id: IPv4VersionNo,
|
||||
Name: "IPv4",
|
||||
Bytes: 4,
|
||||
SegmentIndexSize: 14, // 4 + 4 + 2 + 4,
|
||||
IPCompare: func(ip1, ip2 []byte) int {
|
||||
// ip1 - with Big endian byte order parsed from an input
|
||||
// ip2 - with Little endian byte order read from the xdb index
|
||||
ip2[0], ip2[3] = ip2[3], ip2[0]
|
||||
ip2[1], ip2[2] = ip2[2], ip2[1]
|
||||
return bytes.Compare(ip1, ip2)
|
||||
},
|
||||
}
|
||||
IPv6 = &Version{
|
||||
Id: IPv6VersionNo,
|
||||
Name: "IPv6",
|
||||
Bytes: 16,
|
||||
SegmentIndexSize: 38, // 16 + 16 + 2 + 4,
|
||||
IPCompare: func(ip1, ip2 []byte) int {
|
||||
return bytes.Compare(ip1, ip2)
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func VersionFromIP(ip string) (*Version, error) {
|
||||
r, err := ParseIP(ip)
|
||||
if err != nil {
|
||||
return IPvx, fmt.Errorf("parse ip fail: %w", err)
|
||||
}
|
||||
|
||||
if len(r) == 4 {
|
||||
return IPv4, nil
|
||||
}
|
||||
|
||||
return IPv6, nil
|
||||
}
|
||||
|
||||
func VersionFromName(name string) (*Version, error) {
|
||||
switch strings.ToUpper(name) {
|
||||
case "V4", "IPV4":
|
||||
return IPv4, nil
|
||||
case "V6", "IPV6":
|
||||
return IPv6, nil
|
||||
default:
|
||||
return IPvx, fmt.Errorf("invalid version name `%s`", name)
|
||||
}
|
||||
}
|
||||
|
||||
func VersionFromHeader(header *Header) (*Version, error) {
|
||||
// old structure with IPv4 supports ONLY
|
||||
if header.Version == Structure20 {
|
||||
return IPv4, nil
|
||||
}
|
||||
|
||||
// structure 3.0 after IPv6 supporting
|
||||
if header.Version != Structure30 {
|
||||
return IPvx, fmt.Errorf("invalid version `%d`", header.IPVersion)
|
||||
}
|
||||
|
||||
switch header.IPVersion {
|
||||
case IPv4VersionNo:
|
||||
return IPv4, nil
|
||||
case IPv6VersionNo:
|
||||
return IPv6, nil
|
||||
default:
|
||||
return IPvx, fmt.Errorf("invalid version `%d`", header.Version)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,331 @@
|
|||
# ip2region xdb java 查询客户端实现
|
||||
|
||||
# 使用方式
|
||||
|
||||
### maven 仓库:
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>org.lionsoul</groupId>
|
||||
<artifactId>ip2region</artifactId>
|
||||
<version>3.3.2</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
### 关于查询服务
|
||||
从 `3.2.0` 版本开始提供了一个双协议兼容且并发安全的 `Ip2Region` 查询服务,**建议优先使用该方式来进行查询调用**,具体使用方式如下:
|
||||
```java
|
||||
import org.lionsoul.ip2region.service.Config;
|
||||
import org.lionsoul.ip2region.service.Ip2Region;
|
||||
|
||||
// 1, 创建 v4 的配置:指定缓存策略和 v4 的 xdb 文件路径
|
||||
final Config v4Config = Config.custom()
|
||||
.setCachePolicy(Config.VIndexCache) // 指定缓存策略: NoCache / VIndexCache / BufferCache
|
||||
.setSearchers(15) // 设置初始化的查询器数量
|
||||
// .setXdbInputStream(InputStream) // 设置 v4 xdb 文件的 inputstream 对象
|
||||
// .setCacheSliceBytes(int) // 设置 v4 xdb 缓存的分片字节数
|
||||
// .setXdbFile(File) // 设置 v4 xdb File 对象
|
||||
.setXdbPath("ip2region v4 xdb path") // 设置 v4 xdb 文件的路径
|
||||
.asV4(); // 指定为 v4 配置
|
||||
|
||||
// 2, 创建 v6 的配置:指定缓存策略和 v6 的 xdb 文件路径
|
||||
final Config v6Config = Config.custom()
|
||||
.setCachePolicy(Config.VIndexCache) // 指定缓存策略: NoCache / VIndexCache / BufferCache
|
||||
.setSearchers(15) // 设置初始化的查询器数量
|
||||
// .setXdbInputStream(InputStream) // 设置 v6 xdb 文件的 inputstream 对象
|
||||
// .setCacheSliceBytes(int) // 设置 v6 xdb 缓存的分片字节数
|
||||
// .setXdbFile(File) // 设置 v6 xdb File 对象
|
||||
.setXdbPath("ip2region v6 xdb path") // 设置 v6 xdb 文件的路径
|
||||
.asV6(); // 指定为 v6 配置
|
||||
|
||||
// 备注:Xdb 三种初始化输入的优先级:XdbInputStream -> XdbFile -> XdbPath
|
||||
// setXdbInputStream 仅方便使用者从 jar 包中加载 xdb 文件内容,这时 cachePolicy 只能设置为 Config.BufferCache
|
||||
|
||||
// 3,通过上述配置创建 Ip2Region 查询服务
|
||||
final Ip2Region ip2Region = Ip2Region.create(v4Config, v6Config);
|
||||
|
||||
// 4,导出 ip2region 服务进行双版本的IP地址的并发查询,例如:
|
||||
final String v4Region = ip2Region.search("113.92.157.29"); // 进行 IPv4 查询
|
||||
final String v6Region = ip2Region.search("240e:3b7:3272:d8d0:db09:c067:8d59:539e"); // 进行 IPv6 查询
|
||||
|
||||
// 5,在服务需要关闭的时候,同时关闭 ip2region 查询服务
|
||||
ip2Region.close();
|
||||
```
|
||||
##### `Ip2Region` 查询备注:
|
||||
1. 该查询服务的 API 并发安全且同时支持 `IPv4` 和 `IPv6` 的地址,内部实现会自动判断。
|
||||
2. v4 和 v6 的配置需要单独创建,可以给 v4 和 v6 设置使用不同的缓存策略,也可以指定其中一个为 `null` 则该版本的 IP 地址查询都会返回 `null`。
|
||||
3. 请结合您项目的并发数给 `setSearchers` 一个合适的查询器数量,默认为 20 个,这个值在运行过程中是固定的,每次查询会从池子里租借一个查询器来完成查询操作,查询完成后再归还回去,如果租借的时候池子已经空了则等待直到有可用的查询器来完成查询服务,租借的锁是使用的 `ReentrantLock` 来管理,也可以通过如下方式来设置 `Ip2Region` 查询服务使用公平锁:
|
||||
```java
|
||||
final Ip2Region ip2region = Ip2Region.create(v4Config, v6Config, true);
|
||||
```
|
||||
4. 如果配置设置的缓存策略为 `Config.BufferCache` 即 `全内存缓存` 则默认会使用单实例的内存查询器,该实现天生并发安全,此时通过 `setSearchers` 指定的查询器数量无效。
|
||||
5. 如果使用的是全内存缓存查询且在加载 xdb 二进制内容的时候提示 `OOM`,请参考 [sliceBytes设置](#slicebytes) 然后通过 `ConfigBuilder.setCacheSliceBytes(int)` 设置一个合适的值来避免 OOM。
|
||||
6. 如果 `ip2region` 查询器在提供服务期间,调用 close 默认会最大等待 10 秒钟来等待尽量多的查询器归还。
|
||||
|
||||
|
||||
### 关于查询 API
|
||||
定位信息查询 API 的原型为:
|
||||
```java
|
||||
String search(String ipStr) throw Exception;
|
||||
String search(byte[] ip) throw Exception;
|
||||
```
|
||||
查询出错会抛出异常,如果查询成功会返回字符串的 `region` 信息,如果指定的 ip 查询不到会返回空字符串 `""`,这对于自定义数据或者数据不完整的情况会出现。
|
||||
|
||||
### 关于 IPv4 和 IPv6
|
||||
该 xdb 查询客户端实现同时支持对 IPv4 和 IPv6 的查询,使用方式如下:
|
||||
```java
|
||||
import org.lionsoul.ip2region.xdb.Version;
|
||||
|
||||
// 如果是 IPv4: 设置 xdb 路径为 v4 的 xdb 文件,IP版本指定为 Version.IPv4
|
||||
final String dbPath = "../../data/ip2region_v4.xdb"; // 或者你的 ipv4 xdb 的路径
|
||||
final Version version = Version.IPv4;
|
||||
|
||||
// 如果是 IPv6: 设置 xdb 路径为 v6 的 xdb 文件,IP版本指定为 Version.IPv6
|
||||
final String dbPath = "../../data/ip2region_v6.xdb"; // 或者你的 ipv6 xdb 路径
|
||||
final Version version = Version.IPv6;
|
||||
|
||||
// dbPath 指定的 xdb 的 IP 版本必须和 version 指定的一致,不然查询执行的时候会报错
|
||||
// 备注:以下演示直接使用 dbPath 和 version 变量
|
||||
```
|
||||
|
||||
### 文件验证
|
||||
建议您主动去验证 xdb 文件的适用性,因为后期的一些新功能可能会导致目前的 Searcher 版本无法适用你使用的 xdb 文件,验证可以避免运行过程中的一些不可预测的错误。 你不需要每次都去验证,例如在服务启动的时候,或者手动调用命令验证确认版本匹配即可,不要在每次创建的 Searcher 的时候运行验证,这样会影响查询的响应速度,尤其是高并发的使用场景。
|
||||
```java
|
||||
try {
|
||||
Searcher.verifyFromFile(dbPath);
|
||||
} catch (Exception e) {
|
||||
// 适用性验证失败!!!
|
||||
// 当前查询客户端实现不适用于 dbPath 指定的 xdb 文件的查询.
|
||||
// 应该停止启动服务,使用合适的 xdb 文件或者升级到适合 dbPath 的 Searcher 实现。
|
||||
return;
|
||||
}
|
||||
|
||||
// 验证通过,当前使用的 Searcher 可以安全的用于对 dbPath 指向的 xdb 的查询操作
|
||||
```
|
||||
|
||||
### 完全基于文件的查询
|
||||
|
||||
```java
|
||||
import org.lionsoul.ip2region.xdb.Searcher;
|
||||
import java.io.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class SearcherTest {
|
||||
public static void main(String[] args) {
|
||||
// 1、使用上述的 version 和 dbPath 创建 searcher 对象
|
||||
Searcher searcher = null;
|
||||
try {
|
||||
searcher = Searcher.newWithFileOnly(version, dbPath);
|
||||
} catch (IOException e) {
|
||||
System.out.printf("failed to create searcher with `%s`: %s\n", dbPath, e);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2、查询,IPv4 或者 IPv6 的地址都支持
|
||||
try {
|
||||
String ip = "1.2.3.4";
|
||||
// ip = "2001:4:112:ffff:ffff:ffff:ffff:ffff"; // IPv6
|
||||
long sTime = System.nanoTime();
|
||||
String region = searcher.search(ip);
|
||||
long cost = TimeUnit.NANOSECONDS.toMicros((long) (System.nanoTime() - sTime));
|
||||
System.out.printf("{region: %s, ioCount: %d, took: %d μs}\n", region, searcher.getIOCount(), cost);
|
||||
} catch (Exception e) {
|
||||
System.out.printf("failed to search(%s): %s\n", ip, e);
|
||||
}
|
||||
|
||||
// 3、关闭资源
|
||||
searcher.close();
|
||||
|
||||
// 备注:并发使用,每个线程需要创建一个独立的 searcher 对象单独使用。
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 缓存 `VectorIndex` 索引
|
||||
|
||||
我们可以提前从 `xdb` 文件中加载出来 `VectorIndex` 数据,然后全局缓存,每次创建 Searcher 对象的时候使用全局的 VectorIndex 缓存可以减少一次固定的 IO 操作,从而加速查询,减少 IO 压力。
|
||||
```java
|
||||
import org.lionsoul.ip2region.xdb.Searcher;
|
||||
import java.io.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class SearcherTest {
|
||||
public static void main(String[] args) {
|
||||
// 备注:version 和 dbPath 来源,请看上面的版本描述
|
||||
|
||||
// 1、从 dbPath 中预先加载 VectorIndex 缓存,并且把这个得到的数据作为全局变量,后续反复使用。
|
||||
byte[] vIndex;
|
||||
try {
|
||||
vIndex = Searcher.loadVectorIndexFromFile(dbPath);
|
||||
} catch (Exception e) {
|
||||
System.out.printf("failed to load vector index from `%s`: %s\n", dbPath, e);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2、使用全局的 vIndex 创建带 VectorIndex 缓存的查询对象。
|
||||
Searcher searcher;
|
||||
try {
|
||||
searcher = Searcher.newWithVectorIndex(version, dbPath, vIndex);
|
||||
} catch (Exception e) {
|
||||
System.out.printf("failed to create vectorIndex cached searcher with `%s`: %s\n", dbPath, e);
|
||||
return;
|
||||
}
|
||||
|
||||
// 3、查询,IPv4 或者 IPv6 地址都支持
|
||||
try {
|
||||
String ip = "1.2.3.4";
|
||||
// ip = "2001:4:112:ffff:ffff:ffff:ffff:ffff"; // IPv6
|
||||
long sTime = System.nanoTime();
|
||||
String region = searcher.search(ip);
|
||||
long cost = TimeUnit.NANOSECONDS.toMicros((long) (System.nanoTime() - sTime));
|
||||
System.out.printf("{region: %s, ioCount: %d, took: %d μs}\n", region, searcher.getIOCount(), cost);
|
||||
} catch (Exception e) {
|
||||
System.out.printf("failed to search(%s): %s\n", ip, e);
|
||||
}
|
||||
|
||||
// 4、关闭资源
|
||||
searcher.close();
|
||||
|
||||
// 备注:每个线程需要单独创建一个独立的 Searcher 对象,但是都共享全局的只读 vIndex 缓存。
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 缓存整个 `xdb` 数据
|
||||
|
||||
我们也可以预先加载整个 xdb 文件的数据到内存,然后基于这个数据创建查询对象来实现完全基于文件的查询,类似之前的 memory search。
|
||||
```java
|
||||
import org.lionsoul.ip2region.xdb.Searcher;
|
||||
import java.io.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class SearcherTest {
|
||||
public static void main(String[] args) {
|
||||
// 备注:version 和 dbPath 来源,请看上面的版本描述
|
||||
|
||||
// 1、从 dbPath 加载整个 xdb 到内存。
|
||||
// 从这个 release 版本开始,xdb 的 buffer 使用 LongByteArray 来存储,避免 xdb 文件过大的时候 int 类型的溢出
|
||||
LongByteArray cBuff;
|
||||
try {
|
||||
cBuff = Searcher.loadContentFromFile(dbPath);
|
||||
} catch (Exception e) {
|
||||
System.out.printf("failed to load content from `%s`: %s\n", dbPath, e);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2、使用上述的 cBuff 创建一个完全基于内存的查询对象。
|
||||
Searcher searcher;
|
||||
try {
|
||||
searcher = Searcher.newWithBuffer(version, cBuff);
|
||||
} catch (Exception e) {
|
||||
System.out.printf("failed to create content cached searcher: %s\n", e);
|
||||
return;
|
||||
}
|
||||
|
||||
// 3、查询,IPv4 和 IPv6 都支持
|
||||
try {
|
||||
String ip = "1.2.3.4";
|
||||
// ip = "2001:4:112:ffff:ffff:ffff:ffff:ffff"; // IPv6
|
||||
long sTime = System.nanoTime();
|
||||
String region = searcher.search(ip);
|
||||
long cost = TimeUnit.NANOSECONDS.toMicros((long) (System.nanoTime() - sTime));
|
||||
System.out.printf("{region: %s, ioCount: %d, took: %d μs}\n", region, searcher.getIOCount(), cost);
|
||||
} catch (Exception e) {
|
||||
System.out.printf("failed to search(%s): %s\n", ip, e);
|
||||
}
|
||||
|
||||
// 4、关闭资源 - 该 searcher 对象可以安全用于并发,等整个服务关闭的时候再关闭 searcher
|
||||
// searcher.close();
|
||||
|
||||
// 备注:并发使用,用整个 xdb 数据缓存创建的查询对象可以安全的用于并发,也就是你可以把这个 searcher 对象做成全局对象去跨线程访问。
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
如果调用 `loadContentXXX` 方法来加载 xdb buffer 的过程中出现了 OOM 错误,请参考以下的 [sliceBytes 设置](#slicebytes),选择使用带 sliceBytes 参数的 `loadContentXXX` 方法来加载 。
|
||||
|
||||
### sliceBytes
|
||||
|
||||
sliceBytes 表示 xdb 全内存缓存时 `LongByteArray` 类内部维护的 `List<byte[]> buffs` 集合的分片内存的大小,这个值的最大值也是默认值为 `Searcher.MAX_WRITE_BYTES`,取值的核心是为了减少 `buffs` 的长度, 最小值为 1,buffs 长度越小越好,意味着查询过程中的寻址操作的 buffs 遍历操作越少,该值的设置原则如下:
|
||||
1. 默认为 `Searcher.MAX_WRITE_BYTES`,也就是 `0x7ffff000`,关于该取值的来源可以参考作者博客文章:[https://mp.weixin.qq.com/s/4xHRcnQbIcjtMGdXEGrxsA](https://mp.weixin.qq.com/s/4xHRcnQbIcjtMGdXEGrxsA)。
|
||||
2. 如果 xdb 文件的字节数小于 `Searcher.MAX_WRITE_BYTES`,则 sliceBytes 设置为该 xdb 文件的字节数即可,如果大于 `Searcher.MAX_WRITE_BYTES` 则使用默认值即可。Searcher 的 `loadContent` 或者 `loadContentFromFile` 方法默认都是按照这个原则来自动设置 sliceBytes 的值,唯独 `loadContentFromInputStream` 系列方法因为不方便获取流的大小使用的是默认最大值,因此可以按照上述原则通过调用 `loadContentFromInputStream(InputStream, int)` 手动设置合理的值,或者设置 JVM 的内存限制避免运行时的 OOM 错误。
|
||||
3. 如果 sliceBytes 设置的值小于甚至远远小于 xdb 文件的字节数则会增加查询过程中的寻址遍历操作从而减慢查询,其他无任何影响,随着 IPv6 的普及后期的 xdb 文件大小可能几个G甚至10G+,所以默认 sliceBytes 取的最大值也是为了默认总是能取得最佳的运行效率。
|
||||
|
||||
|
||||
# 编译测试程序
|
||||
|
||||
通过 maven 来编译测试程序。
|
||||
```bash
|
||||
# cd 到 java binding 的根目录
|
||||
cd binding/java/
|
||||
mvn compile package
|
||||
```
|
||||
|
||||
然后会在当前目录的 target 目录下得到一个 ip2region-{version}.jar 的打包文件。
|
||||
|
||||
|
||||
|
||||
# 查询测试
|
||||
|
||||
### 测试命令
|
||||
可以通过 `java -jar target/ip2region-{version}.jar search` 命令来测试查询:
|
||||
```bash
|
||||
➜ java git:(master) ✗ java -jar target/ip2region-3.3.0.jar search --help
|
||||
java -jar ip2region-{version}.jar search [command options]
|
||||
options:
|
||||
--v4-db string ip2region ipv4 binary xdb file path
|
||||
--v4-cache-policy string v4 cache policy, default vectorIndex, options: file/vectorIndex/content
|
||||
--v6-db string ip2region ipv6 binary xdb file path
|
||||
--v6-cache-policy string v6 cache policy, default vectorIndex, options: file/vectorIndex/content
|
||||
--help print this help menu
|
||||
```
|
||||
|
||||
### 参数解析
|
||||
1. `v4-xdb`: IPv4 的 xdb 文件路径,默认为仓库中的 data/ip2region_v4.xdb
|
||||
2. `v6-xdb`: IPv6 的 xdb 文件路径,默认为仓库中的 data/ip2region_v6.xdb
|
||||
3. `v4-cache-policy`: v4 查询使用的缓存策略,默认为 `vectorIndex`,可选:file/vectorIndex/content
|
||||
4. `v6-cache-policy`: v6 查询使用的缓存策略,默认为 `vectorIndex`,可选:file/vectorIndex/content
|
||||
|
||||
### 测试 Demo
|
||||
例如:使用默认的 data/ip2region_v4.xdb 和 data/ip2region_v6.xdb 进行查询测试:
|
||||
```bash
|
||||
➜ java git:(java_app_with_ip2region_service) ✗ java -jar target/ip2region-3.3.0.jar search
|
||||
ip2region search service test program
|
||||
+-v4 xdb: /data01/code/c/ip2region/data/ip2region_v4.xdb (vectorIndex)
|
||||
+-v6 xdb: /data01/code/c/ip2region/data/ip2region_v6.xdb (vectorIndex)
|
||||
type 'quit' to exit
|
||||
ip2region>> 1.2.3.4
|
||||
{region: 美国|华盛顿|0|谷歌, took: 159 μs}
|
||||
ip2region>> 240e:3b7:3272:d8d0:db09:c067:8d59:539e
|
||||
{region: 中国|广东省|深圳市|家庭宽带, took: 346 μs}
|
||||
ip2region>>
|
||||
```
|
||||
输入 v4 或者 v6 的 IP 地址即可进行查询测试,也可以分别设置 `cache-policy` 为 file/vectorIndex/content 来测试三种不同缓存实现的查询效果。
|
||||
|
||||
|
||||
# bench 测试
|
||||
|
||||
### 测试命令
|
||||
可以通过 `java -jar ip2region-{version}.jar bench` 命令来进行 bench 测试,一方面确保 `xdb` 文件没有错误,一方面可以评估查询性能:
|
||||
```bash
|
||||
➜ java git:(fr_java_ipv6) ✗ java -jar target/ip2region-3.1.0.jar bench
|
||||
java -jar ip2region-{version}.jar bench [command options]
|
||||
options:
|
||||
--db string ip2region binary xdb file path
|
||||
--src string source ip text file path
|
||||
--cache-policy string cache policy: file/vectorIndex/content
|
||||
```
|
||||
|
||||
### v4 bench
|
||||
例如:通过默认的 data/ip2region_v4.xdb 和 data/ipv4_source.txt 文件进行 IPv4 的 bench 测试:
|
||||
```bash
|
||||
java -jar target/ip2region-3.1.0.jar bench --db=../../data/ip2region_v4.xdb --src=../../data/ipv4_source.txt
|
||||
```
|
||||
|
||||
### v6 bench
|
||||
例如:通过默认的 data/ip2region_v6.xdb 和 data/ipv6_source.txt 文件进行 IPv6 的 bench 测试:
|
||||
```bash
|
||||
java -jar target/ip2region-3.1.0.jar bench --db=../../data/ip2region_v6.xdb --src=../../data/ipv6_source.txt
|
||||
```
|
||||
|
||||
可以通过分别设置 `cache-policy` 为 file/vectorIndex/content 来测试三种不同缓存实现的效果。
|
||||
@Note: 注意 bench 使用的 src 文件要是生成对应 xdb 文件相同的源文件。
|
||||
|
|
@ -0,0 +1,204 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>org.lionsoul</groupId>
|
||||
<artifactId>ip2region</artifactId>
|
||||
<version>3.3.2</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>ip2region</name>
|
||||
<url>https://github.com/lionsoul2014/ip2region</url>
|
||||
<description>An open source offline IP address data manager framework and locator with both IPv4 and IPv6 suppported</description>
|
||||
|
||||
<licenses>
|
||||
<license>
|
||||
<name>The Apache Software License, Version 2.0</name>
|
||||
<url>https://www.apache.org/licenses/LICENSE-2.0.txt</url>
|
||||
<distribution>repo</distribution>
|
||||
</license>
|
||||
</licenses>
|
||||
|
||||
<scm>
|
||||
<url>git@github.com:lionsoul2014/ip2region.git</url>
|
||||
<connection>scm:git:git@github.com:lionsoul2014/ip2region.git</connection>
|
||||
<developerConnection>scm:git:git@github.com:lionsoul2014/ip2region.git</developerConnection>
|
||||
</scm>
|
||||
|
||||
<developers>
|
||||
<developer>
|
||||
<id>lionsoul</id>
|
||||
<name>chenxin</name>
|
||||
<email>chenxin619315@gmail.com</email>
|
||||
</developer>
|
||||
</developers>
|
||||
|
||||
<distributionManagement>
|
||||
<snapshotRepository>
|
||||
<id>lionsoul</id>
|
||||
<url>https://oss.sonatype.org/content/repositories/snapshots/</url>
|
||||
</snapshotRepository>
|
||||
<repository>
|
||||
<id>lionsoul</id>
|
||||
<url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
|
||||
</repository>
|
||||
</distributionManagement>
|
||||
|
||||
<issueManagement>
|
||||
<url>https://github.com/lionsoul2014/ip2region/issues</url>
|
||||
<system>Github issues</system>
|
||||
</issueManagement>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
<maven.compiler.source>1.8</maven.compiler.source>
|
||||
<maven.compiler.target>1.8</maven.compiler.target>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.13.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
<version>2.1.2</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-sources</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>2.9</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-javadocs</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<additionalparam>${javadoc.opts}</additionalparam>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<failOnError>false</failOnError>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>1.4</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<transformers>
|
||||
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||
<mainClass>org.lionsoul.ip2region.SearcherTest</mainClass>
|
||||
</transformer>
|
||||
</transformers>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>java8-doclint-disabled</id>
|
||||
<activation>
|
||||
<jdk>[1.8,)</jdk>
|
||||
</activation>
|
||||
<properties>
|
||||
<javadoc.opts>-Xdoclint:none</javadoc.opts>
|
||||
</properties>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>release</id>
|
||||
<build>
|
||||
<plugins>
|
||||
<!-- Source -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
<version>2.2.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>jar-no-fork</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<!-- Javadoc -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>2.9.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<additionalparam>${javadoc.opts}</additionalparam>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<!-- GPG -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-gpg-plugin</artifactId>
|
||||
<version>1.5</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>verify</phase>
|
||||
<goals>
|
||||
<goal>sign</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<!--use the Central Portal: https://central.sonatype.org/publish/publish-portal-maven/ instead-->
|
||||
<plugin>
|
||||
<groupId>org.sonatype.central</groupId>
|
||||
<artifactId>central-publishing-maven-plugin</artifactId>
|
||||
<version>0.7.0</version>
|
||||
<extensions>true</extensions>
|
||||
<configuration>
|
||||
<publishingServerId>lionsoul</publishingServerId>
|
||||
<autoPublish>true</autoPublish>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
</profiles>
|
||||
|
||||
</project>
|
||||
|
|
@ -0,0 +1,313 @@
|
|||
// Copyright 2022 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
// @Author Lion <chenxin619315@gmail.com>
|
||||
// @Date 2022/06/23
|
||||
|
||||
package org.lionsoul.ip2region;
|
||||
|
||||
import org.lionsoul.ip2region.service.Config;
|
||||
import org.lionsoul.ip2region.service.InvalidConfigException;
|
||||
import org.lionsoul.ip2region.service.Ip2Region;
|
||||
import org.lionsoul.ip2region.xdb.InetAddressException;
|
||||
import org.lionsoul.ip2region.xdb.XdbException;
|
||||
import org.lionsoul.ip2region.xdb.LongByteArray;
|
||||
import org.lionsoul.ip2region.xdb.Searcher;
|
||||
import org.lionsoul.ip2region.xdb.Util;
|
||||
import org.lionsoul.ip2region.xdb.Version;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.security.CodeSource;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class SearcherTest {
|
||||
|
||||
public static void printHelp(String[] args) {
|
||||
System.out.print("ip2region xdb searcher\n");
|
||||
System.out.print("java -jar ip2region-{version}.jar [command] [command options]\n");
|
||||
System.out.print("Command: \n");
|
||||
System.out.print(" search search input test\n");
|
||||
System.out.print(" bench search bench test\n");
|
||||
}
|
||||
|
||||
public static final String getXdbPath(String fileName) throws IOException {
|
||||
String xdbPath;
|
||||
final CodeSource cs = SearcherTest.class.getProtectionDomain().getCodeSource();
|
||||
if (cs != null) {
|
||||
// log.debugf("code path: %s", cs.getLocation().getPath().concat("../../../../data/"));
|
||||
final Path jarPath = Paths.get(cs.getLocation().getPath());
|
||||
xdbPath = jarPath.getParent().toString().concat("/../../../data/").concat(fileName);
|
||||
} else {
|
||||
xdbPath = "../../../data/".concat(fileName);
|
||||
}
|
||||
|
||||
final File xdbFile = new File(xdbPath);
|
||||
return xdbFile.exists() ? xdbFile.getCanonicalPath() : "";
|
||||
}
|
||||
|
||||
public static final Ip2Region createService(
|
||||
String v4XdbPath, String v4CachePolicy, String v6XdbPath, String v6CachePolicy) throws IOException, XdbException, InvalidConfigException {
|
||||
final Config v4Config = Config.custom()
|
||||
.setCachePolicy(Config.cachePolicyFromName(v4CachePolicy))
|
||||
.setSearchers(1)
|
||||
.setXdbPath(v4XdbPath).asV4();
|
||||
|
||||
final Config v6Config = Config.custom()
|
||||
.setCachePolicy(Config.cachePolicyFromName(v6CachePolicy))
|
||||
.setSearchers(1)
|
||||
.setXdbPath(v6XdbPath).asV6();
|
||||
|
||||
return Ip2Region.create(v4Config, v6Config);
|
||||
}
|
||||
|
||||
public static Searcher createSearcher(String dbPath, String cachePolicy) throws IOException, XdbException {
|
||||
final RandomAccessFile handle = new RandomAccessFile(dbPath, "r");
|
||||
|
||||
// verify the xdb file
|
||||
// @Note: do NOT call it every time you create a searcher since this will slow
|
||||
// down the search response.
|
||||
// @see the util.Verify function for details.
|
||||
Searcher.verify(handle);
|
||||
|
||||
// get the ip version from header
|
||||
final Version version = Version.fromHeader(Searcher.loadHeader(handle));
|
||||
|
||||
// create the final searcher
|
||||
if ("file".equals(cachePolicy)) {
|
||||
return Searcher.newWithFileOnly(version, dbPath);
|
||||
} else if ("vectorIndex".equals(cachePolicy)) {
|
||||
final byte[] vIndex = Searcher.loadVectorIndexFromFile(dbPath);
|
||||
return Searcher.newWithVectorIndex(version, dbPath, vIndex);
|
||||
} else if ("content".equals(cachePolicy)) {
|
||||
final LongByteArray cBuff = Searcher.loadContentFromFile(dbPath);
|
||||
return Searcher.newWithBuffer(version, cBuff);
|
||||
} else {
|
||||
throw new IOException("invalid cache policy `" + cachePolicy + "`, options: file/vectorIndex/content");
|
||||
}
|
||||
}
|
||||
|
||||
public static void searchTest(String[] args) throws IOException, XdbException, InterruptedException, InvalidConfigException {
|
||||
String help = "";
|
||||
String v4DbPath = "", v4CachePolicy = "vectorIndex";
|
||||
String v6DbPath = "", v6CachePolicy = "vectorIndex";
|
||||
for (final String r : args) {
|
||||
if (r.length() < 5) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (r.indexOf("--") != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String key = "", val = "";
|
||||
int sIdx = r.indexOf('=');
|
||||
if (sIdx < 0) {
|
||||
key = r.substring(2);
|
||||
// System.out.printf("missing = for args pair `%s`\n", r);
|
||||
// return;
|
||||
} else {
|
||||
key = r.substring(2, sIdx);
|
||||
val = r.substring(sIdx + 1);
|
||||
}
|
||||
|
||||
// System.out.printf("key=%s, val=%s\n", key, val);
|
||||
if ("help".equals(key)) {
|
||||
help = val == "" ? "true" : val;
|
||||
} else if ("v4-db".equals(key)) {
|
||||
v4DbPath = val;
|
||||
} else if ("v4-cache-policy".equals(key)) {
|
||||
v4CachePolicy = val;
|
||||
} else if ("v6-db".equals(key)) {
|
||||
v6DbPath = val;
|
||||
} else if ("v6-cache-policy".equals(key)) {
|
||||
v6CachePolicy = val;
|
||||
} else {
|
||||
System.out.printf("undefined option `%s`\n", r);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// check and set the default path for v4
|
||||
if (v4DbPath.isEmpty()) {
|
||||
v4DbPath = getXdbPath("ip2region_v4.xdb");
|
||||
}
|
||||
|
||||
// check and set the default path for 6
|
||||
if (v6DbPath.isEmpty()) {
|
||||
v6DbPath = getXdbPath("ip2region_v6.xdb");
|
||||
}
|
||||
|
||||
if (v4DbPath.isEmpty() || v6DbPath.isEmpty() || help.equals("true")) {
|
||||
System.out.print("java -jar ip2region-{version}.jar search [command options]\n");
|
||||
System.out.print("options:\n");
|
||||
System.out.print(" --v4-db string ip2region ipv4 binary xdb file path\n");
|
||||
System.out.print(" --v4-cache-policy string v4 cache policy, default vectorIndex, options: file/vectorIndex/content\n");
|
||||
System.out.print(" --v6-db string ip2region ipv6 binary xdb file path\n");
|
||||
System.out.print(" --v6-cache-policy string v6 cache policy, default vectorIndex, options: file/vectorIndex/content\n");
|
||||
System.out.print(" --help print this help menu\n");
|
||||
return;
|
||||
}
|
||||
|
||||
final Ip2Region ip2region = createService(v4DbPath, v4CachePolicy, v6DbPath, v6CachePolicy);
|
||||
final BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
|
||||
System.out.printf("ip2region search service test program\n"
|
||||
+ "+-v4 xdb: %s (%s)\n"
|
||||
+ "+-v6 xdb: %s (%s)\n"
|
||||
+ "type 'quit' to exit\n", v4DbPath, v4CachePolicy, v6DbPath, v6CachePolicy);
|
||||
while ( true ) {
|
||||
System.out.print("ip2region>> ");
|
||||
String line = reader.readLine().trim();
|
||||
if ( line.length() < 2 ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( line.equalsIgnoreCase("quit") ) {
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
double sTime = System.nanoTime();
|
||||
String region = ip2region.search(line);
|
||||
long cost = TimeUnit.NANOSECONDS.toMicros((long) (System.nanoTime() - sTime));
|
||||
System.out.printf("{region: %s, took: %d μs}\n", region, cost);
|
||||
} catch (Exception e) {
|
||||
System.out.printf("{region: , err: %s}\n", e);
|
||||
}
|
||||
}
|
||||
|
||||
reader.close();
|
||||
ip2region.close();
|
||||
System.out.println("ip2region test program exited, thanks for trying");
|
||||
}
|
||||
|
||||
public static void benchTest(String[] args) throws IOException, XdbException, InetAddressException {
|
||||
String dbPath = "", srcPath = "", cachePolicy = "vectorIndex";
|
||||
for (final String r : args) {
|
||||
if (r.length() < 5) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (r.indexOf("--") != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int sIdx = r.indexOf('=');
|
||||
if (sIdx < 0) {
|
||||
System.out.printf("missing = for args pair `%s`\n", r);
|
||||
return;
|
||||
}
|
||||
|
||||
String key = r.substring(2, sIdx);
|
||||
String val = r.substring(sIdx + 1);
|
||||
if ("db".equals(key)) {
|
||||
dbPath = val;
|
||||
} else if ("src".equals(key)) {
|
||||
srcPath = val;
|
||||
} else if ("cache-policy".equals(key)) {
|
||||
cachePolicy = val;
|
||||
} else {
|
||||
System.out.printf("undefined option `%s`\n", r);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (dbPath.length() < 1 || srcPath.length() < 1) {
|
||||
System.out.print("java -jar ip2region-{version}.jar bench [command options]\n");
|
||||
System.out.print("options:\n");
|
||||
System.out.print(" --db string ip2region binary xdb file path\n");
|
||||
System.out.print(" --src string source ip text file path\n");
|
||||
System.out.print(" --cache-policy string cache policy: file/vectorIndex/content\n");
|
||||
return;
|
||||
}
|
||||
|
||||
Searcher searcher = createSearcher(dbPath, cachePolicy);
|
||||
long count = 0, costs = 0, tStart = System.nanoTime();
|
||||
String line;
|
||||
final Charset charset = Charset.forName("utf-8");
|
||||
final FileInputStream fis = new FileInputStream(srcPath);
|
||||
final BufferedReader reader = new BufferedReader(new InputStreamReader(fis, charset));
|
||||
while ((line = reader.readLine()) != null) {
|
||||
String l = line.trim();
|
||||
String[] ps = l.split("\\|", 3);
|
||||
if (ps.length != 3) {
|
||||
reader.close();
|
||||
System.out.printf("invalid ip segment `%s`\n", l);
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] sip;
|
||||
try {
|
||||
sip = Util.parseIP(ps[0]);
|
||||
} catch (Exception e) {
|
||||
reader.close();
|
||||
System.out.printf("check start ip `%s`: %s\n", ps[0], e);
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] eip;
|
||||
try {
|
||||
eip = Util.parseIP(ps[1]);
|
||||
} catch (Exception e) {
|
||||
reader.close();
|
||||
System.out.printf("check end ip `%s`: %s\n", ps[1], e);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Util.ipCompare(sip, eip) > 0) {
|
||||
reader.close();
|
||||
System.out.printf("start ip(%s) should not be greater than end ip(%s)\n", ps[0], ps[1]);
|
||||
return;
|
||||
}
|
||||
|
||||
for (final byte[] ip : new byte[][]{sip, eip}) {
|
||||
long sTime = System.nanoTime();
|
||||
String region = searcher.search(ip);
|
||||
costs += System.nanoTime() - sTime;
|
||||
|
||||
// check the region info
|
||||
if (!ps[2].equals(region)) {
|
||||
System.out.printf("failed search(%s) with (%s != %s)\n", Util.ipToString(ip), region, ps[2]);
|
||||
reader.close();
|
||||
return;
|
||||
}
|
||||
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
reader.close();
|
||||
searcher.close();
|
||||
long took = System.nanoTime() - tStart;
|
||||
System.out.printf("Bench finished, {cachePolicy: %s, total: %d, took: %ds, cost: %d μs/op}\n",
|
||||
cachePolicy, count, TimeUnit.NANOSECONDS.toSeconds(took),
|
||||
count == 0 ? 0 : TimeUnit.NANOSECONDS.toMicros(costs/count));
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
if (args.length < 1) {
|
||||
printHelp(args);
|
||||
return;
|
||||
}
|
||||
|
||||
if ("search".equals(args[0])) {
|
||||
try {
|
||||
searchTest(args);
|
||||
} catch (Exception e) {
|
||||
System.out.printf("failed running search test: %s\n", e);
|
||||
}
|
||||
} else if ("bench".equals(args[0])) {
|
||||
try {
|
||||
benchTest(args);
|
||||
} catch (Exception e) {
|
||||
System.out.printf("fwailed running bench test: %s\n", e);
|
||||
}
|
||||
} else {
|
||||
printHelp(args);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
// Copyright 2022 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package org.lionsoul.ip2region.service;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.lionsoul.ip2region.xdb.Header;
|
||||
import org.lionsoul.ip2region.xdb.LongByteArray;
|
||||
import org.lionsoul.ip2region.xdb.Version;
|
||||
import org.lionsoul.ip2region.xdb.XdbException;
|
||||
|
||||
/**
|
||||
* ip2region config class
|
||||
* @Author Lion <chenxin619315@gmail.com>
|
||||
* @Date 2025/11/20
|
||||
*/
|
||||
public class Config {
|
||||
// cache policy consts
|
||||
public static final int NoCache = 0;
|
||||
public static final int VIndexCache = 1;
|
||||
public static final int BufferCache = 2;
|
||||
|
||||
// alias of BufferCache but easier to understand or Remember
|
||||
public static final int FullCache = 2;
|
||||
|
||||
// search cache policy
|
||||
public final int cachePolicy;
|
||||
public final Version ipVersion;
|
||||
|
||||
// xdb file path
|
||||
public final File xdbFile;
|
||||
public final Header header;
|
||||
|
||||
public final byte[] vIndex;
|
||||
public final LongByteArray cBuffer;
|
||||
|
||||
public final int searchers;
|
||||
|
||||
// config builder
|
||||
public static ConfigBuilder custom() {
|
||||
return new ConfigBuilder();
|
||||
}
|
||||
|
||||
protected Config(int cachePolicy, Version ipVersion, File xdbFile,
|
||||
Header header, byte[] vIndex, LongByteArray cBuffer, int searchers) throws IOException, XdbException {
|
||||
this.cachePolicy = cachePolicy;
|
||||
this.ipVersion = ipVersion;
|
||||
|
||||
this.xdbFile = xdbFile;
|
||||
this.header = header;
|
||||
this.vIndex = vIndex;
|
||||
this.cBuffer = cBuffer;
|
||||
|
||||
final Version xVersion = Version.fromHeader(header);
|
||||
// verify the ip version (ipVersion and the version of the xdb file should be the same)
|
||||
if (header.ipVersion != ipVersion.id) {
|
||||
throw new XdbException("ip verison not match: xdb file "
|
||||
+ xdbFile.getAbsolutePath() + " (" + xVersion.name + "), as " + ipVersion.name + " expected");
|
||||
}
|
||||
|
||||
this.searchers = searchers;
|
||||
}
|
||||
|
||||
@Override public String toString() {
|
||||
final StringBuffer sb = new StringBuffer();
|
||||
sb.append('{');
|
||||
sb.append("cache_policy:").append(cachePolicy).append(',');
|
||||
sb.append("version:").append(ipVersion.toString()).append(',');
|
||||
sb.append("xdb_path:").append(xdbFile == null ? "null" : xdbFile.getAbsolutePath()).append(',');
|
||||
sb.append("header:").append(header.toString()).append(',');
|
||||
if (vIndex == null) {
|
||||
sb.append("v_index: null, ");
|
||||
} else {
|
||||
sb.append("v_index: {bytes: ").append(vIndex.length).append("},");
|
||||
}
|
||||
if (cBuffer == null) {
|
||||
sb.append("c_buffer: null, ");
|
||||
} else {
|
||||
sb.append("c_buffer: {bytes: ").append(cBuffer.length()).append("},");
|
||||
}
|
||||
sb.append("searchers:").append(searchers);
|
||||
sb.append('}');
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static final int cachePolicyFromName(String name) throws InvalidConfigException {
|
||||
final String lName = name.toLowerCase();
|
||||
if (lName.equals("file") || lName.equals("nocache")) {
|
||||
return NoCache;
|
||||
} else if (lName.equals("vectorindex") || lName.equals("vindex") || lName.equals("vindexcache")) {
|
||||
return VIndexCache;
|
||||
} else if (lName.equals("content") || lName.equals("buffercache")) {
|
||||
return BufferCache;
|
||||
} else {
|
||||
throw new InvalidConfigException("invalid cache policy `" + name + "`");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,135 @@
|
|||
// Copyright 2022 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package org.lionsoul.ip2region.service;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.RandomAccessFile;
|
||||
|
||||
import org.lionsoul.ip2region.xdb.Header;
|
||||
import org.lionsoul.ip2region.xdb.LongByteArray;
|
||||
import org.lionsoul.ip2region.xdb.Searcher;
|
||||
import org.lionsoul.ip2region.xdb.Version;
|
||||
import org.lionsoul.ip2region.xdb.XdbException;
|
||||
|
||||
/**
|
||||
* ip2region config builder
|
||||
* @Author Lion <chenxin619315@gmail.com>
|
||||
* @Date 2025/11/20
|
||||
*/
|
||||
public class ConfigBuilder {
|
||||
|
||||
// cache policy
|
||||
private int cachePolicy = Config.VIndexCache;
|
||||
|
||||
// xdb file path / Object / InputStream.
|
||||
// Priority: InputStream -> File Object -> String path
|
||||
private String xdbPath = null;
|
||||
private File xdbFile = null;
|
||||
private InputStream xdbInputStream = null;
|
||||
|
||||
// slice bytes for in-memory xdb content
|
||||
private int cacheSliceBytes = Searcher.MAX_WRITE_BYTES;
|
||||
|
||||
// searchers
|
||||
private int searchers = 20;
|
||||
|
||||
public ConfigBuilder() {}
|
||||
|
||||
public ConfigBuilder(String xdbPath) {
|
||||
this.xdbPath = xdbPath;
|
||||
}
|
||||
|
||||
public ConfigBuilder setCachePolicy(int cachePolicy) {
|
||||
this.cachePolicy = cachePolicy;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ConfigBuilder setXdbPath(String xdbPath) {
|
||||
this.xdbPath = xdbPath;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ConfigBuilder setXdbFile(File xdbFile) {
|
||||
this.xdbFile = xdbFile;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ConfigBuilder setXdbInputStream(InputStream xdbInputStream) {
|
||||
this.xdbInputStream = xdbInputStream;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ConfigBuilder setCacheSliceBytes(int cacheSliceBytes) {
|
||||
this.cacheSliceBytes = cacheSliceBytes;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ConfigBuilder setSearchers(int searchers) {
|
||||
this.searchers = searchers;
|
||||
return this;
|
||||
}
|
||||
|
||||
private Config build(Version ipVersion) throws IOException, XdbException, InvalidConfigException {
|
||||
if (xdbInputStream == null) {
|
||||
// everyting is fine
|
||||
} else if (cachePolicy != Config.BufferCache) {
|
||||
// @Note: we can't directly rewrite the cachePolicy to Config.BufferCache.
|
||||
// you must know what you are doing.
|
||||
throw new InvalidConfigException("SetXdbInputStream could ONLY be used with cachePolicy = Config.BufferCache");
|
||||
} else {
|
||||
// 1, load the content buffer
|
||||
final LongByteArray cBuffer = Searcher.loadContentFromInputStream(xdbInputStream, cacheSliceBytes);
|
||||
|
||||
// 2, verify the xdb from the buffer
|
||||
Searcher.verify(Searcher.loadHeaderFromBuffer(cBuffer), cBuffer.length());
|
||||
|
||||
// 3, load the header
|
||||
final Header header = Searcher.loadHeaderFromBuffer(cBuffer);
|
||||
|
||||
// create the config without xdbFile and vIndex
|
||||
return new Config(cachePolicy, ipVersion, null, header, null, cBuffer, searchers);
|
||||
}
|
||||
|
||||
// load the header and the cache buffer
|
||||
final File xdbFile;
|
||||
if (this.xdbFile != null) {
|
||||
xdbFile = this.xdbFile;
|
||||
} else if (this.xdbPath != null) {
|
||||
xdbFile = new File(this.xdbPath);
|
||||
} else {
|
||||
throw new InvalidConfigException("Both xdbFile and xdbPath is null");
|
||||
}
|
||||
|
||||
final RandomAccessFile raf = new RandomAccessFile(xdbFile, "r");
|
||||
|
||||
// 1, verify the xdb
|
||||
Searcher.verify(raf);
|
||||
|
||||
// 2, load the header
|
||||
final Header header = Searcher.loadHeader(raf);
|
||||
|
||||
// 3, check and load the vector index buffer
|
||||
final byte[] vIndex = cachePolicy == Config.VIndexCache ? Searcher.loadVectorIndex(raf) : null;
|
||||
|
||||
// 4, check and load the content buffer
|
||||
final LongByteArray cBuffer = cachePolicy == Config.BufferCache ? Searcher.loadContent(raf, cacheSliceBytes) : null;
|
||||
|
||||
raf.close();
|
||||
return new Config(cachePolicy, ipVersion, xdbFile, header, vIndex, cBuffer, searchers);
|
||||
}
|
||||
|
||||
// build the final #Config instance for IPv4
|
||||
public Config asV4() throws IOException, XdbException, InvalidConfigException {
|
||||
return build(Version.IPv4);
|
||||
}
|
||||
|
||||
// build the final #Config instance for IPv6
|
||||
public Config asV6() throws IOException, XdbException, InvalidConfigException {
|
||||
return build(Version.IPv6);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package org.lionsoul.ip2region.service;
|
||||
|
||||
public class InvalidConfigException extends Exception {
|
||||
public InvalidConfigException(String str) {
|
||||
super(str);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,177 @@
|
|||
// Copyright 2022 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package org.lionsoul.ip2region.service;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.lionsoul.ip2region.xdb.InetAddressException;
|
||||
import org.lionsoul.ip2region.xdb.Searcher;
|
||||
import org.lionsoul.ip2region.xdb.Util;
|
||||
import org.lionsoul.ip2region.xdb.XdbException;
|
||||
|
||||
/**
|
||||
* ip2region searcher manager service to provider:
|
||||
* 1. Unified query interface for IPv4 and IPv6 address.
|
||||
* 2. Concurrency search support.
|
||||
*
|
||||
* @Author Lion <chenxin619315@gmail.com>
|
||||
* Date 2025/11/21
|
||||
*/
|
||||
public class Ip2Region {
|
||||
|
||||
/* v4 pool for cache policy vIndex or NoCache */
|
||||
private final SearcherPool v4Pool;
|
||||
|
||||
/* v4 xdb searcher for cache policy cBuffer */
|
||||
private final Searcher v4InMemSearcher;
|
||||
|
||||
/* v6 pool for cache policy vIndex or NoCache*/
|
||||
private final SearcherPool v6Pool;
|
||||
|
||||
/* v6 xdb searcher for cache policy cBuffer */
|
||||
private final Searcher v6InMemSearcher;
|
||||
|
||||
public static final Ip2Region create(final Config v4Config, final Config v6Config) throws IOException {
|
||||
return new Ip2Region(v4Config, v6Config).init();
|
||||
}
|
||||
|
||||
public static final Ip2Region create(final String v4XdbPath, final String v6XdbPath) throws IOException, XdbException, InvalidConfigException {
|
||||
return new Ip2Region(new File(v4XdbPath), new File(v6XdbPath)).init();
|
||||
}
|
||||
|
||||
public static final Ip2Region create(final File v4XdbFile, final File v6XdbFile) throws IOException, XdbException, InvalidConfigException {
|
||||
return new Ip2Region(v4XdbFile, v6XdbFile).init();
|
||||
}
|
||||
|
||||
/**
|
||||
* init the ip2reigon with two xdb file path and default cachePolicy vIndex.
|
||||
* set it to null to disabled the search for specified version
|
||||
*
|
||||
* @param v4XdbFile
|
||||
* @param v6XdbFile
|
||||
* @throws XdbException
|
||||
* @throws IOException
|
||||
* @throws InvalidConfigException
|
||||
*/
|
||||
protected Ip2Region(File v4XdbFile, File v6XdbFile) throws IOException, XdbException, InvalidConfigException {
|
||||
this(
|
||||
v4XdbFile == null ? null : Config.custom().setXdbFile(v4XdbFile).asV4(),
|
||||
v6XdbFile == null ? null : Config.custom().setXdbFile(v6XdbFile).asV6()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* init the ip2region with specified config.
|
||||
* set it to null for disabled the search for specified version
|
||||
*
|
||||
* @param v4Config
|
||||
* @param v6Config
|
||||
* @throws IOException
|
||||
*/
|
||||
protected Ip2Region(Config v4Config, Config v6Config) throws IOException {
|
||||
if (v4Config == null) {
|
||||
// @Note: with IPv4 disabled ?
|
||||
this.v4InMemSearcher = null;
|
||||
this.v4Pool = null;
|
||||
} else if (v4Config.cachePolicy == Config.BufferCache) {
|
||||
this.v4InMemSearcher = Searcher.newWithBuffer(v4Config.ipVersion, v4Config.cBuffer);
|
||||
this.v4Pool = null;
|
||||
} else {
|
||||
this.v4InMemSearcher = null;
|
||||
this.v4Pool = new SearcherPool(v4Config);
|
||||
}
|
||||
|
||||
if (v6Config == null) {
|
||||
// @Note: with IPv6 disabled ?
|
||||
this.v6InMemSearcher = null;
|
||||
this.v6Pool = null;
|
||||
} else if (v6Config.cachePolicy == Config.BufferCache) {
|
||||
this.v6InMemSearcher = Searcher.newWithBuffer(v6Config.ipVersion, v6Config.cBuffer);
|
||||
this.v6Pool = null;
|
||||
} else {
|
||||
this.v6InMemSearcher = null;
|
||||
this.v6Pool = new SearcherPool(v6Config);
|
||||
}
|
||||
}
|
||||
|
||||
// init the current ip2region service
|
||||
protected Ip2Region init() throws IOException {
|
||||
if (v4Pool != null) {
|
||||
v4Pool.init();
|
||||
}
|
||||
|
||||
if (v6Pool != null) {
|
||||
v6Pool.init();
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public String search(String ipString) throws InetAddressException, IOException, InterruptedException {
|
||||
return search(Util.parseIP(ipString));
|
||||
}
|
||||
|
||||
public String search(byte[] ipBytes) throws InetAddressException, IOException, InterruptedException {
|
||||
if (ipBytes.length == 4) {
|
||||
return v4Search(ipBytes);
|
||||
} else if (ipBytes.length == 16) {
|
||||
return v6Search(ipBytes);
|
||||
} else {
|
||||
throw new InetAddressException("invalid byte ip address with length=" + ipBytes.length);
|
||||
}
|
||||
}
|
||||
|
||||
protected String v4Search(final byte[] ipBytes) throws IOException, InetAddressException, InterruptedException {
|
||||
if (v4InMemSearcher != null) {
|
||||
return v4InMemSearcher.search(ipBytes);
|
||||
}
|
||||
|
||||
// IPv4 search is disabled
|
||||
if (v4Pool == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final Searcher searcher = v4Pool.borrowSearcher();
|
||||
try {
|
||||
return searcher.search(ipBytes);
|
||||
} finally {
|
||||
v4Pool.returnSearcher(searcher);
|
||||
}
|
||||
}
|
||||
|
||||
protected String v6Search(final byte[] ipBytes) throws IOException, InetAddressException, InterruptedException {
|
||||
if (v6InMemSearcher != null) {
|
||||
return v6InMemSearcher.search(ipBytes);
|
||||
}
|
||||
|
||||
// IPv6 search is disabled
|
||||
if (v6Pool == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final Searcher searcher = v6Pool.borrowSearcher();
|
||||
try {
|
||||
return searcher.search(ipBytes);
|
||||
} finally {
|
||||
v6Pool.returnSearcher(searcher);
|
||||
}
|
||||
}
|
||||
|
||||
public void close() throws InterruptedException {
|
||||
close(10000);
|
||||
}
|
||||
|
||||
public void close(long timeoutMillis) throws InterruptedException {
|
||||
if (v4Pool != null) {
|
||||
v4Pool.close(timeoutMillis);
|
||||
}
|
||||
|
||||
if (v6Pool != null) {
|
||||
v6Pool.close(timeoutMillis);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,130 @@
|
|||
// Copyright 2022 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package org.lionsoul.ip2region.service;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.locks.Condition;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import org.lionsoul.ip2region.xdb.Searcher;
|
||||
|
||||
/**
|
||||
* ip2region searcher pool manager to provider Concurrency search support.
|
||||
*
|
||||
* @author Leon<chenxin619315@gmail.com>
|
||||
* Date 2025/11/21
|
||||
*/
|
||||
public class SearcherPool {
|
||||
// config instance
|
||||
public final Config config;
|
||||
|
||||
// searcher pool
|
||||
private final Queue<Searcher> pool;
|
||||
|
||||
// lock & conditions
|
||||
private final ReentrantLock lock;
|
||||
private final Condition emptyCondition;
|
||||
private final Condition fullCondition;
|
||||
|
||||
// searcher numbers that was loaned out
|
||||
private int loanCount;
|
||||
|
||||
// static method to create and init the searcher pool
|
||||
public static final SearcherPool create(final Config config) throws IOException {
|
||||
return new SearcherPool(config).init();
|
||||
}
|
||||
|
||||
public static final SearcherPool create(final Config config, boolean fair) throws IOException {
|
||||
return new SearcherPool(config, fair).init();
|
||||
}
|
||||
|
||||
protected SearcherPool(Config config) throws IOException {
|
||||
this(config, false);
|
||||
}
|
||||
|
||||
protected SearcherPool(Config config, boolean fair) {
|
||||
assert config.searchers > 0;
|
||||
this.config = config;
|
||||
this.pool = new LinkedList<>();
|
||||
this.lock = new ReentrantLock(fair);
|
||||
this.emptyCondition = this.lock.newCondition();
|
||||
this.fullCondition = this.lock.newCondition();
|
||||
this.loanCount = 0;
|
||||
}
|
||||
|
||||
protected SearcherPool init() throws IOException {
|
||||
// create the searchers
|
||||
for (int i = pool.size(); i < config.searchers; i++) {
|
||||
final Searcher searcher = new Searcher(config.ipVersion, config.xdbFile, config.vIndex, config.cBuffer);
|
||||
pool.add(searcher);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getLoanCount() {
|
||||
lock.lock();
|
||||
int lc = this.loanCount;
|
||||
lock.unlock();
|
||||
return lc;
|
||||
}
|
||||
|
||||
public Searcher borrowSearcher() throws InterruptedException {
|
||||
lock.lock();
|
||||
try {
|
||||
while (pool.isEmpty()) {
|
||||
emptyCondition.await();
|
||||
}
|
||||
|
||||
loanCount++;
|
||||
return pool.poll();
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void returnSearcher(final Searcher searcher) {
|
||||
lock.lock();
|
||||
try {
|
||||
pool.add(searcher);
|
||||
loanCount--;
|
||||
emptyCondition.signal();
|
||||
|
||||
// check and signal the full condition.
|
||||
// pool close
|
||||
if (loanCount == 0) {
|
||||
fullCondition.signal();
|
||||
}
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
// close the searcher pool
|
||||
public void close() throws InterruptedException {
|
||||
close(10000);
|
||||
}
|
||||
|
||||
public void close(long timeoutMillis) throws InterruptedException {
|
||||
lock.lock();
|
||||
try {
|
||||
while (loanCount > 0) {
|
||||
fullCondition.wait(timeoutMillis);
|
||||
}
|
||||
|
||||
final Iterator<Searcher> it = pool.iterator();
|
||||
while (it.hasNext()) {
|
||||
final Searcher searcher = it.next();
|
||||
try {searcher.close();} catch (IOException e) {}
|
||||
it.remove();
|
||||
}
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
// Copyright 2022 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
// @Author Lion <chenxin619315@gmail.com>
|
||||
// @Date 2022/06/23
|
||||
|
||||
package org.lionsoul.ip2region.xdb;
|
||||
|
||||
public class Header {
|
||||
public final int version;
|
||||
public final int indexPolicy;
|
||||
public final long createdAt;
|
||||
public final long startIndexPtr;
|
||||
public final long endIndexPtr;
|
||||
|
||||
// since xdb 3.0 with IPv6 supporting
|
||||
public final int ipVersion;
|
||||
public final int runtimePtrBytes;
|
||||
|
||||
public final byte[] buffer;
|
||||
|
||||
public Header(byte[] buff) {
|
||||
assert buff.length >= 16;
|
||||
version = LittleEndian.getUint16(buff, 0);
|
||||
indexPolicy = LittleEndian.getUint16(buff, 2);
|
||||
createdAt = LittleEndian.getUint32(buff, 4);
|
||||
startIndexPtr = LittleEndian.getUint32(buff, 8);
|
||||
endIndexPtr = LittleEndian.getUint32(buff, 12);
|
||||
ipVersion = LittleEndian.getUint16(buff, 16);
|
||||
runtimePtrBytes = LittleEndian.getUint16(buff, 18);
|
||||
buffer = buff;
|
||||
}
|
||||
|
||||
@Override public String toString() {
|
||||
return "{" +
|
||||
"Version:" + version + ',' +
|
||||
"IndexPolicy:" + indexPolicy + ',' +
|
||||
"CreatedAt:" + createdAt + ',' +
|
||||
"StartIndexPtr:" + startIndexPtr + ',' +
|
||||
"EndIndexPtr:" + endIndexPtr + ',' +
|
||||
"IPVersion:" + ipVersion + ',' +
|
||||
"RuntimePtrBytes:" + runtimePtrBytes +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
// Copyright 2022 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package org.lionsoul.ip2region.xdb;
|
||||
|
||||
// IPv4 version implementation
|
||||
// @Author Lion <chenxin619315@gmail.com>
|
||||
// @Date 2025/09/10
|
||||
|
||||
public class IPv4 extends Version {
|
||||
public IPv4() {
|
||||
// segmentIndex: 4 + 4 + 2 + 4
|
||||
super(4, "IPv4", 4, 14);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int putBytes(byte[] buff, int offset, byte[] ip) {
|
||||
// use the Little endian byte order to compatible with the old searcher implementation
|
||||
buff[offset++] = ip[3];
|
||||
buff[offset++] = ip[2];
|
||||
buff[offset++] = ip[1];
|
||||
buff[offset ] = ip[0];
|
||||
return ip.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int ipSubCompare(byte[] ip1, byte[] buff, int offset) {
|
||||
// ip1: Big endian byte order parsed from input
|
||||
// ip2: Little endian byte order read from xdb index.
|
||||
// @Note: to compatible with the old Litten endian index encode implementation.
|
||||
int j = offset + ip1.length - 1;
|
||||
for (int i = 0; i < ip1.length; i++, j--) {
|
||||
final int i1 = (int) (ip1[i] & 0xFF);
|
||||
final int i2 = (int) (buff[j] & 0xFF);
|
||||
if (i1 < i2) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (i1 > i2) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
// Copyright 2022 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package org.lionsoul.ip2region.xdb;
|
||||
|
||||
// IPv4 version implementation
|
||||
// @Author Lion <chenxin619315@gmail.com>
|
||||
// @Date 2025/09/10
|
||||
|
||||
public class IPv6 extends Version {
|
||||
|
||||
public IPv6() {
|
||||
// segmentIndex: 16 + 16 + 2 + 4
|
||||
super(6, "IPv6", 16, 38);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int putBytes(byte[] buff, int offset, byte[] ip) {
|
||||
System.arraycopy(ip, 0, buff, offset, ip.length);
|
||||
return ip.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int ipSubCompare(byte[] ip1, byte[] buff, int offset) {
|
||||
return Util.ipSubCompare(ip1, buff, offset);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
// Copyright 2022 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package org.lionsoul.ip2region.xdb;
|
||||
|
||||
public class InetAddressException extends Exception {
|
||||
|
||||
public InetAddressException(String str) {
|
||||
super(str);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
// Copyright 2022 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package org.lionsoul.ip2region.xdb;
|
||||
|
||||
// Little Endian basic data type decode and encode.
|
||||
// @Author Lion <chenxin619315@gmail.com>
|
||||
// @Date 2025/09/10
|
||||
|
||||
public class LittleEndian {
|
||||
|
||||
public final static int[] shiftIndex = {0, 8, 16, 24, 32, 40, 48, 56};
|
||||
|
||||
// put specified bytes to the buffer started from the offset
|
||||
public static void put(final byte[] buff, int offset, long value, int bytes) {
|
||||
if (bytes > 8) {
|
||||
throw new IndexOutOfBoundsException("bytes should be <= 8");
|
||||
}
|
||||
|
||||
for (int i = 0; i < bytes; i++) {
|
||||
buff[offset++] = (byte)((value >>> shiftIndex[i]) & 0xFF);
|
||||
}
|
||||
}
|
||||
|
||||
// put an uint32 (4 bytes long) to the buffer from the offset
|
||||
public static void putUint32(final byte[] buff, int offset, long value) {
|
||||
buff[offset++] = (byte) (value & 0xFF);
|
||||
buff[offset++] = (byte) ((value >> 8) & 0xFF);
|
||||
buff[offset++] = (byte) ((value >> 16) & 0xFF);
|
||||
buff[offset ] = (byte) ((value >> 24) & 0xFF);
|
||||
}
|
||||
|
||||
// put a 2-bytes int to the buffer from the specified offset
|
||||
public static void putUint16(final byte[] buff, int offset, int value) {
|
||||
buff[offset++] = (byte) (value & 0xFF);
|
||||
buff[offset ] = (byte) ((value >> 8) & 0xFF);
|
||||
}
|
||||
|
||||
// get an uint32 from a byte array from the specified offset
|
||||
public static long getUint32(final byte[] buff, int offset) {
|
||||
return (
|
||||
((buff[offset++] & 0x000000FFL)) |
|
||||
((buff[offset++] << 8) & 0x0000FF00L) |
|
||||
((buff[offset++] << 16) & 0x00FF0000L) |
|
||||
((buff[offset ] << 24) & 0xFF000000L)
|
||||
);
|
||||
}
|
||||
|
||||
// get an 2 bytes int from a byte array from the specified offset
|
||||
public static int getUint16(final byte[] buff, int offset) {
|
||||
return (
|
||||
((buff[offset++]) & 0x000000FF) |
|
||||
((buff[offset ] << 8) & 0x0000FF00)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue