Compare commits

..

564 Commits

Author SHA1 Message Date
Leon / 狮子的魂 2955431edb
Merge pull request #499 from lionsoul2014/fix_golang_searcher
Fix golang searcher
2026-08-01 08:53:04 +08:00
lionsoul2014 c0d85cc71d extend the check for the runtime ptr size 2026-08-01 08:51:32 +08:00
lionsoul2014 d5e3616d8e close the handle 2026-08-01 08:50:56 +08:00
Leon / 狮子的魂 cd40e3a1d5
Merge pull request #497 from lionsoul2014/data_with_geofeed_merge_20260709
merge the geofeeds of global top providers on 20260709
2026-07-10 00:02:42 +08:00
lionsoul2014 ee19ac95ca merge the geofeed of global top provider on 20260709 2026-07-09 23:54:54 +08:00
Leon / 狮子的魂 a99989343e
Merge pull request #495 from Alice39s/fix/js-license-metadata
fix: correct license metadata and Python packaging typo
2026-06-30 22:01:18 +08:00
Alice39s 324bfdf35d
fix: correct Python MANIFEST.in typo 2026-06-30 19:22:01 +09:00
Alice39s f4d81dd3dd
fix: ensure root SPDX identifier and correct npm license metadata 2026-06-30 19:22:01 +09:00
lionsoul2014 ba96deaecf Erlang IPv6 supports 2026-06-28 10:12:48 +08:00
Leon / 狮子的魂 c4eb1a14ce
Merge pull request #492 from Alice39s/feature/erlang-ipv6
feat(binding/erlang): implement IPv6 dual-stack support for Erlang binding
2026-06-28 10:09:08 +08:00
Alice39s dbef88e337
chore: fix trailing whitespace on worktree entries in .gitignore 2026-06-28 05:18:11 +09:00
Alice39s 30ed0cf028
docs(erlang): update README test counts to 63 2026-06-28 05:18:08 +09:00
Alice39s d06690f651
test(erlang): add shared test helper and DRY app setup 2026-06-28 05:18:03 +09:00
Alice39s daf6749c34
fix(erlang): pass expected version to worker and fail fast on xdb mismatch 2026-06-28 05:17:59 +09:00
Alice39s e50ead2cba
fix(erlang): validate tuple segment ranges in ip2region_util 2026-06-28 05:17:55 +09:00
Alice39s b1eb00e25e
docs(erlang): update READMEs with IPv6 examples and benchmark output 2026-06-28 05:10:10 +09:00
Alice39s fac3f73193
test(erlang): expand xdb header and table helper coverage 2026-06-28 05:10:07 +09:00
Alice39s d1dda93abd
fix(erlang): guard xdb:search against unconfigured pool 2026-06-28 05:10:04 +09:00
Alice39s 44013251b6
test(erlang): fix integer ip_version assertion and add invalid format cases 2026-06-28 05:09:38 +09:00
Alice39s 546ce589a9
docs(erlang): update README examples for IPv6 and current test output 2026-06-28 04:58:45 +09:00
Alice39s 507b7bddc1
fix(erlang): post-review cleanups — pread, self-contained tests, poolboy app dep, dead docs 2026-06-28 04:54:08 +09:00
Alice39s 25513cb246
fix(erlang): unify v4 pool name and stop caching errors 2026-06-28 04:12:44 +09:00
Alice39s 445eb4cb8e
fix(erlang): integer guard, cache dedup, dead macro cleanup 2026-06-28 04:12:43 +09:00
Alice39s a1667d1182
refactor(erlang): simplify benchmark output to ms/QPS and add CPU clock info 2026-06-28 01:04:00 +09:00
Alice39s d189535bb8
chore(erlang): add Makefile bench targets, IPv6-capable benchmark script and docs 2026-06-28 00:51:53 +09:00
Alice39s 06d54d8084
docs(erlang): add dual-stack IPv4/IPv6 setup and usage guide 2026-06-28 00:31:17 +09:00
Alice39s 8849cc9130
feat(erlang): auto-route search by IPv4/IPv6 to correct worker pool 2026-06-28 00:07:22 +09:00
Alice39s c911ca1290
feat(erlang): create version-specific worker pools from db config 2026-06-28 00:07:09 +09:00
Alice39s 241831a6f0
feat(erlang): make worker xdb version-aware for IPv4/IPv6 2026-06-28 00:01:09 +09:00
Alice39s 2f0dc5f33f
feat(erlang): add dual-stack IP parsing utilities 2026-06-27 23:52:40 +09:00
Alice39s c1db56c369
feat(erlang): add xdb header parser helper module 2026-06-27 23:51:07 +09:00
Alice39s 211c374ee2
feat(erlang): add xdb header and dual-stack table/pool macros 2026-06-27 23:50:19 +09:00
Alice39s a3b8d817ee
chore: ignore .worktrees directory 2026-06-27 21:27:36 +09:00
lionsoul2014 b7bb22589f fix the name to fit the geo standard 2026-06-26 16:20:14 +08:00
Leon / 狮子的魂 1068bf05a3
Merge pull request #491 from lionsoul2014/ft_golang_segments_iterate_with_full_options
more options for segments iterate
2026-06-23 10:09:23 +08:00
lionsoul2014 fc46254325 more options for segments iterate 2026-06-23 10:07:48 +08:00
lionsoul2014 7b320846c9 update the test assert and optimize the bench tests 2026-06-11 16:37:31 +08:00
Leon / 狮子的魂 e360f3d031
Merge pull request #488 from isslower/codex/python-test-args
Fix Python test helpers to use function arguments
2026-06-09 13:19:39 +08:00
isslower e7c240a8d9 Fix Python test helper arguments 2026-06-09 12:51:13 +08:00
lionsoul2014 d638015592 region.fields with sep 2026-05-30 12:22:44 +08:00
lionsoul2014 3eb65a2657 sep args for segment parse 2026-05-30 11:02:41 +08:00
lionsoul2014 fac3d0376a PushBack for empty match 2026-05-24 20:14:29 +08:00
lionsoul2014 a8b9d542c6 srcReader is nil check 2026-05-24 20:13:47 +08:00
Leon / 狮子的魂 82a73fa60d
Merge pull request #487 from lionsoul2014/fix_golang_cidr_to_range
fix Index overflow for the cidr parse with bits equals to IP max bits
2026-05-19 19:49:56 +08:00
lionsoul2014 8b79b69524 fix Index overflow for the cidr parse with bits equals to IP max bits 2026-05-19 19:43:51 +08:00
Leon / 狮子的魂 c5fb71facb
Merge pull request #486 from lionsoul2014/fr_go_binding_with_xdb_stream_input
xdb io.Reader based stream input supports
2026-05-19 11:46:59 +08:00
lionsoul2014 62e1f98497 xdb io.Reader based stream input supports 2026-05-19 11:45:11 +08:00
Leon / 狮子的魂 2af5ee176c
Merge pull request #485 from lionsoul2014/fr_golang_maker_io_stream
add io.Reader based xdb input supports
2026-05-19 11:43:14 +08:00
lionsoul2014 0ce74b8873 add io.Reader based xdb input supports 2026-05-19 11:42:03 +08:00
Leon / 狮子的魂 f4fcd5e900
Merge pull request #484 from lionsoul2014/fr_golang_cidr_to_range
Fr golang CIDR supports
2026-05-16 11:06:38 +08:00
lionsoul2014 072b6fed24 segments iterate with CIDR supports 2026-05-16 10:58:49 +08:00
lionsoul2014 f939363a19 SegmentFrom parser is ready for CIDR and IP range 2026-05-16 10:14:20 +08:00
lionsoul2014 353524325a CIDR to ip range supports 2026-05-16 00:07:15 +08:00
lionsoul2014 59cf547107 re-use the Header 2026-05-09 19:52:13 +08:00
Leon / 狮子的魂 3147144a7f
Merge pull request #481 from lionsoul2014/data_opt_merge_issues_480
merge the data reported by issues/480
2026-05-08 10:23:36 +08:00
lionsoul2014 d4299a1d1b merge the data reported by issues/480 2026-05-08 10:21:28 +08:00
lionsoul2014 18ecd7ae85 add Cangjie query client 2026-05-06 20:58:41 +08:00
狮子的魂 5fbdef93cd
!24 Add Cangjie binding with service layer, tests, and docs
Merge pull request !24 from lewist4cj/master
2026-05-06 12:48:54 +00:00
lewist4cj a90191e8d8 Add Cangjie binding with service layer, tests, and docs
Cangjie ip2region xdb searcher binding with:
- Low-level Searcher API (content buff / vector index / file only)
- Multi-threaded service layer (Config, SearcherPool, Ip2Region)
- 41 unit tests covering xdb and service packages
- Example project with 4 usage demos + performance benchmark
- Full documentation in README.md
2026-05-05 10:59:51 +08:00
Leon / 狮子的魂 593e028677
Merge pull request #478 from lionsoul2014/golang_maker_region_mgr
Golang maker region struct and cache impl
2026-04-29 17:41:08 +08:00
lionsoul2014 72c657c378 export the Region API for callbacks 2026-04-29 17:36:30 +08:00
lionsoul2014 19a8816827 clean the region cache 2026-04-29 16:04:40 +08:00
lionsoul2014 787bd7ae07 use the Region cache 2026-04-29 15:52:40 +08:00
lionsoul2014 5b10989ac2 Thread-safe impl for CacheRegion 2026-04-29 13:44:14 +08:00
lionsoul2014 90e827633a Use the Region struct to manager the region info instead of old string 2026-04-29 12:23:51 +08:00
Leon / 狮子的魂 4dd0a1e006
Merge pull request #477 from lionsoul2014/data_merge_reports_on_20260429
merge reports before 2026/04/29
2026-04-29 12:17:36 +08:00
lionsoul2014 77134c4315 merge reports before 2026/04/29 2026-04-29 12:15:39 +08:00
Leon / 狮子的魂 fedf5aaf03
Merge pull request #476 from lionsoul2014/data_merge_reports_on_20260420
merge reports from community before 20260420
2026-04-28 19:00:50 +08:00
lionsoul2014 d28a10b0c1 merge reports from community before 20260420 2026-04-28 18:58:34 +08:00
lionsoul2014 aab908c923 create if file not exists 2026-04-24 16:14:45 +08:00
lionsoul2014 c314ef54ca add SaveToFile(string) api 2026-04-24 09:41:02 +08:00
lionsoul2014 69d267d85d fix the bug of convert eList to segment list 2026-04-23 17:33:23 +08:00
Leon / 狮子的魂 a603a75ab7
Merge pull request #475 from lionsoul2014/opt_golang_editor
editor with put callback and auto segments merge
2026-04-22 16:29:55 +08:00
lionsoul2014 d730db816a editor with put callback and auto segments merge 2026-04-22 16:28:11 +08:00
lionsoul2014 d36edc6e15 empty cli args value & help flag support 2026-04-22 15:34:45 +08:00
lionsoul2014 1d9ffabfeb add the LISENCE to the golang package root 2026-04-19 10:23:13 +08:00
Leon / 狮子的魂 03cc433717
Merge pull request #474 from lionsoul2014/opt_v6_stdlize_cn_region_name
stdlize the cn region name for v6 data
2026-04-17 16:30:04 +08:00
lionsoul2014 3562b7af8e stdlize the cn region name for v6 data 2026-04-17 16:27:03 +08:00
Leon / 狮子的魂 165746ba38
Merge pull request #473 from lionsoul2014/opt_data_v4_stdlize_name_of_autonomous_prefecture
use the short name for all the cn autonomous prefecture
2026-04-16 22:46:57 +08:00
lionsoul2014 79243eb0ba use the short name for all the cn autonomous prefecture 2026-04-16 22:45:19 +08:00
lionsoul2014 be71b92f20 stdlize the city name of cn with suffix zhou 2026-04-16 16:30:47 +08:00
Leon / 狮子的魂 d380827871
Merge pull request #472 from lionsoul2014/opt_v4_data_field_name_std
stdlize all the province and city names for CN ipv4 segments
2026-04-16 16:07:02 +08:00
lionsoul2014 1a08f5e568 stdlize all the province and city names for CN ip segments 2026-04-16 16:00:26 +08:00
lionsoul2014 36c9de10bb use the sCmd args for sub-command 2026-04-09 21:06:08 +08:00
Leon / 狮子的魂 61ef15cff0
Merge pull request #468 from lionsoul2014/data_updates_on_20260408
merge some updates reported by community
2026-04-08 22:07:59 +08:00
lionsoul2014 d6444e740f merge some updates reported by community 2026-04-08 22:04:29 +08:00
lionsoul2014 e3094c8ef2 export the cmd util funcs 2026-04-08 10:56:37 +08:00
lionsoul2014 5244ebad08 update doc desc about setFairLock(boolean) 2026-04-05 22:04:48 +08:00
Leon / 狮子的魂 4d391acac2
Merge pull request #466 from lionsoul2014/opt_golang_search_api
Opt golang search api
2026-04-04 18:43:38 +08:00
lionsoul2014 5cb17713a7 input ip type changed to any to supports string or bytes ip 2026-04-04 18:42:12 +08:00
lionsoul2014 e1f0d5126a go1.18+ 2026-04-04 18:39:52 +08:00
lionsoul2014 d9fc17727e add bytes ip sub and middle util func 2026-04-04 18:39:31 +08:00
Leon / 狮子的魂 dabe7798f6
Merge pull request #465 from lionsoul2014/opt_java_service_fair_lock
add ConfigBuilder.setFairLock(boolean) to config whether ReentrantLoc…
2026-04-04 17:56:18 +08:00
lionsoul2014 8f841e6237 add ConfigBuilder.setFairLock(boolean) to config whether ReentrantLock uses a fair lock 2026-04-04 17:54:05 +08:00
Leon / 狮子的魂 d7b60f41e4
Merge pull request #464 from lionsoul2014/data_update_report_by_issue_461
update the data reported by issues/461
2026-04-04 17:15:03 +08:00
lionsoul2014 83d6fafaa0 update the data reported by issues/461 2026-04-04 17:12:37 +08:00
Leon / 狮子的魂 02f6b31894
Merge pull request #463 from lionsoul2014/data_update_on_20260403
merge the reports from chengdu china mobile on 2026/04/03
2026-04-03 12:20:24 +08:00
lionsoul2014 97e647234a merge the reports from chengdu china mobile on 2026/04/03 2026-04-03 12:18:27 +08:00
Leon / 狮子的魂 1209b72452
Merge pull request #462 from lionsoul2014/opt_golang_ip_util
optimize the golang ip utils
2026-04-02 11:28:09 +08:00
lionsoul2014 62aa42e05e remove the old rules 2026-04-02 11:27:25 +08:00
lionsoul2014 e76f995ceb add autoMege flag and return total/merged count for segments iterate 2026-04-02 11:24:56 +08:00
lionsoul2014 edb29aea3b add ip sub/half/middle functions 2026-04-02 11:00:19 +08:00
lionsoul2014 8a7b088216 IPv6 supports for nginx module 2026-03-23 20:55:19 +08:00
Leon / 狮子的魂 897ee5c0a9
Merge pull request #460 from fa1seut0pia/feature-ipv6
IPv6 supports for nginx module
2026-03-23 20:53:33 +08:00
fa1seut0pia 1eba8ece3b 容器化 golang xdb_maker 2026-03-22 09:50:24 +08:00
fa1seut0pia 007cb0808a nginx 模块支持IPv6 2026-03-22 09:35:54 +08:00
fa1seut0pia 7d6ea00303 nginx 模块支持IPv6 2026-03-22 07:06:28 +08:00
fa1seut0pia e183607da5 ngx_module_libs 与 bingding/c/Makefile 生成的 libxdb.a 保持一致 2026-03-22 07:06:28 +08:00
fa1seut0pia b0466348da 避免 shell 通配符展开 2026-03-22 07:06:28 +08:00
lionsoul2014 04e51e2182 mv all the tests files to the ./sample 2026-03-13 09:36:24 +08:00
lionsoul2014 c63b1dc5db empty source and auto append supports 2026-03-12 11:23:43 +08:00
Leon / 狮子的魂 1a29562c2d
Merge pull request #459 from lionsoul2014/fr_java_non_contiguous_range
maker non-contiguous and disorder ip segments supports
2026-03-07 11:30:39 +08:00
lionsoul2014 29c7048f93 optimize the disorder segments check 2026-03-07 11:28:30 +08:00
lionsoul2014 fa16a4a45d non-contiguous and disorder ip range supports 2026-03-06 14:28:07 +08:00
Leon / 狮子的魂 8adec5a3c8
Merge pull request #457 from lionsoul2014/fr_go_editor_non_contiguous_range
discontinuous or unordered data segments edit supports
2026-03-05 14:24:25 +08:00
lionsoul2014 51e194a105 discontinuous or unordered data segments edit supports 2026-03-05 14:22:03 +08:00
lionsoul2014 b965ab8be9 non-contiguous segments load 2026-03-04 14:39:50 +08:00
Leon / 狮子的魂 a720267842
Merge pull request #456 from lionsoul2014/fr_go_non_contiguous_range
non-contiguous ip ranges supports
2026-03-04 14:07:49 +08:00
lionsoul2014 112dfb99b5 non-contiguous ip ranges supports 2026-03-04 12:44:05 +08:00
lionsoul2014 ca665cfd2e update the usage doc since v6 for nginx moudle is not ready yet 2026-03-03 13:53:54 +08:00
Leon / 狮子的魂 0ad8031754
Merge pull request #455 from lionsoul2014/opt_missing_search_return
optimize the ptr checking and return empty string for missing data
2026-03-03 13:45:13 +08:00
lionsoul2014 fb0efface4 optimize the ptr checking and return empty string for missing data 2026-03-03 13:40:54 +08:00
lionsoul2014 e373f92312 maven version to 3.3.5 2026-02-28 15:26:06 +08:00
Leon / 狮子的魂 3d21a8247c
Merge pull request #454 from lionsoul2014/fr_java_config_builder_assert
assert the builder data source args to avoid errors like reported on…
2026-02-28 15:24:24 +08:00
lionsoul2014 d5e10e1383 assert the builder data source args to ignore errors like reported on issues/453 2026-02-28 15:22:54 +08:00
Leon / 狮子的魂 1a8d01d967
Merge pull request #452 from lionsoul2014/i18n_csharp_maker_readme
add english readme for c# maker
2026-02-26 14:26:15 +08:00
lionsoul2014 e87f580510 add english readme for c# maker 2026-02-26 14:24:40 +08:00
lionsoul2014 962335954a use xdb binary 2026-02-26 14:22:51 +08:00
Leon / 狮子的魂 2c01e01d2c
Merge pull request #451 from lionsoul2014/i18n_python_maker_readme
I18n python maker readme
2026-02-26 14:13:04 +08:00
lionsoul2014 79886f77e7 update the command title 2026-02-26 14:11:16 +08:00
lionsoul2014 132221b494 add english readme for python maker 2026-02-26 14:08:10 +08:00
lionsoul2014 aef4f91348 i18n readme for source ip editor 2026-02-26 13:55:34 +08:00
Leon / 狮子的魂 9d4b165409
Merge pull request #450 from lionsoul2014/i18n_cpp_maker_readme
add english readme for cpp maker
2026-02-25 23:19:51 +08:00
Leon / 狮子的魂 4b55d14fca
Merge pull request #449 from lionsoul2014/i18n_rust_maker_readme
add english readme for rust maker
2026-02-25 23:19:14 +08:00
lionsoul2014 b030661865 add english readme for cpp maker 2026-02-25 23:17:50 +08:00
lionsoul2014 537e6f9306 add english readme for rust maker 2026-02-25 23:13:29 +08:00
Leon / 狮子的魂 aaad26244d
Merge pull request #448 from lionsoul2014/i18n_java_maker_readme
add english readme for java maker
2026-02-25 23:05:52 +08:00
lionsoul2014 a31a966546 add english readme for java maker 2026-02-25 23:01:17 +08:00
Leon / 狮子的魂 832e3fd77d
Merge pull request #447 from lionsoul2014/i18n_golang_maker_readme
I18n golang maker readme
2026-02-25 22:46:43 +08:00
lionsoul2014 ef51b901ee link to the local lang readme 2026-02-25 22:45:44 +08:00
lionsoul2014 e8242a24ea add english readme for golang maker 2026-02-25 22:41:35 +08:00
Leon / 狮子的魂 30e5f2732d
Merge pull request #446 from lionsoul2014/i18n_cpp_readme
add english readme for c++ binding
2026-02-25 22:24:19 +08:00
lionsoul2014 1c6d299efa add english readme for c++ binding 2026-02-25 22:21:31 +08:00
Leon / 狮子的魂 dce0bb582f
Merge pull request #445 from lionsoul2014/i18n_erlang_readme
I18n erlang readme
2026-02-25 20:13:13 +08:00
lionsoul2014 b3115ba7aa adjust the layout 2026-02-25 20:12:14 +08:00
lionsoul2014 81885a16cf add english readme for erlang binding 2026-02-25 20:07:35 +08:00
lionsoul2014 2b61de20d6 package version to 3.1.7 with cn/en doc 2026-02-25 18:49:22 +08:00
lionsoul2014 7a1970729a python 3.0.3 with cn/en README doc 2026-02-25 14:12:16 +08:00
Leon / 狮子的魂 5a53add68b
Merge pull request #444 from lionsoul2014/i18n_javascript_readme
add english readme for javascript binding
2026-02-25 13:11:03 +08:00
lionsoul2014 c9e8acaffc add english readme for javascript binding 2026-02-25 13:07:05 +08:00
Leon / 狮子的魂 ce92aabc11
Merge pull request #443 from lionsoul2014/i18n_python_readme
add english readme for python binding
2026-02-24 16:12:13 +08:00
lionsoul2014 3f0480be1f add english readme for python binding 2026-02-24 16:09:57 +08:00
lionsoul2014 a694cfa8d2 add global emoji for supported lang list 2026-02-24 15:56:58 +08:00
Leon / 狮子的魂 8a44c98f99
Merge pull request #442 from lionsoul2014/i18n_rust_readme
add english readme for rust binding
2026-02-24 15:50:00 +08:00
lionsoul2014 cab8fbc4ab add english readme for rust binding 2026-02-24 15:47:33 +08:00
Leon / 狮子的魂 7d11be2ce7
Merge pull request #441 from lionsoul2014/i18n_lua_readme
I18n lua readme
2026-02-24 15:38:05 +08:00
lionsoul2014 8302525ae8 remove the lang label 2026-02-24 15:34:08 +08:00
lionsoul2014 e926501591 use the gloabl emoji only 2026-02-24 15:31:22 +08:00
lionsoul2014 87418d879a add english readme for pure lua binding 2026-02-24 15:29:41 +08:00
Leon / 狮子的魂 16fa5572f2
Merge pull request #440 from lionsoul2014/i18n_lua_c_readme
add english readme for lua c extension binding
2026-02-24 14:48:03 +08:00
lionsoul2014 39c297ad04 add english readme for lua c extension binding 2026-02-24 14:45:34 +08:00
Leon / 狮子的魂 6081603bd8
Merge pull request #439 from lionsoul2014/i18n_c_readme
add english readme for c binding
2026-02-24 13:43:14 +08:00
lionsoul2014 2861abcb83 add english readme for c binding 2026-02-24 13:40:09 +08:00
Leon / 狮子的魂 daf54bf877
Merge pull request #438 from lionsoul2014/i18n_java_readme
add english readme for java binding
2026-02-24 12:28:14 +08:00
lionsoul2014 6c98b8faf9 add english readme for java binding 2026-02-24 12:22:27 +08:00
Leon / 狮子的魂 1dc0003661
Merge pull request #437 from lionsoul2014/i18n_php_readme
I18n php readme
2026-02-24 11:58:00 +08:00
lionsoul2014 c4a3a2e1f8 add en README for php binding 2026-02-24 11:54:18 +08:00
lionsoul2014 0e828bba0d lang navi 2026-02-24 11:54:06 +08:00
lionsoul2014 b0ab8a0b2f update the README title 2026-02-23 21:23:23 +08:00
Leon / 狮子的魂 9caef15329
Merge pull request #436 from lionsoul2014/i18n_golang_readme
I18n golang readme
2026-02-23 19:32:27 +08:00
lionsoul2014 572fda5910 remove the tailing empty line 2026-02-23 19:28:06 +08:00
lionsoul2014 67d709ab39 add en README and golang binding 2026-02-23 19:22:48 +08:00
lionsoul2014 076f3918fa Optimize the layout and headings 2026-02-22 21:31:22 +08:00
lionsoul2014 9411b7f8c2 stdlize the en table header 2026-02-22 21:24:04 +08:00
lionsoul2014 75c375d146 update the paragraph title 2026-02-22 21:19:36 +08:00
lionsoul2014 5037816f41 set en README as default 2026-02-22 21:16:47 +08:00
Leon / 狮子的魂 aafee317c8
Merge pull request #435 from lionsoul2014/i18n_readme
I18n supporting with zh and en README
2026-02-22 19:13:21 +08:00
lionsoul2014 839d5f59a5 active the en README 2026-02-22 19:11:00 +08:00
lionsoul2014 d4180c0f57 zh as the default lang 2026-02-22 19:09:08 +08:00
lionsoul2014 8da3f55092 take en as the default desc lang 2026-02-22 19:03:42 +08:00
lionsoul2014 7ff7e5f61f add en and zh readme 2026-02-22 18:53:33 +08:00
Leon / 狮子的魂 44fd27f027
Merge pull request #434 from ArgoZhang/test-ip
test: update tests and make all of them passed
2026-02-12 20:11:50 +08:00
Argo Zhang 3539f5e4e3 chore: bump version 3.0.1 2026-02-12 13:32:57 +08:00
Argo Zhang 7bb61ec59c test: 更新单元测试 2026-02-12 11:04:18 +08:00
lionsoul2014 89852a70f0 special chars filter sample 2026-02-10 15:13:48 +08:00
lionsoul2014 d427b4dcd8 fix the doc desc errors 2026-02-02 19:02:55 +08:00
lionsoul2014 ad4253dbb0 update the demo tests result with the latest data 2026-01-28 22:48:03 +08:00
lionsoul2014 e6e3087144 source data clear based supports 2026-01-28 20:02:58 +08:00
lionsoul2014 ad0cfd6bd9 pack the ftell & fseek for later large file supports 2026-01-27 18:56:04 +08:00
lionsoul2014 0cf4cf8e26 fix the typo 2026-01-27 15:55:20 +08:00
Leon / 狮子的魂 ba2c9ec119
Merge pull request #430 from lionsoul2014/reserved_region_stdlize
stdlize the Reserved IP region info
2026-01-25 19:28:42 +08:00
lionsoul2014 91f281d53c stdlize the Reserved IP region info 2026-01-25 19:17:19 +08:00
lionsoul2014 05158e0a2e search tests result updated with the latest data 2026-01-25 18:56:46 +08:00
lionsoul2014 384567f184 Merge branch 'master' of github.com:lionsoul2014/ip2region 2026-01-25 14:49:41 +08:00
lionsoul2014 937c0772ec exp the iso-3166-alpha2-code 2026-01-25 14:48:25 +08:00
Leon / 狮子的魂 ebc6253dfe
Merge pull request #429 from lionsoul2014/update_readme_demo_tests_result
Update readme demo tests result
2026-01-25 14:46:14 +08:00
lionsoul2014 969a5f0e03 tests with output in Chinese and English 2026-01-25 14:44:31 +08:00
lionsoul2014 c2bb78aecd update the demo tests result with the latest data 2026-01-25 14:37:50 +08:00
lionsoul2014 e139731d9e update the demo tests to the latest result 2026-01-25 14:23:09 +08:00
lionsoul2014 96717ab155 update to the latest region format country|province|city|ISP|iso-3166-alpha2-code 2026-01-25 13:24:02 +08:00
Leon / 狮子的魂 0520f299ee
Merge pull request #428 from lionsoul2014/v6_updates_on_202601
V6 updates on 2026/01 with overseas region info updated and published in English ...
2026-01-25 13:14:10 +08:00
Leon / 狮子的魂 bbe2dddecf
Merge pull request #427 from lionsoul2014/v4_updates_on_202601
ipv4 data update on 2026/01 (overseas region update and with english,…
2026-01-25 13:13:46 +08:00
lionsoul2014 14753ffc29 re-gen the ipv6 xdb binary 2026-01-25 13:12:30 +08:00
lionsoul2014 3127ac0cdc ipv6 data updates on 2026/01 with overseas region info update and published in English 2026-01-25 13:08:36 +08:00
lionsoul2014 108ef78a51 ipv4 data update on 2026/01 (overseas region update and with english, add ISO-3166 alpha2 code field) 2026-01-25 12:55:54 +08:00
Leon / 狮子的魂 f2c988287c
Merge pull request #424 from lionsoul2014/merge_updates_20260121
merge the update reports from the communitiy
2026-01-21 16:14:38 +08:00
lionsoul2014 7bab822a49 merge the update reports from the communitiy 2026-01-21 16:11:33 +08:00
Leon / 狮子的魂 1df8b55789
Merge pull request #423 from lionsoul2014/fix_cn_special_administrative_region
fix the name of the CN special administrative region
2026-01-21 15:50:44 +08:00
lionsoul2014 68cd1053d2 fix the name of the cnc special administrative region 2026-01-21 15:46:11 +08:00
lionsoul2014 398149f17e update the desc for ip2region.close and builder.setCacheSliceBytes 2026-01-09 11:30:43 +08:00
Leon / 狮子的魂 4c569581b8
Merge pull request #419 from lionsoul2014/ipv4_region_fix_20260108
updated 908 segments based on the reports from community
2026-01-08 12:23:14 +08:00
Leon / 狮子的魂 3d876f895e
Merge pull request #418 from lionsoul2014/fix_ipv6_municipality_name
stdlize the municipality name
2026-01-08 12:21:59 +08:00
lionsoul2014 0863e172eb update 908 segments based on the reports from community 2026-01-08 12:19:42 +08:00
lionsoul2014 437a2c424a stdlize the municipality name 2026-01-08 11:28:58 +08:00
Leon / 狮子的魂 2cb7863112
Merge pull request #417 from lionsoul2014/ipv6_source_isp_stdlize
remove the scene and replace it with stdlized isp for ipv6 data
2026-01-07 16:00:14 +08:00
Leon / 狮子的魂 249ea17178
Merge pull request #416 from lionsoul2014/ipv4_source_isp_stdlize
ipv4 source isp name stdlize
2026-01-07 15:57:50 +08:00
lionsoul2014 697eabc33b remove the scene and replace it with stdlized isp 2026-01-07 15:50:24 +08:00
lionsoul2014 930924bb34 stdlize the isp name 2026-01-07 15:44:21 +08:00
lionsoul2014 7839a335ed update the region for 103.151.173.0-255 2026-01-07 11:50:02 +08:00
Leon / 狮子的魂 91816b108b
Merge pull request #415 from lionsoul2014/fix_ipv4_city_260107
update the city name with the latest administrative info
2026-01-07 11:39:55 +08:00
lionsoul2014 31dabf8c95 update the city name for some lines with the latest administrative info 2026-01-07 11:36:06 +08:00
lionsoul2014 7628ba32c2 update the POM verison to 3.3.4 2026-01-05 22:00:02 +08:00
lionsoul2014 4577e9a17b update the default sliceBytes 2026-01-05 21:59:38 +08:00
lionsoul2014 f302c68012 to README 2025-12-31 22:04:07 +08:00
Leon / 狮子的魂 fe2dfc1088
Merge pull request #413 from 4ba-Co/fix/412-peak-memory-usage
fix: Correct abnormal memory spikes
2025-12-31 21:53:00 +08:00
vccddd 93f7652594 fix: Correct abnormal memory spikes
ContentCacheStrategy now implements ICacheStrategy only and load data from files directly
2025-12-31 13:34:09 +08:00
lionsoul2014 0d152d91a5 usually we don't need to call setCacheSliceBytes 2025-12-29 19:42:00 +08:00
Leon / 狮子的魂 351be412e6
Merge pull request #411 from lionsoul2014/fr_java_fix_slice_bytes
Fr java fixed slice bytes
2025-12-29 19:39:32 +08:00
lionsoul2014 bd4a2d6bd1 pom verison 3.3.3 with fixed slice bytes supports 2025-12-29 19:36:19 +08:00
lionsoul2014 bf76adad4e content loads with fixed slice bytes tests 2025-12-29 19:36:02 +08:00
lionsoul2014 02e10db478 take the ip parse for costs stats 2025-12-29 19:35:43 +08:00
lionsoul2014 ad08637d21 use the fixed slice bytes for all content loads 2025-12-29 19:34:48 +08:00
lionsoul2014 245bbaa366 fixed sliceBytes supports 2025-12-29 19:34:12 +08:00
lionsoul2014 56606de2f6 update the fields list doc link 2025-12-26 12:19:27 +08:00
lionsoul2014 de6089e321 pom version upgrade to 3.3.2 2025-12-24 13:04:32 +08:00
lionsoul2014 37f5e4475c doc about sliceBytes setting 2025-12-24 13:04:14 +08:00
lionsoul2014 1f850dcde4 add #setCacheSliceBytes and tests 2025-12-24 13:01:52 +08:00
lionsoul2014 7f9e6b8586 loadContentXXX methods with sliceBytes supports 2025-12-24 13:01:07 +08:00
lionsoul2014 b0de60f6cb update the doc for concurrency safety search 2025-12-23 12:43:37 +08:00
lionsoul2014 f12b9765b3 Buffer tests for loaded with InputStream 2025-12-15 17:40:00 +08:00
lionsoul2014 8c20994b12 add #size() to return the internal buffs size 2025-12-15 17:39:30 +08:00
lionsoul2014 897af4532e update the usage desc for setXdbInputStream 2025-12-12 15:14:58 +08:00
Leon / 狮子的魂 9669585216
Merge pull request #408 from lionsoul2014/fr_java_easier_to_build
Ip2Region Config build from xdb File Object or xdb InputStream
2025-12-12 14:55:12 +08:00
lionsoul2014 e84ea900d4 update pom version to 3.3.1 2025-12-12 14:49:26 +08:00
lionsoul2014 450fce589c Config build tests from XdbFile and XdbInputStream 2025-12-12 14:49:05 +08:00
lionsoul2014 9b0bedc045 remove the space 2025-12-12 14:48:20 +08:00
lionsoul2014 6059d63504 build from InputStream supports 2025-12-12 14:29:09 +08:00
lionsoul2014 e11a390afc content/vectorIndex/verify from Buffer 2025-12-12 14:28:50 +08:00
lionsoul2014 ff75ddbcfd verify from xdb File/Path 2025-12-12 12:16:59 +08:00
lionsoul2014 ea297041dd load from xdb File and xdb Path 2025-12-12 12:15:21 +08:00
lionsoul2014 c68cc0097c xdbFile is null check 2025-12-12 11:58:03 +08:00
lionsoul2014 c2a0a55005 config build from xdb path and File supports 2025-12-12 11:50:05 +08:00
lionsoul2014 b52555c5ec xdbPath to xdbFile and with path and File create supports 2025-12-12 11:49:10 +08:00
lionsoul2014 cb9ed02bc6 adjust the data to std format 2025-12-10 10:59:11 +08:00
lionsoul2014 d4b8f9f841 go is ready for concurrent safe search 2025-12-07 19:51:01 +08:00
lionsoul2014 7f454276a1 remove the err declare 2025-12-07 00:45:01 +08:00
Leon / 狮子的魂 f80830f666
Merge pull request #407 from lionsoul2014/golang_test_with_ip2region_service
Golang search test driven by `Ip2Region` service
2025-12-07 00:43:08 +08:00
lionsoul2014 2fbf899140 title the bench blocks 2025-12-07 00:41:28 +08:00
lionsoul2014 72912301ee bold the service use tips 2025-12-07 00:39:45 +08:00
lionsoul2014 6531f3970f search test driven by ip2region service 2025-12-07 00:38:14 +08:00
lionsoul2014 b44654230f add cache policy parse func 2025-12-07 00:38:00 +08:00
Leon / 狮子的魂 1b9fb0c95c
Merge pull request #406 from lionsoul2014/java_app_with_ip2region_service
Java search test driven by ip2region service
2025-12-06 12:42:27 +08:00
lionsoul2014 242bff6208 pom version set to 2025-12-06 12:38:37 +08:00
lionsoul2014 a1c536ba98 Update the Doc for search tests drived by Ip2Region service 2025-12-06 12:38:02 +08:00
lionsoul2014 2454568203 search test driver change to Ip2Region service 2025-12-06 12:24:23 +08:00
lionsoul2014 48dce3545e parse cache policy from name 2025-12-06 12:23:52 +08:00
Leon / 狮子的魂 dde56752af
Merge pull request #404 from lionsoul2014/fr_java_service
Package all the classes into the service package.
2025-12-05 23:50:51 +08:00
lionsoul2014 d9ce5da85f track the total searches and display the avg took 2025-12-05 23:45:38 +08:00
lionsoul2014 dae949b8ce pom version to 3.2.2 2025-12-05 23:37:10 +08:00
lionsoul2014 54628265b3 pack all the service class to service package 2025-12-05 23:33:23 +08:00
lionsoul2014 9d67e3fcad service.NewIp2Region 2025-12-05 23:18:06 +08:00
lionsoul2014 bbccbfa451 add Usage doc for Ip2Region service 2025-12-05 23:15:29 +08:00
lionsoul2014 1f8bf73a0f ip2region search service & tests 2025-12-05 18:51:44 +08:00
lionsoul2014 620646e8e0 stdlize the code style 2025-12-05 18:51:23 +08:00
lionsoul2014 1adc5643aa Add IP Add Sub funcs 2025-12-05 18:50:46 +08:00
lionsoul2014 316355a72e remove the Stat 2025-12-05 12:49:14 +08:00
lionsoul2014 e73321a860 searcher pool is ready 2025-12-05 12:42:45 +08:00
lionsoul2014 ab22a5e03c config is ready 2025-12-04 14:15:48 +08:00
lionsoul2014 ace0c7f300 update String format 2025-12-04 14:14:40 +08:00
lionsoul2014 2a8e3444d4 searcher pool impl 2025-12-04 13:14:03 +08:00
lionsoul2014 4c9ef35a9a Add string method 2025-12-04 13:13:49 +08:00
lionsoul2014 540831e6a9 add String method 2025-12-04 13:13:10 +08:00
lionsoul2014 a220db2345 export the NewSearcher 2025-12-04 13:12:50 +08:00
lionsoul2014 619b6645a5 maven pom version 3.2.1 2025-12-04 11:11:41 +08:00
lionsoul2014 40c6c2c1bd maven pom version 3.2.1 2025-12-04 11:10:10 +08:00
lionsoul2014 89bb8cb709 Align the comments 2025-12-04 10:59:47 +08:00
狮子的魂 e64864aa40
!21 chore: fix the typo to (Seacher to Searcher)
Merge pull request !21 from 酒饮三酌/master
2025-12-04 02:54:43 +00:00
lionsoul2014 c78ba4827d service config 2025-12-03 13:36:49 +08:00
libo a3d1ba5d30 chore: 调整拼写错误单词Seacher->Searcher 2025-12-03 11:51:44 +08:00
lionsoul2014 000e19d419 disassemble the header info 2025-12-03 11:47:32 +08:00
lionsoul2014 b7f2ffa285 update the concurrent test with more tests and assert check 2025-12-02 10:37:13 +08:00
lionsoul2014 12c94cf0b6 Doc update 2025-12-01 13:53:52 +08:00
lionsoul2014 3f84cd41c1 Merge branch 'master' of gitee.com:lionsoul/ip2region 2025-12-01 13:51:54 +08:00
lionsoul2014 9669237830 update the Concurrency Safety Desc 2025-12-01 13:51:06 +08:00
lionsoul2014 8cecf2f3aa update the Update Concurrency Safety Desc 2025-12-01 13:41:37 +08:00
lionsoul2014 ab16071797 update the remarks for ip2region service 2025-12-01 13:06:34 +08:00
lionsoul2014 4a4c8de636 Merge branch 'fr_java_service' 2025-12-01 13:03:50 +08:00
lionsoul2014 942f3d9dbd java ip2region search service is ready 2025-12-01 13:03:11 +08:00
Leon / 狮子的魂 a383e4dd17
Merge pull request #403 from liuyunbin/cpp-ipv6
refactor and support ipv6 for binding maker and editor use C++
2025-12-01 11:18:09 +08:00
lionsoul2014 b5ae94b36d ip2region service with thread safe and auto ip version detect supports 2025-11-28 16:34:48 +08:00
lionsoul2014 efbbb576ee SearcherApp to SearcherTest 2025-11-28 16:34:06 +08:00
lionsoul2014 5c5c0ab160 add ipAddOne and ipSubOne impls 2025-11-28 16:33:47 +08:00
lionsoul2014 7f2d1ae789 SearcherApp to SearcherTest 2025-11-28 16:33:25 +08:00
Yunbin Liu a91c8e9747 refactor and support ipv6 for binding maker and editor use C++ 2025-11-28 08:19:56 +00:00
lionsoul2014 f8a154aeda searcher pool and tests 2025-11-27 15:11:37 +08:00
lionsoul2014 a8087e06b7 searcher pool init 2025-11-25 15:10:21 +08:00
lionsoul2014 317a900077 add vIndex & cBuffer fields and auto build 2025-11-25 15:03:31 +08:00
Leon / 狮子的魂 096d68ea77
Merge pull request #401 from ArgoZhang/dev-net
fix(NET): refactor GetData method
2025-11-24 16:07:01 +08:00
Argo Zhang 23ae923cff doc: 更新 CHANGELOG 文件 2025-11-22 18:49:57 +08:00
Argo Zhang c0f0efdb95 chore: 更新版本号为 3.0.0 2025-11-22 18:49:40 +08:00
Argo Zhang b3c2d40588 doc: 压力测试结果 2025-11-22 13:10:24 +08:00
Argo Zhang 7612e679fb feat: 增加 IDispose 接口保证释放文件句柄 2025-11-22 13:04:38 +08:00
Argo Zhang 5b8b47df5e perf: 增加预热方法 2025-11-22 12:53:22 +08:00
Argo Zhang 0b3635b3c3 refactor: 使用接口代替虚类 2025-11-22 12:53:02 +08:00
Argo Zhang 33f8bf766d test: 更新单元测试 2025-11-22 09:14:34 +08:00
Argo Zhang 0b3d794738 refactor: Search(uint) 方法标记过期 2025-11-22 09:14:28 +08:00
lionsoul2014 613501fc00 simplify the js desc 2025-11-21 22:47:38 +08:00
lionsoul2014 e95452c1c8 Merge branch 'master' of github.com:lionsoul2014/ip2region 2025-11-21 22:44:41 +08:00
lionsoul2014 3ad925406b c# is ready for IPv6 2025-11-21 22:42:49 +08:00
Leon / 狮子的魂 6e4af36dc9
Merge pull request #399 from ArgoZhang/feat-ipV6
IPv6 supports for C# binding
2025-11-21 22:37:39 +08:00
Argo Zhang d1151accde doc: 增加支持框架列表文档增加 NET10 支持 2025-11-21 17:09:31 +08:00
Argo Zhang 2c9ba9c56c doc: 增加基准测试结果 2025-11-21 17:05:22 +08:00
Argo Zhang 85144e1c40 refactor: 代码重构 2025-11-21 17:05:13 +08:00
Argo Zhang 426038b3fe refactor: 重构代码 2025-11-21 15:46:09 +08:00
lionsoul2014 2c7b170637 still working on the solutions 2025-11-21 14:49:31 +08:00
Argo Zhang 14d2332483 doc: 更新头文件 2025-11-21 13:14:42 +08:00
Argo Zhang ad84da7d3c test: 更新单元测试 2025-11-21 12:38:10 +08:00
Argo Zhang 9ecca28fc2 refactor: 更新时差问题 2025-11-21 12:37:47 +08:00
Argo Zhang 4ea2e7360d chore: 增加压力测试 2025-11-21 12:25:24 +08:00
Argo Zhang edc2559489 test: 更新单元测试 2025-11-21 12:25:13 +08:00
Argo Zhang 7bd81fecbf chore: 增加 net10 支持 2025-11-21 12:24:49 +08:00
Argo Zhang 6684d0a928 perf: 优化缓存策略 2025-11-21 12:24:32 +08:00
Argo Zhang 30fa54a83f perf: 更新基准测试 2025-11-21 11:27:51 +08:00
Argo Zhang ca7fc93953 test: 增加单元测试提高代码覆盖率 2025-11-21 11:18:37 +08:00
Argo Zhang db50f7b0da test: 增加单元测试覆盖向量缓存逻辑 2025-11-21 11:15:46 +08:00
Argo Zhang 5cfd5f8242 perf: 增加向量缓存逻辑提高性能 2025-11-21 11:15:31 +08:00
Argo Zhang 87b69e6b5f perf: 提高 Content 缓存模式性能 2025-11-21 09:42:59 +08:00
Argo Zhang 002c482db3 doc: 文档统一格式化 2025-11-21 09:03:37 +08:00
Argo Zhang 75d944eaf7 doc: 增加类注释 2025-11-21 08:52:17 +08:00
Argo Zhang 92cc3fd7cb chore: 增加 editorconfig 配置文件 2025-11-21 08:52:07 +08:00
Argo Zhang f1d0e1bee0 chore: 更新 BenchMark 工程 2025-11-21 08:15:23 +08:00
Argo Zhang d7d06e2185 chore: 单元测试框架更改为 net10 2025-11-20 17:52:48 +08:00
Argo Zhang 55a493fd8e chore: bump version 3.0.0 2025-11-20 17:51:13 +08:00
Argo Zhang f03861162f test: 增加全数据验证单元测试 2025-11-20 17:50:33 +08:00
Argo Zhang cbf8f9bfa8 test: 增加单元测试 2025-11-20 17:49:57 +08:00
Argo Zhang 3aa732d1ce test: 增加单元测试 2025-11-20 17:31:21 +08:00
Argo Zhang 9ef1fc34ac test: 增加单元测试提高代码覆盖率 2025-11-20 17:31:13 +08:00
Argo Zhang 62ab160572 refactor: 精简代码 2025-11-20 17:30:57 +08:00
Argo Zhang 87666aa353 test: 更新单元测试 2025-11-20 16:25:38 +08:00
Argo Zhang a2778f9b98 refactor: 增加边界数据支持 2025-11-20 16:25:25 +08:00
Argo Zhang af82b7e18e feat: 增加 XdbVersion 结构体与读取方法 2025-11-20 15:45:39 +08:00
lionsoul2014 a6cb5161ff config and config builder is ready 2025-11-20 15:40:54 +08:00
Argo Zhang d4d211c50b test: 更新单元测试 2025-11-20 15:26:30 +08:00
Argo Zhang 31cd078455 refactor: 兼容 netstander 框架 2025-11-20 15:26:12 +08:00
Argo Zhang 499e091761 feat: 增加 IPv6 算法支持 2025-11-20 15:22:21 +08:00
Argo Zhang 4f39707beb refactor: 更改为 XdbVersion 2025-11-20 15:22:04 +08:00
Argo Zhang 7fab19c46f chore: 更新逻辑 2025-11-19 18:01:38 +08:00
lionsoul2014 bd30b77d54 fix the typo 2025-11-13 09:39:23 +08:00
lionsoul2014 cd78ff2797 add min version supports desc 2025-11-05 12:23:43 +08:00
lionsoul2014 8692309c5b fix the typo to util.load_xxx 2025-11-05 10:59:12 +08:00
lionsoul2014 aa5437c637 3.0.2 with python >= 3.7 supports 2025-11-05 10:35:53 +08:00
Argo Zhang 8573f2f04e feat: 增加 Version 适配 ipV6 2025-11-04 15:45:38 +08:00
lionsoul2014 6eee95967b add tips for compile & usage verson match 2025-11-04 15:02:35 +08:00
Argo Zhang ef7ac3c088 refactor: 更新单元测试 2025-11-04 14:58:04 +08:00
lionsoul2014 4b7172766c lua 5.1/5.2/5.3/5.4 supports 2025-11-04 14:36:20 +08:00
lionsoul2014 dc64c27abd c99 std supports 2025-11-02 23:03:07 +08:00
lionsoul2014 1da81ffc02 API define optimize for C99 std supports 2025-11-02 22:54:44 +08:00
lionsoul2014 ae94b2dac3 version 3.0.1 with License classifier removed 2025-11-02 22:18:36 +08:00
lionsoul2014 f8f01ecd13 use μs 2025-11-02 22:17:47 +08:00
lionsoul2014 db95055a56 μs for elapsed 2025-11-02 21:43:54 +08:00
lionsoul2014 32a755ac3f use the py-ip2region as the pypi package name 2025-11-02 18:34:21 +08:00
lionsoul2014 784f336646 add keywords 2025-11-01 23:02:49 +08:00
lionsoul2014 732d4eb945 ip2region 3.0.0 and pip install desc 2025-10-31 23:41:35 +08:00
lionsoul2014 e753437afc include the bench search test script 2025-10-31 23:41:00 +08:00
lionsoul2014 ab0c39b7cd add ip2region pip install desc 2025-10-31 23:40:38 +08:00
lionsoul2014 db3b9b22ae ip2region package for pypi 2025-10-31 16:12:36 +08:00
lionsoul2014 91e542de38 python is ready for IPv6 2025-10-31 11:58:47 +08:00
lionsoul2014 bec072cd04 add ReadMe & python is ready for IPv6 2025-10-31 11:56:29 +08:00
lionsoul2014 bf0ded1171 search with ip_string for 3-kinds-of cache-policy searcher 2025-10-31 11:52:17 +08:00
lionsoul2014 c4113e5eae xdb bench script and bench test passed 2025-10-30 23:20:03 +08:00
lionsoul2014 731269ebad .3f format the took 2025-10-30 23:19:38 +08:00
lionsoul2014 9797b595ea strip the input 2025-10-30 23:18:03 +08:00
lionsoul2014 3ea717043c fix the bug of buffer read 2025-10-30 23:17:43 +08:00
lionsoul2014 42b9cebb9d call the ip_compare args func 2025-10-30 22:23:57 +08:00
lionsoul2014 9e2ca6bd98 ignore __pycache__ 2025-10-30 22:17:41 +08:00
lionsoul2014 0422ed3f4f to run_search_test 2025-10-30 15:34:33 +08:00
lionsoul2014 7f5844ca94 xdb searcher and searcher test script 2025-10-30 15:33:26 +08:00
lionsoul2014 cc97bb80a2 fix the bug of ip_sub_compare call 2025-10-30 15:33:02 +08:00
lionsoul2014 d4d5525392 utils is ready and with unit tests passed 2025-10-30 11:37:19 +08:00
lionsoul2014 6c653f85cd multi-line the str format 2025-10-29 22:59:05 +08:00
lionsoul2014 47067dbe73 utils keep going and unit tests 2025-10-29 22:55:10 +08:00
lionsoul2014 f6213aaaa4 remove the old implmentation 2025-10-29 22:53:04 +08:00
lionsoul2014 3a3abdd01c python ipv6 supporting 2025-10-29 16:03:19 +08:00
lionsoul2014 bbf5df5601 fix the typo 2025-10-27 21:48:25 +08:00
lionsoul2014 0f2260d116 warnings fixed 2025-10-27 16:00:37 +08:00
lion 572aa7b76a update the default xdb path 2025-10-24 12:49:00 +08:00
lion 580d1bb431 update the default xdb path to the latest xdb 3.0 version 2025-10-24 12:47:34 +08:00
lion 029a5578aa remove the un-used stats sub-cmd 2025-10-23 14:26:52 +08:00
lion 501c3c086d seperate all the sub commands impl 2025-10-23 14:02:51 +08:00
狮子的魂 / Leon 08d3ef69b0
Merge pull request #392 from gongzhengyang/master
Fix: rust bind return empty string for not found IP
2025-10-21 14:47:55 +08:00
gongzhengyang 4ebfa61179 Fix: rust bind return empty string for not found IP 2025-10-21 14:35:18 +08:00
lion 8a7a899c64 fix the sample region info 2025-10-21 13:31:01 +08:00
lion f7ad4309f8 search api prototyp desc 2025-10-21 13:12:59 +08:00
lion d56230518a search api desc 2025-10-21 12:57:25 +08:00
lion 2a77acf1c0 return empty string for empty match 2025-10-21 12:57:14 +08:00
lion d5d0c15c8d syntax the api code with php 2025-10-21 11:20:44 +08:00
lion b3498197a8 return empty string for empty match 2025-10-21 11:14:58 +08:00
lion d10c57b598 search api desc 2025-10-21 11:14:22 +08:00
lion 7050da1a68 search api desc 2025-10-21 10:55:42 +08:00
lion bb3c845c81 search api desc & return empty string for empty match 2025-10-21 10:48:25 +08:00
lion ca6c0506e6 search api desc and return empty string for empty match 2025-10-21 10:40:34 +08:00
lion 79cc6c1b0d putInt2 to putUint16 and getInt2 to getUint16 2025-10-21 10:34:53 +08:00
lion bd69a255fd return empty string for empty match 2025-10-21 10:28:56 +08:00
lion 8f6effd8eb verify api and verify the xdb before search or bench test 2025-10-20 22:26:15 +08:00
lion d503206e14 3.1.5 release verison with third party package lists 2025-10-20 19:10:21 +08:00
lion b3383680f9 code format stdlize 2025-10-20 19:09:37 +08:00
lion 567224ca60 pure lua script is ready for IPv6 2025-10-20 19:01:30 +08:00
lion 541420b8df pure lua script is ready for IPv6 2025-10-20 18:59:39 +08:00
lion 6cb1da2794 search process forward 2025-10-20 15:37:51 +08:00
lion ec7cb6d3ed Merge branch 'master' into fr_lua_ipv6 2025-10-19 23:08:31 +08:00
lion a30d922225 lines merge 2025-10-19 23:07:00 +08:00
lion a1b2c21767 optimize the ipv6 bytes to string 2025-10-19 23:05:15 +08:00
lion 1e31b4b143 ip util functions is ready 2025-10-19 22:57:06 +08:00
lion baa9f62d6b Merge branch 'master' into fr_lua_ipv6 2025-10-19 18:42:52 +08:00
狮子的魂 / Leon 3b4a229918
Merge pull request #390 from ArgoZhang/feat-services
feat(CSharp): Add injection service extension method
2025-10-19 18:41:48 +08:00
Argo Zhang d362231ae8 test: 更新依赖到最新 2025-10-19 17:37:18 +08:00
Argo Zhang 1ad01ff98b doc: 更新 readme 文档 2025-10-19 17:37:09 +08:00
Argo Zhang 5deb6d6553 test: 更新依赖文件名 2025-10-19 17:36:57 +08:00
Argo Zhang 444b2036d1 chore: 更新基准测试文件名 2025-10-19 17:36:37 +08:00
Argo Zhang 35e505bd74 test: 更新单元测试依赖文件 2025-10-19 17:36:03 +08:00
Argo Zhang 2fe8032d8b chore: 更改项目框架为 net9 2025-10-19 17:35:37 +08:00
lion 69eb94dcd8 funcs to parse ip address (IPv4 and IPv6) 2025-10-19 17:26:17 +08:00
Argo Zhang 0ad3172f2e doc: 增加注释 2025-10-19 15:30:34 +08:00
Argo Zhang 2ac7655f62 chore: 更改依赖 2025-10-19 15:30:26 +08:00
Argo Zhang 6aab24fb88 refactor: 更改为 TryAddSingleton 防止重复注册 2025-10-19 15:24:39 +08:00
Argo Zhang b94123fee3 refactor: 重构代码 2025-10-19 15:19:00 +08:00
Argo Zhang 0caff12306 chore: 更新依赖包版本号为具体版本号 2025-10-19 15:16:39 +08:00
Argo Zhang 6ad1e8e2e2 test: 更新单元测试 xdb 文件路径 2025-10-19 15:16:23 +08:00
Argo Zhang 9e979efd7b refactor: 更新 link 路径 2025-10-19 15:11:58 +08:00
Argo Zhang f594838784 refactor: 更改扩展类名称 2025-10-19 15:09:06 +08:00
Argo Zhang 24dcb24e24 refactor: 增加 Keyed 逻辑 2025-10-19 15:08:16 +08:00
Argo Zhang ddb2b4ff1d Merge remote-tracking branch 'github.com/master' into feat-services 2025-10-19 14:57:44 +08:00
Argo Zhang 2662fb05c2 feat: 增加注入服务支持 2025-10-19 14:55:37 +08:00
Argo Zhang 7e4d37b1db refactor: 增加更多框架支持 2025-10-19 14:55:21 +08:00
Argo Zhang d1715c75f6 refactor: 更新 xdb 文件名 2025-10-19 14:54:49 +08:00
Argo Zhang c82d2e6117 chore: 增加 slnx 解决方案工程 2025-10-19 14:54:31 +08:00
lion ffb632c3d0 add third party js binding impl 2025-10-19 10:19:40 +08:00
lion 11f890f3a4 xdb base tech links 2025-10-19 09:55:47 +08:00
lion 87b6cd5247 update the social media desc 2025-10-19 09:53:24 +08:00
lion 61dc2af93d track the latest optimized ipv6 source and xdb 2025-10-17 12:43:54 +08:00
lion 413ccd32ce version 3.1.3 with empty match interception 2025-10-17 12:42:52 +08:00
lion e3b87326d2 empty match interception with null return 2025-10-17 12:35:10 +08:00
lion 5a51b75411 optimize the doc 2025-10-15 21:33:54 +08:00
lion a2b76d38a9 fix the duration stats 2025-10-15 13:39:18 +08:00
lion 8c7c83cf7f deprecated the old nodejs and typescript binding 2025-10-15 11:36:17 +08:00
lion fbadc7cca0 new javascript binding is Ready with IPv4 and IPv6 supported 2025-10-15 11:27:10 +08:00
lion f03d33e5fe publish the ip2region.js package and update the doc for it 2025-10-15 11:10:47 +08:00
lion c809c072bc add search test demo docs 2025-10-15 10:18:45 +08:00
lion 5c1522fce4 search test app is ready 2025-10-15 10:13:10 +08:00
lion a3eedc5438 rm unnecessary brackets 2025-10-15 10:12:52 +08:00
lion 4f8cdb05c9 remove the unused code 2025-10-15 09:30:32 +08:00
lion 008bbbe4d7 bench script is ready and bench test passed 2025-10-14 21:44:07 +08:00
lion d35a7b9120 bench script 2025-10-14 14:20:19 +08:00
lion 4d71b47e05 merge the tests to util or searcher 2025-10-14 11:25:12 +08:00
lion 40ea8a267f remove the debug checks 2025-10-13 22:35:51 +08:00
lion 2dcccf22a3 verify impl and verify the xdb files 2025-10-13 22:21:30 +08:00
lion e073e26f52 fix the bug of ipToString with tailing double colon 2025-10-13 18:42:20 +08:00
lion ed42fd0fba adjust the export order 2025-10-13 18:13:55 +08:00
lion 9959117d92 ReadMe for basic usage 2025-10-13 18:06:54 +08:00
lion 3cab1bae2e add ts type define and api exports 2025-10-13 15:28:30 +08:00
lion 5a8d7c4152 use the ESM standard 2025-10-13 14:35:45 +08:00
lion 4507c2f706 javascript is Ready for IPv6 2025-10-13 12:05:20 +08:00
lion ad78dbe547 searcher with ipv4 tests passed 2025-10-12 13:48:26 +08:00
lion e924a7c17d version ip compare is ready 2025-10-12 11:07:22 +08:00
lion 6c463282d0 merge the header/version to util.js 2025-10-11 23:57:55 +08:00
lion bb19c7110a mi = max-index 2025-10-11 23:45:05 +08:00
lion 5fce4eaa01 ipv6 parse and toString beta version 2025-10-11 23:43:37 +08:00
lion 01bd749d4a ipv6 parse and to string alpha version 2025-10-11 19:34:24 +08:00
lion 3693eb47a8 Merge branch 'master' into fr_javascript_ipv6 2025-10-11 15:53:47 +08:00
lion e39ac2d6d1 optimize the type define 2025-10-11 15:53:09 +08:00
lion d97967d565 ip parse and to string util implementing 2025-10-11 15:49:24 +08:00
lion 8650273a85 Merge branch 'master' into fr_javascript_ipv6 2025-10-11 14:45:17 +08:00
lion ccf7f323c2 header/vector/content load util funcs is ready 2025-10-11 13:28:20 +08:00
lion a930a17e39 fix the typo 2025-10-10 16:14:23 +08:00
lion 0a456283e6 list the ts-ip2region addon repo 2025-10-10 16:12:35 +08:00
lion d6345bc015 ignore the package-lock.json 2025-10-10 16:07:45 +08:00
lion 086250c666 javascript binding with IPv4 and IPv6 supports 2025-10-10 16:03:33 +08:00
lion 54a160e230 ignore the test_util binary 2025-10-10 10:31:22 +08:00
lion 2d71241a3b for msvc 2025-09-30 09:36:52 +08:00
lion 8a254e3e83 rust maker is ready for IPv6 2025-09-28 22:43:45 +08:00
狮子的魂 95f5fcfcdb
Merge pull request #383 from gongzhengyang/master
Add rust maker (with IPv4 and IPv6 supported)
2025-09-28 22:39:03 +08:00
gongzhengyang 2cd59657e1 Improve rust maker log output 2025-09-28 16:44:40 +08:00
gongzhengyang 1f1ad5aaf6 Style: fmt binding rust code 2025-09-28 15:51:54 +08:00
gongzhengyang e887c01615 Fix: remove maker rust unused deps 2025-09-28 15:46:37 +08:00
gongzhengyang df6004b42e Feat: add rust maker for ipv4 and ipv6 2025-09-28 15:27:11 +08:00
gongzhengyang 05c7c17af0 Docs: add bench result for rust binding in readme 2025-09-28 15:27:11 +08:00
lion a6c0a66a36 update the doc links 2025-09-28 14:14:47 +08:00
lion a137813509 enable all standard warnings & fix all the warnings 2025-09-25 22:24:01 +08:00
lion 0179edc3a8 auto manager the region buffer & remove the unused vars 2025-09-25 22:11:52 +08:00
lion 4ff67f168c optimize the region filtering to achieve higher consecutive segments merging rate 2025-09-25 11:17:09 +08:00
lion cb3329390a Allow empty region info 2025-09-24 15:56:40 +08:00
lion 93c5088591 rust is ready for IPv6 2025-09-24 15:05:48 +08:00
狮子的魂 1ac2113afc
Merge pull request #382 from gongzhengyang/master
Rust binding supports multi cache policies and IPv6
2025-09-24 15:03:32 +08:00
gongzhengyang 139972dfb8 Fix: rust code fmt 2025-09-24 14:55:08 +08:00
gongzhengyang 379ab6ce73 Fix: rust binding bench count error 2025-09-24 14:50:09 +08:00
gongzhengyang d3592eb7cd Feat: improve bench query code in rust example, add usage at Readme 2025-09-24 14:35:42 +08:00
gongzhengyang b6410e16d0 Docs: improve rust binding usage in Readme 2025-09-24 10:42:12 +08:00
gongzhengyang d7f4ef5a07 Feat: limit bench IPv6 range 2025-09-24 10:41:47 +08:00
gongzhengyang 7a4bd4ed2b Fix: binary search ip unneccessary repeat call 2025-09-24 10:40:53 +08:00
gongzhengyang a6931ce31b Docs: improve binding rust readme docs, basic usage 2025-09-23 19:51:56 +08:00
gongzhengyang 32e005e1e6 Feat: rust binding support IPv6 search 2025-09-23 19:51:51 +08:00
gongzhengyang b5b00aeb87 Feat: improve rust binding example read file 2025-09-23 19:51:44 +08:00
gongzhengyang b16fe21506 Feat: add file and vector index cache search 2025-09-23 19:51:34 +08:00
lion 931fb9e67b use the local stack region_buffer instead of heap allocation 2025-09-23 18:15:46 +08:00
lion 7576daa6cc empty input ignore 2025-09-23 11:09:23 +08:00
lion 75893f0331 include time.h for windows struture tm 2025-09-22 23:47:38 +08:00
lion 355c0d8673 default lua version to 5.4 2025-09-21 13:06:25 +08:00
lion 815b1fb54b lua c module is ready for IPv6 2025-09-21 13:02:42 +08:00
lion 29fa8e25ab lua c ext is ready for IPv6 2025-09-21 12:56:42 +08:00
lion 13324d1575 ignore the .vscode 2025-09-21 11:30:43 +08:00
lion 46f56f0eca remove the unused defines 2025-09-21 00:51:19 +08:00
lion 2fe0d1f59e Ipv6 search is ready 2025-09-21 00:45:45 +08:00
lion b01253e6b3 ip parse and binary ip bytes use 2025-09-20 17:29:11 +08:00
lion 6712ee9504 calling the latest xdb api 2025-09-20 14:37:22 +08:00
lion 5fa69300ad lua_c ipv6 supporting 2025-09-20 14:03:09 +08:00
lion 7e599e05a0 print the error tip if region_buffer init failed 2025-09-19 16:16:19 +08:00
lion a36a85558f c is ready for IPv6 search 2025-09-19 13:30:37 +08:00
lion 48c2b395bc c binding is ready for IPv6 2025-09-19 13:08:47 +08:00
lion dff3084b93 C binding is ready for IPv6 2025-09-18 23:09:19 +08:00
lion 8fbdf82797 IPv6 search and docs is ready 2025-09-18 15:53:48 +08:00
lion 73c8b20d3a apply the bytes ip to the whole search process 2025-09-17 23:10:42 +08:00
lion 69b9f062e4 directly compare the ip from the buffer 2025-09-17 14:45:03 +08:00
lion 1575e581cd try to apply the bytes_ip_t for the search 2025-09-17 14:43:28 +08:00
lion 4cff9eb6ee ip parse / to_string / compare util function is ready 2025-09-17 13:07:35 +08:00
lion 0fd0bc59d0 bytes ip utils prototype 2025-09-16 22:09:49 +08:00
lion c64837db67 ip util function update for IPv6 2025-09-16 15:29:55 +08:00
lion eb80b89c0e ipv6 supporting for clang 2025-09-16 14:46:34 +08:00
lion 9a14e32736 add xdb file verify util and call it before the search/bench test 2025-09-16 12:35:22 +08:00
lion 4c1b0362c8 fix the typo 2025-09-16 12:34:50 +08:00
lion 5d11d6c0fb php ipv6 supports is ready 2025-09-15 15:27:43 +08:00
lion 7bbe7d681d PHP binding IPv6 supports is ready 2025-09-15 15:22:49 +08:00
lion 95c129c7d1 PHP IPv6 supporting 2025-09-14 23:14:42 +08:00
lion 716326f4bc list the source files 2025-09-13 23:30:14 +08:00
lion d1e9b6e3bc Docs for the java maker with IPv6 supported 2025-09-13 00:37:51 +08:00
lion ae344d20aa Java maker and binding IPv6 supports is ready 2025-09-13 00:24:51 +08:00
lion 537077279a Java IPv6 support is ready 2025-09-13 00:21:25 +08:00
lion a91fdd38bc updatet the pom desc 2025-09-12 15:48:20 +08:00
lion 9af9c927fe IPv6 impl for java binding is ready 2025-09-12 15:43:37 +08:00
lion f226ef1f79 update the comments 2025-09-12 15:31:12 +08:00
lion e8b6049584 resource defer close and some optimizations 2025-09-12 12:52:15 +08:00
lion 6db9654c7d IPv6 supporting for Java searcher 2025-09-12 12:41:00 +08:00
lion c6c0ee954f fix the bug of region filtering 2025-09-12 09:44:34 +08:00
lion 4911bdcd41 maker is ready and with consecutive segments check and merge 2025-09-11 21:08:51 +08:00
lion 23b2446736 segment contains and max ptr confirm 2025-09-11 15:18:43 +08:00
lion 6727987960 java maker 3.0 with IPv6 supported 2025-09-11 15:06:10 +08:00
lion 629f509c6f use the unit test instead 2025-09-10 22:54:34 +08:00
lion 23b3c3bc9b fix the func desc 2025-09-10 22:53:53 +08:00
lion 1cf2e419e1 remove the unused imports 2025-09-10 22:48:44 +08:00
lion 697b46bb2e ip util functions and its unit test 2025-09-10 22:46:14 +08:00
lion 83fd149c59 ipv6 utils and testing 2025-09-10 15:38:15 +08:00
lion 87d53b5018 some optimizations 2025-09-09 22:34:13 +08:00
lion f0a9462041 JRE-1.8 at least 2025-09-09 15:58:35 +08:00
lion 6a729e40a5 update the desc about region format 2025-09-08 12:21:15 +08:00
lion fdec88605f update the cli search demo 2025-09-08 11:52:34 +08:00
347 changed files with 1213690 additions and 703365 deletions

2
.gitattributes vendored
View File

@ -1,2 +0,0 @@
data/ipv6_source.txt filter=lfs diff=lfs merge=lfs -text
data/ip2region_v6.xdb filter=lfs diff=lfs merge=lfs -text

27
.gitignore vendored
View File

@ -6,7 +6,6 @@
*.log *.log
*.la *.la
*.so *.so
*.xdb
*.iml *.iml
META-INF/ META-INF/
.DS_Store .DS_Store
@ -27,20 +26,20 @@ META-INF/
# vim swp file # # vim swp file #
*.swp *.swp
.idea .idea
.vscode
# binding # 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/classes/
/binding/java/doc/ /binding/java/doc/
/binding/java/target/ /binding/java/target/
/binding/java/*.jar /binding/java/*.jar
# python
/**/__pycache__
# clang # clang
/binding/c/xdb_searcher /binding/c/xdb_searcher
/binding/c/util_test /binding/c/test_util
/binding/c/cmake-build-debug /binding/c/cmake-build-debug
# lua/luc_c # lua/luc_c
@ -58,24 +57,25 @@ target
# VS ignore cases # VS ignore cases
/**/*.sln /**/*.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#/**/.vs/
/binding/c#/**/packages /binding/c#/**/packages
/binding/c#/**/bin /binding/c#/**/bin
/binding/c#/**/obj /binding/c#/**/obj
# Nodejs # Nodejs
/v1.0/binding/nodejs/tests/unitTests/__snapshots__
/v1.0/binding/nodejs/coverage
/binding/nodejs/tests/unitTests/__snapshots__ /binding/nodejs/tests/unitTests/__snapshots__
/binding/nodejs/coverage /binding/nodejs/coverage
/binding/nodejs/node_modules /binding/nodejs/node_modules
/binding/nodejs/.nyc_output /binding/nodejs/.nyc_output
/binging/nodejs/package-lock.json /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 # maker
## golang ## golang
/maker/golang/dbmaker /maker/golang/dbmaker
@ -89,3 +89,6 @@ target
#vscode #vscode
.vscode .vscode
build build
# git worktrees
.worktrees/

View File

@ -1,3 +1,11 @@
ip2region is dual-licensed under the Apache License 2.0 and the MIT License.
You may use, distribute, and modify this software under either license,
at your option. The full text of both licenses appears below.
SPDX-License-Identifier: Apache-2.0 OR MIT
==============================================================================
Apache License Apache License
Version 2.0, January 2004 Version 2.0, January 2004
http://www.apache.org/licenses/ http://www.apache.org/licenses/

125
README.md Normal file
View File

@ -0,0 +1,125 @@
:globe_with_meridians: [中文简体](README_zh.md) | [English](README.md)
# ip2region
[ip2region](https://ip2region.net) - is an offline IP address localization library and IP localization data management framework. It supports both `IPv4` and `IPv6` with query efficiency at the 10-microsecond level. It provides `xdb` data generation and query client implementations for many mainstream programming languages.
# Features
### 1. Offline Localization Library
The project itself provides raw data for both IPv4 (`data/ipv4_source.txt`) and IPv6 (`data/ipv6_source.txt`), along with corresponding xdb files (`data/ip2region_v4.xdb` and `data/ip2region_v6.xdb`) to achieve city-level query localization. The field format is: `Country|Province|City|ISP|iso-alpha2-code`. Localization information for China is entirely in Chinese, while regional information for non-China areas is entirely in English.
### 2. Data Management Framework
`xdb` supports hundreds of millions of IP data segment rows. Region information supports full customization. The region information of the built-in data is fixed in the format: `Country|Province|City|ISP|iso-alpha2-Code`. You can append data for specific business needs to the region, such as: GPS information/International Standard Regional Codes/Zip codes, etc. In other words, you can fully use ip2region to manage your own IP localization data.
### 3. Data Deduplication and Compression
The `xdb` format generation program automatically processes the input raw data, checks and completes the merging of adjacent IP segments, and performs deduplication and compression of identical regional information.
### 4. High-Speed Query Response
Even for queries based entirely on the `xdb` file, the single query response time is at the 10-microsecond level. Memory-accelerated queries can be enabled through the following two methods:
1. `vIndex` Index Caching: Uses a fixed `512KiB` of memory to cache vector index data, reducing one disk IO operation and maintaining average query efficiency within 100 microseconds.
2. Entire `xdb` File Caching: Loads the entire `xdb` file into memory. Memory usage is equal to the `xdb` file size. There is no disk IO operation, maintaining 10-microsecond level query efficiency.
### 5. Unified Query Interface
`xdb` provides version-compatible query implementations. A unified API can simultaneously provide queries for both IPv4 and IPv6 data and return unified data.
# `xdb` Query
For API introductions, usage documentation, and test programs, please refer to the README introduction under the corresponding `searcher` query client. All query binding implementations are as follows:
| Language | Description | IPv4 Support | IPv6 Support |
| --- | --- | --- | --- |
| [Golang](binding/golang/README.md) | golang query client | :white_check_mark: | :white_check_mark: |
| [PHP](binding/php/README.md) | php query client | :white_check_mark: | :white_check_mark: |
| [Java](binding/java/README.md) | java query client | :white_check_mark: | :white_check_mark: |
| [C](binding/c/README.md) | C[std=c99] query client | :white_check_mark: | :white_check_mark: |
| [Lua_c](binding/lua_c/README.md) | lua c extension query client | :white_check_mark: | :white_check_mark: |
| [Lua](binding/lua/README.md) | lua query client | :white_check_mark: | :white_check_mark: |
| [Rust](binding/rust/README.md) | rust query client | :white_check_mark: | :white_check_mark: |
| [Python](binding/python/README.md) | python query client | :white_check_mark: | :white_check_mark: |
| [Javascript](binding/javascript/README.md) | javascript query client | :white_check_mark: | :white_check_mark: |
| [Csharp](binding/csharp) | csharp query client | :white_check_mark: | :white_check_mark: |
| [Erlang](binding/erlang/README.md) | erlang query client | :white_check_mark: | :white_check_mark: |
| [Nginx](binding/nginx) | nginx extension query client | :white_check_mark: | :white_check_mark: |
| [C++](binding/cpp/README.md) | C++ query client | :white_check_mark: | :white_check_mark: |
| [Cangjie](binding/cangjie/README.md) | Cangjie query client | :white_check_mark: | :white_check_mark: |
The following toolchain implementations are contributed by community developers via third-party repositories:
| Language | Description |
| --- | --- |
| [ip2region-composer](https://github.com/zoujingli/ip2region) | php composer management client |
| [ip2region-ts](https://github.com/Steven-Qiang/ts-ip2region2) | node.js addon management client |
| [ruby-ip2region](https://github.com/jicheng1014/ruby-ip2region) | ruby xdb query client implementation |
| [Ip2regionTool](https://github.com/orestonce/Ip2regionTool) | ip2region data conversion tool |
# `xdb` Generation
For API introductions, usage documentation, and test programs, please refer to the README documents under the following `maker` generation programs:
| Language | Description | IPv4 Support | IPv6 Support |
| --- | --- | --- | --- |
| [Golang](maker/golang/README.md) | golang xdb generation program | :white_check_mark: | :white_check_mark: |
| [Java](maker/java/README.md) | java xdb generation program | :white_check_mark: | :white_check_mark: |
| [Python](maker/python/README.md) | python xdb generation program | :white_check_mark: | :x: |
| [Csharp](maker/csharp/README.md) | csharp xdb generation program | :white_check_mark: | :x: |
| [Rust](maker/rust/README.md) | rust xdb generation program | :white_check_mark: | :white_check_mark: |
| [C++](maker/cpp) | C++ xdb generation program | :white_check_mark: | :white_check_mark: |
# `xdb` Update
The core of the ip2region project lies in **researching the design and implementation of IP data storage and fast querying**. The raw data `./data/ipv4_source.txt` and `./data/ipv6_source.txt` included in the project are updated irregularly. For scenarios with high requirements for data accuracy and update frequency, it is recommended to purchase commercial offline data from the [Ip2Region Community](https://ip2region.net/products/offline) or third-party vendors. You can try to update the data yourself using the following methods:
### Manual Editing and Updating
You can modify the data yourself based on the raw IP data provided by ip2region in `./data/ipv4_source.txt` and `./data/ipv6_source.txt` using the editing tools provided by ip2region. Currently, the data sources include:
1. Data provided by the ip2region community (please refer to the official account at the bottom for community notifications)
2. Project Issues tagged with `[Data_Updates]`
3. Other custom data: e.g., data provided by customers, data obtained through GPS and WIFI positioning, or legal and compliant data from other platforms.
For instructions on using the raw IP data editing tools, please refer to the README documents under the following `maker` generation programs:
| Language | Description | IPv4 Support | IPv6 Support |
| --- | --- | --- | --- |
| [Golang](maker/golang/README.md#xdb-data-editing) | golang IP raw data editor | :white_check_mark: | :white_check_mark: |
| [C++](maker/cpp/README.md) | C++ IP raw data editor | :white_check_mark: | :white_check_mark: |
### Detection Automatic Update
If you want to update data via your own API or data source, you can refer to the update algorithm based on the "Detection Algorithm" shared in the following videos to write your own update program:
1. [Data Update Implementation Video Sharing - part1](https://www.bilibili.com/video/BV1934y1E7Q5/)
2. [Data Update Implementation Video Sharing - part2](https://www.bilibili.com/video/BV1pF411j7Aw/)
# Official Community
The Ip2Region official community was officially launched on `2025/06/12`. On one hand, it provides stable [commercial offline data](https://ip2region.net/products/offline) services. On the other hand, it facilitates the strengthening of the IP toolchain and data services outside the core code, such as [usage documentation](https://ip2region.net/doc/), [query testing](https://ip2region.net/search/demo), and data correction. For more information and services regarding the community, please visit the [Ip2Region Official Community](https://ip2region.net/).
# Related Remarks
### 1. xdb Technical Documents:
1. xdb Data Structure Analysis: ["ip2region xdb - Data Structure Description"](https://ip2region.net/doc/xdb/structure)
2. xdb Query Process Analysis: ["ip2region xdb - Query Process Description"](https://ip2region.net/doc/xdb/search)
3. xdb Generation Process Analysis: ["ip2region xdb - Generation Process Description"](https://ip2region.net/doc/xdb/generate)
4. xdb File Generation Tutorial: ["ip2region xdb - File Generation Tutorial"](https://ip2region.net/doc/data/xdb_make)
5. xdb Concurrent Safety Query: ["ip2region xdb - Concurrent Safety Query"](https://ip2region.net/doc/xdb/concurrent)
6. xdb Data Update Method: ["ip2region Data Update and Use of xdb Data Editor"](https://mp.weixin.qq.com/s/cZH5qIn4E5rQFy6N32RCzA)
### 3. Technical Information Blogs
1. WeChat Official Account - lionsoul-org, the author's active technical sharing channel
2. [Ip2Region Official Community](https://ip2region.net)

117
README_zh.md Normal file
View File

@ -0,0 +1,117 @@
:globe_with_meridians: [中文简体](README_zh.md) | [English](README.md)
# ip2region
[ip2region](https://ip2region.net) - 是一个离线IP地址定位库和IP定位数据管理框架同时支持 `IPv4``IPv6` 10微秒级别的查询效率提供了众多主流编程语言的 `xdb` 数据生成和查询客户端实现。
# 项目特性
### 1、离线定位库
项目本身同时了提供了一份 IPv4 (`data/ipv4_source.txt`) 和 IPv6 (`data/ipv6_source.txt`) 的原始数据和对应的 xdb 文件(`data/ip2region_v4.xdb` 和 `data/ip2region_v6.xdb`) 用于实现精确到城市的查询定位功能,字段格式为:`国家|省份|城市|ISP|iso-alpha2-code(国家两字母简称)`,中国的定位信息全部为中文,非中国地区的地域信息全部为英文。
### 2、数据管理框架
`xdb` 支持亿级别的 IP 数据段行数region 信息支持完全自定义,自带数据的 region 信息固定了格式为:`国家|省份|城市|ISP|iso-alpha2-Code`,你可以在 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/README_zh.md) | golang 查询客户端 | :white_check_mark: | :white_check_mark: |
| [PHP](binding/php/README_zh.md) | php 查询客户端 | :white_check_mark: | :white_check_mark: |
| [Java](binding/java/README_zh.md) | java 查询客户端 | :white_check_mark: | :white_check_mark: |
| [C](binding/c/README_zh.md) | C[std=c99] 查询客户端 | :white_check_mark: | :white_check_mark: |
| [Lua_c](binding/lua_c/README_zh.md) | lua c 扩展查询客户端 | :white_check_mark: | :white_check_mark: |
| [Lua](binding/lua/README_zh.md) | lua 查询客户端 | :white_check_mark: | :white_check_mark: |
| [Rust](binding/rust/README_zh.md) | rust 查询客户端 | :white_check_mark: | :white_check_mark: |
| [Python](binding/python/README_zh.md) | python 查询客户端 | :white_check_mark: | :white_check_mark: |
| [Javascript](binding/javascript/README_zh.md) | javascript 查询客户端 | :white_check_mark: | :white_check_mark: |
| [Csharp](binding/csharp) | csharp 查询客户端 | :white_check_mark: | :white_check_mark: |
| [Erlang](binding/erlang/README_zh.md) | erlang 查询客户端 | :white_check_mark: | :white_check_mark: |
| [Nginx](binding/nginx) | nginx 扩展查询客户端 | :white_check_mark: | :white_check_mark: |
| [C++](binding/cpp/README_zh.md) | C++ 查询客户端 | :white_check_mark: | :white_check_mark: |
| [Cangjie](binding/cangjie/README.md) | 仓颉 查询客户端 | :white_check_mark: | :white_check_mark: |
以下工具链实现由社区开发者通过第三方仓库贡献:
| 编程语言 | 描述 |
| --- | --- |
| [ip2region-composer](https://github.com/zoujingli/ip2region) | php composer 管理客户端 |
| [ip2region-ts](https://github.com/Steven-Qiang/ts-ip2region2) | node.js addon 管理客户端|
| [ruby-ip2region](https://github.com/jicheng1014/ruby-ip2region) | ruby xdb 查询客户端实现 |
| [Ip2regionTool](https://github.com/orestonce/Ip2regionTool) | ip2region 数据转换工具 |
# `xdb` 生成
API 介绍,使用文档和测试程序请参考如下 `maker` 生成程序下的 README 文档:
| 编程语言 | 描述 | IPv4 支持 | IPv6 支持 |
| --- | --- | --- | --- |
| [Golang](maker/golang/README_zh.md) | golang xdb 生成程序 | :white_check_mark: | :white_check_mark: |
| [Java](maker/java/README_zh.md) | java xdb 生成程序 | :white_check_mark: | :white_check_mark: |
| [Python](maker/python/README_zh.md) | python xdb 生成程序 | :white_check_mark: | :x: |
| [Csharp](maker/csharp/README_zh.md) | csharp xdb 生成程序 | :white_check_mark: | :x: |
| [Rust](maker/rust/README_zh.md) | rust xdb 生成程序 | :white_check_mark: | :white_check_mark: |
| [C++](maker/cpp) | C++ xdb 生成程序 | :white_check_mark: | :white_check_mark: |
# `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/README_zh.md#xdb-数据编辑) | golang IP 原始数据编辑器 | :white_check_mark: | :white_check_mark: |
| [C++](maker/cpp) | C++ IP 原始数据编辑器 | :white_check_mark: | :white_check_mark: |
### 检测自动更新
如果你想通过你自己的 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)

115
ReadMe.md
View File

@ -1,115 +0,0 @@
# Ip2region 是什么
[ip2region](https://ip2region.net) - 是一个离线IP地址定位库和IP定位数据管理框架同时支持`IPv4`和`IPv6`10微秒级别的查询效率提供了众多主流编程语言的 `xdb` 数据生成和查询客户端实现。
# Ip2region 特性
### 1、离线定位库
项目本身同时了提供了一份 IPv4 和 IPv6 的原始数据和对应的 xdb 文件(`ip2region_v4.xdb` 和 `ip2region_v6.xdb`) 用于实现精确到城市的的查询定位功能。
### 2、数据管理框架
`xdb` 支持亿级别的 IP 数据段行数,默认的 region 信息都固定了格式:`国家|省份|城市|ISP`缺省的地域信息默认是0。
region 信息支持完全自定义,例如:你可以在 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: | :soon: | [Lion](https://github.com/lionsoul2014) |
| [Java](binding/java) | java xdb 查询客户端 | :white_check_mark: | :soon: | [Lion](https://github.com/lionsoul2014) |
| [Lua](binding/lua) | lua xdb 查询客户端 | :white_check_mark: | :soon: | [Lion](https://github.com/lionsoul2014) |
| [ANSI C](binding/c) | ANSC c xdb 查询客户端 | :white_check_mark: | :soon: | [Lion](https://github.com/lionsoul2014) |
| [Lua_c](binding/lua_c) | lua c 扩展 xdb 查询客户端 | :white_check_mark: | :soon: | [Lion](https://github.com/lionsoul2014) |
| [Rust](binding/rust) | rust xdb 查询客户端 | :white_check_mark: | :x: | [gongzhengyang](https://github.com/gongzhengyang) |
| [Python](binding/python) | python xdb 查询客户端 | :white_check_mark: | :x: | [厉害的花花](https://github.com/luckydog6132) |
| [Nodejs](binding/nodejs) | nodejs xdb 查询客户端 | :white_check_mark: | :x: | [Wu Jian Ping](https://github.com/wujjpp) |
| [Csharp](binding/csharp) | csharp xdb 查询客户端 | :white_check_mark: | :x: | [Alen Lee](https://github.com/malus2077) |
| [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: | :x: | [Yunbin Liu](https://github.com/liuyunbin) |
| [Typescript](binding/typescript) | Typescript xdb 查询客户端 | :white_check_mark: | :x: | [Alan Lee](https://github.com/malus2077) |
以下工具链实现由社区开发者通过第三方仓库贡献:
| 编程语言 | 描述 | 贡献者 |
|:--------------------------------------------------------------- |:------------------------|:-----------------------------------------------|
| [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: | :soon: | [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: | :x: | [KevinWang](https://github.com/KevinWL) |
| [C++](maker/cpp) | C++ xdb 生成程序 | :white_check_mark: | :x: | [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: | :soon: | [Lion](https://github.com/lionsoul2014) |
| [C++](maker/cpp#xdb-数据编辑) | C++ IP 原始数据编辑器 | :white_check_mark: | :x: | [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 整个缓存的查询都 <b></b> 并发安全的,基于文件的查询都 <b>不是</b> 并发安全的实现,不同进程/线程/协程需要通过创建不同的查询对象来安全使用,并发量很大的情况下,基于文件查询的方式可能会导致打开文件数过多的错误,请修改内核的最大允许打开文件数(fs.file-max=一个更高的值)或者将整个xdb加载到内存进行安全并发使用。
### 2、技术资源分享
1. xdb 数据结构分析:[“ip2region xdb-数据结构描述“](https://ip2region.net/doc/xdb/ipv4_structure)
2. xdb 查询过程分析:[“ip2region xdb-查询过程描述”](https://ip2region.net/doc/xdb/ipv4_search)
3. xdb 生成过程分析:[“ip2region xdb-生成过程描述”](https://ip2region.net/doc/xdb/ipv4_generate)
4. xdb 文件生成教程:[“ip2region xdb-文件生成教程”](https://ip2region.net/doc/data/ipv4_xdb_make)
5. xdb 数据更新方法:[“ip2region 数据更新和 xdb 数据编辑器的使用”](https://mp.weixin.qq.com/s/cZH5qIn4E5rQFy6N32RCzA)
### 3、技术信息博客
1. [Ip2Region 官方社区](https://ip2region.net)
2. 请先关注微信公众号 lionsoul-org (狮子的魂)

View File

@ -1,24 +1,27 @@
all: xdb_searcher util_test all: xdb_searcher test_util
xdb_searcher: xdb_searcher.h xdb_searcher.c main.c xdb_searcher: xdb_api.h xdb_util.c xdb_searcher.c main.c
gcc -O2 -I./ xdb_searcher.c main.c -o xdb_searcher gcc -std=c99 -Wall -O2 -I./ xdb_util.c xdb_searcher.c main.c -o xdb_searcher
util_test: xdb_searcher.h xdb_searcher.c util_test.c test_util: xdb_api.h xdb_util.c test_util.c
gcc -O2 -I./ xdb_searcher.c util_test.c -o util_test gcc -std=c99 -Wall -O2 -I./ xdb_util.c test_util.c -o test_util
xdb_searcher.o: xdb_searcher.c xdb_searcher.o: xdb_searcher.c
gcc -c -o xdb_searcher.o xdb_searcher.c gcc -std=c99 -Wall -c xdb_searcher.c
xdb_searcher_lib: xdb_searcher.o 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/lib
mkdir -p build/include mkdir -p build/include
ar -rc build/lib/libxdb_searcher.a `find . -name *.o` ar -rc build/lib/libxdb.a `find . -name "*.o"`
cp xdb_searcher.h build/include cp xdb_api.h build/include
clean: clean:
find ./ -name \*.o | xargs rm -f find ./ -name \*.o | xargs rm -f
find ./ -name util_test | xargs rm -f find ./ -name test_util | xargs rm -f
find ./ -name xdb_searcher | xargs rm -f find ./ -name xdb_searcher | xargs rm -f
rm -rf build rm -rf build
.PHONY: all clean xdb_searcher util_test .PHONY: all clean xdb_searcher test_util

370
binding/c/README.md Normal file
View File

@ -0,0 +1,370 @@
:globe_with_meridians: [中文简体](README_zh.md) | [English](README.md)
# ip2region c Query Client
# Usage
### About Query API
The prototype of the Query API is as follows:
```c
// Query via string IP
int xdb_search_by_string(xdb_searcher_t *, const string_ip_t *, xdb_region_buffer_t *);
// Query via binary IP returned by xdb_parse_ip
int xdb_search(xdb_searcher_t *, const bytes_ip_t *, int, xdb_region_buffer_t *);
```
If the query fails, a non-`0` error code will be returned. If the query is successful, the `region` information string can be obtained from `xdb_region_buffer_t`. If the input IP cannot be found, `xdb_region_buffer_t` will receive an empty string `""`.
### About IPv4 and IPv6
This xdb query client implementation supports both IPv4 and IPv6 queries. The usage is as follows:
```c
#include "xdb_api.h";
// For IPv4: Set xdb path to the v4 xdb file, specify IP version as IPv4
const char *db_path = "../../data/ip2region_v4.xdb"; // or your ipv4 xdb path
xdb_version_t *version = XDB_IPv4;
// For IPv6: Set xdb path to the v6 xdb file, specify IP version as IPv6
const char *db_path = "../../data/ip2region_v6.xdb"; // or your ipv6 xdb path
xdb_version_t *version = XDB_IPv6;
// The IP version of the xdb specified by db_path must be consistent with the version, otherwise an error will occur during query execution
// Note: The following demonstration directly uses db_path and version variables
```
### XDB File Verification
It is recommended that you proactively verify the applicability of the xdb file, as some future new features may cause the current Searcher version to be incompatible with the xdb file you are using. Verification can avoid unpredictable errors during runtime. You do not need to verify every time; for example, verify when the service starts or manually call a command to confirm version matching. Do not run verification every time a Searcher is created, as this will affect query response speed, especially in high-concurrency scenarios.
```c
#include "xdb_api.h";
int errcode = xdb_verify(db_path);
if ($err != 0) {
// Applicability verification failed!!!
// The current query client implementation is not suitable for querying the xdb file specified by db_path.
// You should stop the service and use a suitable xdb file or upgrade to a Searcher implementation compatible with db_path.
printf("failed to verify xdb file `%s`, errcode: %d\n", db_path, errcode);
return;
}
// Verification passed, the current Searcher can be safely used for query operations on the xdb pointed to by dbPath
```
### File-Based Query
```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;
// Initialize region_buffer_t using region_buffer from stack space
int err = xdb_region_buffer_init(&region, region_buffer, sizeof(region_buffer));
if (err != 0) {
printf("failed to init the region buffer with errcode=%d\n", err);
return 1;
}
// Initialize winsock when the service starts; no need to call repeatedly, only needed on Windows systems
err = xdb_init_winsock();
if (err != 0) {
printf("failed to init the winsock with errno=%d\n", err);
return 1;
}
// 1. Initialize xdb query object from db_path.
// @Note: Use the db_path and version described above to create the 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. Call search API to query, both IPv4 and IPv6 are supported.
const char *ip_string = "1.2.3.4";
// ip_string = "240e:3b7:3272:d8d0:db09:c067:8d59:539e"; // IPv6
long cost_time = 0, s_time = xdb_now();
err = xdb_search_by_string(&searcher, ip_string, &region);
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);
}
// Clean up memory resources for region info; must be called after every search
xdb_region_buffer_free(&region);
// Note: For concurrent use, each thread needs to define and initialize its own searcher query object independently.
// 3. Close xdb searcher
xdb_close(&searcher);
xdb_clean_winsock(); // Call on Windows
return 0;
}
```
### Caching `VectorIndex`
We can pre-load VectorIndex data from the xdb file and cache it globally. Using the global VectorIndex cache every time a Searcher object is created can reduce a fixed IO operation, thereby accelerating queries and reducing IO pressure.
```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;
// Initialize region_buffer with NULL to let it manage memory allocation automatically
int err = xdb_region_buffer_init(&region, NULL, 0);
if (err != 0) {
printf("failed to init the region buffer with errcode=%d\n", err);
return 0;
}
// Initialize winsock when the service starts; no need to call repeatedly, only needed on Windows systems
err = xdb_init_winsock();
if (err != 0) {
printf("failed to init the winsock with errno=%d\n", err);
return 1;
}
// 1. Load VectorIndex from the db_path described above.
// Obtain v_index to create a global cache for subsequent repeated use.
// Note: v_index does not need to be loaded every time; it is recommended to load it once at service startup as a global resource.
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. Use the global VectorIndex variable to create an xdb searcher with VectorIndex cache.
// @Note: Use the db_path and version described above to create the 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. Call search API to query, both IPv4 and IPv6 are supported
const char *ip_string = "1.2.3.4";
// ip_string = "240e:3b7:3272:d8d0:db09:c067:8d59:539e"; // IPv6
long cost_time = 0, s_time = xdb_now();
err = xdb_search_by_string(&searcher, ip_string, &region);
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);
}
// Clean up memory resources for region info; must be called after every search
xdb_region_buffer_free(&region);
// Note: For concurrent use, each thread needs to define and initialize its own searcher query object independently.
// 4. Close xdb searcher; if the service is being shut down, the memory for v_index also needs to be freed.
xdb_close(&searcher);
xdb_close_vector_index(v_index);
xdb_clean_winsock();
return 0;
}
```
### Caching the Entire `xdb` File
We can also pre-load the entire xdb file into memory and then create a query object based on this data to achieve fully memory-based queries, similar to the previous 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;
// Initialize region_buffer with NULL to let it manage memory allocation automatically
int err = xdb_region_buffer_init(&region, NULL, 0);
if (err != 0) {
printf("failed to init the region buffer with errcode=%d\n", err);
return 0;
}
// Initialize winsock when the service starts; no need to call repeatedly, only needed on Windows systems
err = xdb_init_winsock();
if (err != 0) {
printf("failed to init the winsock with errno=%d\n", err);
return 1;
}
// 1. Load the entire xdb data from the db_path described above.
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. Use the global c_buffer variable to create a fully memory-based xdb query object.
// @Note: Use the version described above to create the 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. Call search API to query, both IPv4 and IPv6 are supported
const char *ip_string = "1.2.3.4";
// ip_string = "240e:3b7:3272:d8d0:db09:c067:8d59:539e"; // IPv6
long cost_time = 0, s_time = xdb_now();
err = xdb_search_by_string(&searcher, ip_string, &region);
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);
}
// Clean up memory resources for region info; must be called after every search
xdb_region_buffer_free(&region);
// Note: For concurrent use, xdb query objects created this way can be safely used for concurrency.
// It is recommended to create them when the service starts and then use them safely in parallel until the service shuts down.
// 4. Close xdb searcher; memory for c_buffer needs to be freed when shutting down the service.
xdb_close(&searcher);
xdb_close_content(c_buffer);
xdb_clean_winsock();
return 0;
}
```
### About Storage of Location Information
In older implementations, search-related functions relied on a specified `region_buffer` memory to store location information, which had significant limitations.
The new implementation provides an `xdb_region_buffer_t` object to manage these memory allocations. You can still specify a fixed `region_buffer` to create memory management for the region; this is suitable when the maximum length of your location information is known, which helps reduce memory fragmentation during runtime. If the length of the location information is uncertain or if your program is not suited for pre-allocating a block of memory, you can initialize `xdb_region_buffer_t` by specifying `NULL`. In this case, the object will automatically manage memory allocation, making it suitable for storing location information of any length, though this approach will certainly increase memory fragmentation over long-term operation.
```c
// 1. Create region_buffer by specifying a memory block
char buffer[512];
xdb_region_buffer_t region;
int err = xdb_region_buffer_init(&region, buffer, sizeof(buffer));
if (err != 0) {
// Initialization failed
printf("failed to init region buffer width errcode=%d", err);
return;
}
// 2. Create region_buffer by specifying NULL to let it allocate memory as needed automatically
xdb_region_buffer_t region;
int err = xdb_region_buffer_init(&region, NULL, 0);
if (err != 0) {
// Initialization failed
printf("failed to init region buffer width errcode=%d", err);
return;
}
// Note: After each query call, you must manually call the function to free memory.
// The search function will report an error if used with uncleaned region info.
xdb_region_buffer_free(&region);
```
# Compiling the Test Program
Compile and obtain the `xdb_searcher` executable as follows:
```bash
# cd to the c binding root directory
➜ 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
```
# Query Testing
Test queries against xdb via the `xdb_searcher search` command:
```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
```
Example: performing IPv4 query testing using the default data/ip2region_v4.xdb:
```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>> 1.2.3.4
{region: Australia|Queensland|Brisbane|0|AU, io_count: 5, took: 39 μs}
ip2region>> 120.229.45.2
{region: 中国|广东省|深圳市|移动|CN, io_count: 3, took: 13 μs}
```
Example: performing IPv6 query testing using the default data/ip2region_v6.xdb:
```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: United States|Florida|Miami|velia.net Internetdienste GmbH|US, io_count: 14, took: 76 μs}
ip2region>> 240e:3b7:3272:d8d0:db09:c067:8d59:539e
{region: 中国|广东省|深圳市|电信|CN, io_count: 8, took: 42 μs}
```
Enter an IP to perform a query; enter `quit` to exit the test program. You can also set `cache-policy` to file/vectorIndex/content respectively to test the efficiency of the three different cache implementations.
# bench Testing
Perform bench testing via the `xdb_searcher bench` command. This ensures there are no errors in the query program and the `xdb` file, while also providing average query performance through a large number of queries:
```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
```
Example: performing IPv4 bench testing via the default data/ip2region_v4.xdb and data/ipv4_source.txt:
```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}
```
Example: performing IPv6 bench testing via the default data/ip2region_v6.xdb and data/ipv6_source.txt:
```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}
```
You can set the `cache-policy` parameter to test the efficiency of different cache mechanisms (file/vectorIndex/content). @Note: Please ensure that the `src` file used for benching is the same source file used to generate the corresponding `xdb` file.

357
binding/c/README_zh.md Normal file
View File

@ -0,0 +1,357 @@
:globe_with_meridians: [中文简体](README_zh.md) | [English](README.md)
# ip2region 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(&region, 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 = "240e:3b7:3272:d8d0:db09:c067:8d59:539e"; // IPv6
long cost_time = 0, s_time = xdb_now();
err = xdb_search_by_string(&searcher, ip_string, &region);
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(&region);
// 备注:并发使用,每一个线程需要单独定义并且初始化一个 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(&region, 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 = "240e:3b7:3272:d8d0:db09:c067:8d59:539e"; // IPv6
long cost_time = 0, s_time = xdb_now();
err = xdb_search_by_string(&searcher, ip_string, &region);
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(&region);
// 备注:并发使用,每一个线程需要单独定义并且初始化一个 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(&region, 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 = "240e:3b7:3272:d8d0:db09:c067:8d59:539e"; // IPv6
long cost_time = 0, s_time = xdb_now();
err = xdb_search_by_string(&searcher, ip_string, &region);
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(&region);
// 备注:并发使用,使用这种方式创建的 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(&region, 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(&region, NULL, 0);
if (err != 0) {
// 初始化失败
printf("failed to init region buffer width errcode=%d", err);
return;
}
// 备注:在每次调用 search 完成 IP 定位信息的查询后,你需要手动调用函数来释放内存 .
// search 函数使用未经清理的 region 信息会报错。
xdb_region_buffer_free(&region);
```
# 测试程序编译
通过如下方式编译得到 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>> 1.2.3.4
{region: Australia|Queensland|Brisbane|0|AU, io_count: 5, took: 39 μs}
ip2region>> 120.229.45.2
{region: 中国|广东省|深圳市|移动|CN, io_count: 3, took: 13 μ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: United States|Florida|Miami|velia.net Internetdienste GmbH|US, io_count: 14, took: 76 μs}
ip2region>> 240e:3b7:3272:d8d0:db09:c067:8d59:539e
{region: 中国|广东省|深圳市|电信|CN, io_count: 8, took: 42 μ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 文件相同的源文件。

View File

@ -1,193 +0,0 @@
# ip2region xdb c 查询客户端实现
# 使用方式
### 完全基于文件的查询
```c
#include <stdio.h>
#include "xdb_searcher.h"
int main(int argc, char *argv[]) {
char *db_path = "ip2region.xdb file path";
xdb_searcher_t searcher;
char region_buffer[256], ip_buffer[16], *ip = "1.2.3.4";
long s_time;
// 1、从 db_path 初始化 xdb 查询对象
int err = xdb_new_with_file_only(&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 查询
// 得到的 region 信息会存储到 region_buffer 里面,如果你自定义了数据,请确保给足 buffer 的空间。
s_time = xdb_now();
err = xdb_search_by_string(&searcher, ip, region_buffer, sizeof(region_buffer));
if (err != 0) {
printf("failed search(%s) with errno=%d\n", ip, err);
} else {
printf("{region: %s, took: %d μs}", region_buffer, (int)(xdb_now() - s_time));
}
// 备注:并发使用,没一个线程需要单独定义并且初始化一个 searcher 查询对象。
// 3、关闭 xdb 查询器
xdb_close(&searcher);
return 0;
}
```
### 缓存 `VectorIndex` 索引
我们可以提前从 xdb 文件中加载出来 VectorIndex 数据,然后全局缓存,每次创建 Searcher 对象的时候使用全局的 VectorIndex 缓存可以减少一次固定的 IO 操作,从而加速查询,减少 IO 压力。
```c
#include <stdio.h>
#include "xdb_searcher.h"
int main(int argc, char *argv[]) {
char *db_path = "ip2region.xdb file path";
xdb_vector_index_t *v_index;
xdb_searcher_t searcher;
char region_buffer[256], ip_buffer[16], *ip = "1.2.3.4";
long s_time;
// 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 查询对象
int err = xdb_new_with_vector_index(&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 查询
// 得到的 region 信息会存储到 region_buffer 里面,如果你自定义了数据,请确保给足 buffer 的空间。
s_time = xdb_now();
err = xdb_search_by_string(&searcher, ip, region_buffer, sizeof(region_buffer));
if (err != 0) {
printf("failed search(%s) with errno=%d\n", ip, err);
} else {
printf("{region: %s, took: %d μs}", region_buffer, (int)(xdb_now() - s_time));
}
// 备注:并发使用,没一个线程需要单独定义并且初始化一个 searcher 查询对象。
// 4、关闭 xdb 查询器,如果是要关闭服务,也需要释放 v_index 的内存。
xdb_close(&searcher);
xdb_close_vector_index(v_index);
return 0;
}
```
### 缓存整个 `xdb` 数据
我们也可以预先加载整个 ip2region.xdb 的数据到内存,然后基于这个数据创建查询对象来实现完全基于文件的查询,类似之前的 memory search。
```c
#include <stdio.h>
#include "xdb_searcher.h"
int main(int argc, char *argv[]) {
char *db_path = "ip2region.xdb file path";
xdb_content_t *c_buffer;
xdb_searcher_t searcher;
char region_buffer[256], ip_buffer[16], *ip = "1.2.3.4";
long s_time;
// 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 查询对象
err = xdb_new_with_buffer(&searcher, c_buffer);
if (err != 0) {
printf("failed to create content cached searcher with errcode=%d\n", err);
return 2;
}
// 3、调用 search API 查询
// 得到的 region 信息会存储到 region_buffer 里面,如果你自定义了数据,请确保给足 buffer 的空间。
s_time = xdb_now();
err = xdb_search_by_string(&searcher, ip, region_buffer, sizeof(region_buffer));
if (err != 0) {
printf("failed search(%s) with errno=%d\n", ip, err);
} else {
printf("{region: %s, took: %d μs}", region_buffer, (int)(xdb_now() - s_time));
}
// 备注:并发使用,使用这种方式创建的 xdb 查询对象可以安全用于并发。
// 建议在服务启动的时候创建好,然后一直安全并发使用,直到服务关闭。
// 4、关闭 xdb 查询器,关闭服务的时候需要释放 c_buffer 的内存。
xdb_close(&searcher);
xdb_close_content(c_buffer);
return 0;
}
```
# 测试程序编译
通过如下方式编译得到 xdb_searcher 可执行程序:
```bash
# cd 到 c binding 根目录
make
```
# 查询测试
通过 `xdb_searcher search` 命令来测试对 ip2region.xdb 的查询:
```bash
➜ c git:(c_binding) ✗ ./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.xdb 进行查询测试:
```bash
➜ c git:(c_binding) ✗ ./xdb_searcher search --db=../../data/ip2region.xdb --cache-policy=vectorIndex
ip2region xdb searcher test program, cache_policy: vectorIndex
type 'quit' to exit
ip2region>> 1.2.3.4
{region: 美国|0|华盛顿|0|谷歌, io_count: 7, took: 13 μs}
ip2region>>
```
输入 ip 即可进行查询,输入 quit 即可退出测试程序。也可以分别设置 `cache-policy` 为 file/vectorIndex/content 来测试三种不同的缓存实现的效率。
# bench 测试
通过 `xdb_searcher bench` 命令来进行 bench 测试,一方面确保查询程序和 `xdb` 文件没有错误,另一方面可以通过大量的查询得到评价的查询性能:
```bash
➜ c git:(c_binding) ✗ ./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.xdb 和 data/ip.merge.txt 来进行 bench 测试:
```bash
➜ c git:(c_binding) ✗ ./xdb_searcher bench --db=../../data/ip2region.xdb --src=../../data/ip.merge.txt --cache-policy=vectorIndex
Bench finished, {cache_policy: vectorIndex, total: 3417955, took: 4.233s, cost: 1 μs/op}
```
可以设置 `cache-policy` 参数来分别测试 file/vectorIndex/content 不同缓存实现机制的效率。 @Note:请注意 bench 使用的 src 文件需要是生成对应的 xdb 文件相同的源文件。

View File

@ -7,56 +7,112 @@
// @Date 2022/06/28 // @Date 2022/06/28
#include "stdio.h" #include "stdio.h"
#include "xdb_searcher.h" #include "xdb_api.h"
struct searcher_test_entry { struct searcher_test_entry {
xdb_searcher_t searcher; xdb_searcher_t searcher;
xdb_vector_index_t *v_index; xdb_vector_index_t *v_index;
xdb_content_t *c_buffer; 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; typedef struct searcher_test_entry searcher_test_t;
int init_searcher_test(searcher_test_t *test, char *db_path, char *cache_policy) { int init_searcher_test(searcher_test_t *test, char *db_path, char *cache_policy) {
int err; 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->v_index = NULL;
test->c_buffer = NULL; test->c_buffer = NULL;
if (strcmp(cache_policy, "file") == 0) { if (strcmp(cache_policy, "file") == 0) {
err = xdb_new_with_file_only(&test->searcher, db_path); err = xdb_new_with_file_only(version, &test->searcher, db_path);
if (err != 0) { if (err != 0) {
printf("failed to create searcher with errcode=%d\n", err); printf("failed to create searcher with errcode=%d\n", err);
return 1; errcode = 4;
goto defer;
} }
} else if (strcmp(cache_policy, "vectorIndex") == 0) { } else if (strcmp(cache_policy, "vectorIndex") == 0) {
test->v_index = xdb_load_vector_index_from_file(db_path); test->v_index = xdb_load_vector_index_from_file(db_path);
if (test->v_index == NULL) { if (test->v_index == NULL) {
printf("failed to load vector index from `%s`\n", db_path); printf("failed to load vector index from `%s`\n", db_path);
return 2; errcode = 4;
goto defer;
} }
err = xdb_new_with_vector_index(&test->searcher, db_path, test->v_index); err = xdb_new_with_vector_index(version, &test->searcher, db_path, test->v_index);
if (err != 0) { if (err != 0) {
printf("failed to create vector index cached searcher with errcode=%d\n", err); printf("failed to create vector index cached searcher with errcode=%d\n", err);
return 3; errcode = 5;
goto defer;
} }
} else if (strcmp(cache_policy, "content") == 0) { } else if (strcmp(cache_policy, "content") == 0) {
test->c_buffer = xdb_load_content_from_file(db_path); test->c_buffer = xdb_load_content_from_file(db_path);
if (test->c_buffer == NULL) { if (test->c_buffer == NULL) {
printf("failed to load xdb content from `%s`\n", db_path); printf("failed to load xdb content from `%s`\n", db_path);
return 4; errcode = 4;
goto defer;
} }
err = xdb_new_with_buffer(&test->searcher, test->c_buffer); err = xdb_new_with_buffer(version, &test->searcher, test->c_buffer);
if (err != 0) { if (err != 0) {
printf("failed to create content cached searcher with errcode=%d\n", err); printf("failed to create content cached searcher with errcode=%d\n", err);
return 5; errcode = 5;
goto defer;
} }
} else { } else {
printf("invalid cache policy `%s`, options: file/vectorIndex/content\n", cache_policy); printf("invalid cache policy `%s`, options: file/vectorIndex/content\n", cache_policy);
return 6; errcode = 6;
goto defer;
} }
return 0; // 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) { void destroy_searcher_test(searcher_test_t *test) {
@ -64,13 +120,13 @@ void destroy_searcher_test(searcher_test_t *test) {
// check and free the vector index // check and free the vector index
if (test->v_index != NULL) { if (test->v_index != NULL) {
xdb_close_vector_index(test->v_index); xdb_free_vector_index(test->v_index);
test->v_index = NULL; test->v_index = NULL;
} }
// check and free the content buffer // check and free the content buffer
if (test->c_buffer != NULL) { if (test->c_buffer != NULL) {
xdb_close_content(test->c_buffer); xdb_free_content(test->c_buffer);
test->c_buffer = NULL; test->c_buffer = NULL;
} }
} }
@ -107,8 +163,12 @@ void test_search(int argc, char *argv[]) {
// for search // for search
long s_time, c_time; long s_time, c_time;
unsigned int ip; char line[512] = {'\0'};
char line[512] = {'\0'}, region[512] = {'\0'};
// ip parse
xdb_version_t *version;
bytes_ip_t ip_bytes[16] = {'\0'};
searcher_test_t test; searcher_test_t test;
for (i = 2; i < argc; i++) { for (i = 2; i < argc; i++) {
@ -152,6 +212,13 @@ void test_search(int argc, char *argv[]) {
return; 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); // printf("db_file=%s, cache_policy=%s\n", db_file, cache_policy);
err = init_searcher_test(&test, db_file, cache_policy); err = init_searcher_test(&test, db_file, cache_policy);
if (err != 0) { if (err != 0) {
@ -159,8 +226,9 @@ void test_search(int argc, char *argv[]) {
return; return;
} }
printf("ip2region xdb searcher test program, " printf("ip2region xdb searcher test program\n"
"cache_policy: %s\ntype 'quit' to exit\n", cache_policy); "source xdb: %s (%s, %s)\n"
"type 'quit' to exit\n", db_file, xdb_get_version(&test.searcher)->name, cache_policy);
while ( 1 ) { while ( 1 ) {
printf("ip2region>> "); printf("ip2region>> ");
get_line(stdin, line); get_line(stdin, line);
@ -168,26 +236,31 @@ void test_search(int argc, char *argv[]) {
continue; continue;
} }
if (strcasecmp( line, "quit") == 0 ) { if (strcmp(line, "quit") == 0 ) {
break; break;
} }
if (xdb_check_ip(line, &ip) != 0) { version = xdb_parse_ip(line, ip_bytes, sizeof(ip_bytes));
if (version == NULL) {
printf("invalid ip address `%s`\n", line); printf("invalid ip address `%s`\n", line);
continue; continue;
} }
s_time = xdb_now(); s_time = xdb_now();
err = xdb_search(&test.searcher, ip, region, sizeof(region)); err = xdb_search(&test.searcher, ip_bytes, version->bytes, &test.region);
if (err != 0) { if (err != 0) {
printf("{err: %d, io_count: %d}\n", err, xdb_get_io_count(&test.searcher)); printf("{err: %d, io_count: %d}\n", err, xdb_get_io_count(&test.searcher));
} else { } else {
c_time = xdb_now() - s_time; c_time = xdb_now() - s_time;
printf("{region: %s, io_count: %d, took: %ld μs}\n", region, xdb_get_io_count(&test.searcher), c_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); destroy_searcher_test(&test);
xdb_clean_winsock();
printf("searcher test program exited, thanks for trying\n"); printf("searcher test program exited, thanks for trying\n");
} }
@ -197,13 +270,18 @@ void test_bench(int argc, char *argv[]) {
char db_file[256] = {'\0'}, src_file[256] = {'\0'}, cache_policy[16] = {"vectorIndex"}; char db_file[256] = {'\0'}, src_file[256] = {'\0'}, cache_policy[16] = {"vectorIndex"};
FILE *handle; FILE *handle;
char line[1024] = {'\0'}, sip_str[16] = {'\0'}, eip_str[16] = {'\0'}; char line[1024] = {'\0'}, sip_str[INET6_ADDRSTRLEN+1] = {'\0'}, eip_str[INET6_ADDRSTRLEN+1] = {'\0'};
char src_region[512] = {'\0'}, region_buffer[512] = {'\0'}; char src_region[512] = {'\0'};
unsigned int sip, eip, mip, ip_list[5];
int count = 0, took; int count = 0, took;
long s_time, t_time, c_time = 0; long s_time, t_time, c_time = 0;
searcher_test_t test;
// 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++) { for (i = 2; i < argc; i++) {
r = argv[i]; r = argv[i];
if (strlen(r) < 5) { if (strlen(r) < 5) {
@ -246,6 +324,13 @@ void test_bench(int argc, char *argv[]) {
return; 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); // printf("db_file=%s, src_file=%s, cache_policy=%s\n", db_file, src_file, cache_policy);
s_time = xdb_now(); s_time = xdb_now();
err = init_searcher_test(&test, db_file, cache_policy); err = init_searcher_test(&test, db_file, cache_policy);
@ -262,57 +347,63 @@ void test_bench(int argc, char *argv[]) {
} }
while(fgets(line, sizeof(line), handle) != NULL) { while(fgets(line, sizeof(line), handle) != NULL) {
n = sscanf(line, "%15[^|]|%15[^|]|%511[^\n]", sip_str, eip_str, src_region); n = sscanf(line, "%46[^|]|%46[^|]|%511[^\n]", sip_str, eip_str, src_region);
if (n != 3) { if (n != 3) {
printf("invalid ip segment line `%s`\n", line); printf("invalid ip segment line `%s`\n", line);
return; return;
} }
if (xdb_check_ip(sip_str, &sip) != 0) { s_version = xdb_parse_ip(sip_str, sip_bytes, sizeof(sip_bytes));
if (s_version == NULL) {
printf("invalid start ip `%s`\n", sip_str); printf("invalid start ip `%s`\n", sip_str);
return; return;
} }
if (xdb_check_ip(eip_str, &eip) != 0) { e_version = xdb_parse_ip(eip_str, eip_bytes, sizeof(eip_bytes));
if (e_version == NULL) {
printf("invalid end ip `%s`\n", sip_str); printf("invalid end ip `%s`\n", sip_str);
return; return;
} }
if (sip > eip) { 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); printf("start ip(%s) should not be greater than end ip(%s)\n", sip_str, eip_str);
return; return;
} }
mip = xdb_mip(sip, eip); ip_list[0] = sip_bytes;
ip_list[0] = sip; ip_list[1] = eip_bytes;
ip_list[1] = xdb_mip(sip, mip); for (i = 0; i < 2; i++) {
ip_list[2] = mip;
ip_list[3] = xdb_mip(mip, eip);
ip_list[4] = eip;
for (i = 0; i < 5; i++) {
t_time = xdb_now(); t_time = xdb_now();
err = xdb_search(&test.searcher, ip_list[i], region_buffer, sizeof(region_buffer)); err = xdb_search(&test.searcher, ip_list[i], s_version->bytes, &test.region);
c_time += xdb_now() - t_time;
if (err != 0) { if (err != 0) {
xdb_long2ip(ip_list[i], sip_str); 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", sip_str, err); printf("failed to search ip `%s` with errno=%d\n", ip_string, err);
return; return;
} }
c_time += xdb_now() - t_time;
// check the region info // check the region info
if (strcmp(region_buffer, src_region) != 0) { if (strcmp(test.region.value, src_region) != 0) {
xdb_long2ip(ip_list[i], sip_str); xdb_ip_to_string(ip_list[i], s_version->bytes, ip_string, sizeof(ip_string));
printf("failed to search(%s) with (%s != %s)\n", sip_str, region_buffer, src_region); printf("failed to search(%s) with (%s != %s)\n", ip_string, test.region.value, src_region);
return; return;
} }
// free the region buffer
xdb_region_buffer_free(&test.region);
count++; count++;
} }
}; };
took = xdb_now() - s_time; took = xdb_now() - s_time;
destroy_searcher_test(&test); destroy_searcher_test(&test);
xdb_clean_winsock();
fclose(handle); fclose(handle);
printf("Bench finished, {cache_policy: %s, total: %d, took: %.3fs, cost: %d μs/op}\n", 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)); cache_policy, count, took/1e6, count == 0 ? 0 : (int)(c_time/count));

213
binding/c/test_util.c Normal file
View File

@ -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;
}

View File

@ -1,101 +0,0 @@
// 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_searcher.h"
void test_check_ip() {
char *ip_list[] = {
"1.2.3.4", "192.168.2.3", "120.24.78.129", "255.255.255.0",
"256.7.12.9", "12.56.78.320", "32.12.45.192", "222.221.220.219",
"192.168.1.101 ", "132.96.12.98a", "x23.12.2.12"
};
int errcode, i;
unsigned int ip;
char ip_buff[16] = {'\0'};
for (i = 0; i < 11; i++) {
errcode = xdb_check_ip(ip_list[i], &ip);
if (errcode != 0) {
printf("invalid ip address `%s`\n", ip_list[i]);
continue;
}
xdb_long2ip(ip, ip_buff);
printf("long(%-15s)=%-10u, long2ip(%-10u)=%-15s", ip_list[i], ip, ip, ip_buff);
if (strcmp(ip_list[i], ip_buff) != 0) {
printf(" --[Failed]\n");
} else {
printf(" --[Ok]\n");
}
}
}
void test_load_header() {
xdb_header_t *header = xdb_load_header_from_file("../../data/ip2region.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"
" length: %d\n"
"}\n",
header->version, header->index_policy, header->created_at,
header->start_index_ptr, header->end_index_ptr, header->length
);
}
xdb_close_header(header);
}
void test_load_vector_index() {
xdb_vector_index_t *v_index = xdb_load_vector_index_from_file("../../data/ip2region.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_close_vector_index(v_index);
}
void test_load_content() {
xdb_content_t *content = xdb_load_content_from_file("../../data/ip2region.xdb");
if (content == NULL) {
printf("failed to load content from file\n");
} else {
printf("content loaded from file, length=%d\n", content->length);
}
xdb_close_content(content);
}
// valgrind --tool=memcheck --leak-check=full ./a.out
int main(int argc, char *argv[]) {
printf("test check ip ... \n");
test_check_ip();
printf("|--done\n\n");
printf("test load header ... \n");
test_load_header();
printf("|--done\n\n");
printf("test load vector index ... \n");
test_load_vector_index();
printf("|--done\n\n");
printf("test load content ... \n");
test_load_content();
printf("|--done\n\n");
return 0;
}

285
binding/c/xdb_api.h Normal file
View File

@ -0,0 +1,285 @@
// 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
// @Note:
// this define must be put before any header include
// force the LFS for ftell
#define _FILE_OFFSET_BITS 64
#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);
// large file seek and tell
XDB_PUBLIC(int) xdb_fseek(FILE *, long long, int);
XDB_PUBLIC(long long) xdb_ftell(FILE *);
// --- 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

View File

@ -6,42 +6,104 @@
// @Author Lion <chenxin619315@gmail.com> // @Author Lion <chenxin619315@gmail.com>
// @Date 2022/06/27 // @Date 2022/06/27
#include "xdb_searcher.h" #include "xdb_api.h"
// for Linux // --- region buffer
#ifdef XDB_LINUX XDB_PUBLIC(int) xdb_region_buffer_init(xdb_region_buffer_t *region, char *buffer, size_t length) {
#include "sys/time.h" if (buffer == NULL) {
#endif 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
}
// @Note: since 2023/10/13 to compatible with the windows system region->value = buffer;
#ifdef XDB_WINDOWS return 0;
#include <windows.h>
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);
} }
#endif
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 // internal function prototype define
XDB_PRIVATE(int) read(xdb_searcher_t *, long offset, char *, size_t length); XDB_PRIVATE(int) read(xdb_searcher_t *, long offset, char *, size_t length);
XDB_PRIVATE(int) xdb_new_base(xdb_searcher_t *xdb, const char *db_path, const xdb_vector_index_t *v_index, const xdb_content_t *c_buffer) { 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)); memset(xdb, 0x00, sizeof(xdb_searcher_t));
// set the version
xdb->version = version;
// check the content buffer first // check the content buffer first
if (c_buffer != NULL) { if (c_buffer != NULL) {
xdb->v_index = NULL; xdb->v_index = NULL;
@ -62,16 +124,16 @@ XDB_PRIVATE(int) xdb_new_base(xdb_searcher_t *xdb, const char *db_path, const xd
} }
// xdb searcher new api define // xdb searcher new api define
XDB_PUBLIC(int) xdb_new_with_file_only(xdb_searcher_t *xdb, const char *db_path) { XDB_PUBLIC(int) xdb_new_with_file_only(xdb_version_t *version, xdb_searcher_t *xdb, const char *db_path) {
return xdb_new_base(xdb, db_path, NULL, NULL); return xdb_new_base(version, xdb, db_path, NULL, NULL);
} }
XDB_PUBLIC(int) xdb_new_with_vector_index(xdb_searcher_t *xdb, const char *db_path, const xdb_vector_index_t *v_index) { 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(xdb, db_path, v_index, NULL); return xdb_new_base(version, xdb, db_path, v_index, NULL);
} }
XDB_PUBLIC(int) xdb_new_with_buffer(xdb_searcher_t *xdb, const xdb_content_t *c_buffer) { 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(xdb, NULL, NULL, c_buffer); return xdb_new_base(version, xdb, NULL, NULL, c_buffer);
} }
XDB_PUBLIC(void) xdb_close(void *ptr) { XDB_PUBLIC(void) xdb_close(void *ptr) {
@ -84,93 +146,109 @@ XDB_PUBLIC(void) xdb_close(void *ptr) {
// --- xdb searcher search api define // --- xdb searcher search api define
XDB_PUBLIC(int) xdb_search_by_string(xdb_searcher_t *xdb, const char *str_ip, char *region_buffer, size_t length) { XDB_PUBLIC(int) xdb_search_by_string(xdb_searcher_t *xdb, const string_ip_t *ip_string, xdb_region_buffer_t *region) {
unsigned int ip = 0; bytes_ip_t ip_bytes[16] = {'\0'};
int errcode = xdb_check_ip(str_ip, &ip); xdb_version_t *version = xdb_parse_ip(ip_string, ip_bytes, sizeof(ip_bytes));
if (errcode != 0) { if (version == NULL) {
return 10 + errcode; return 10;
} else { } else {
return xdb_search(xdb, ip, region_buffer, length); return xdb_search(xdb, ip_bytes, version->bytes, region);
} }
} }
XDB_PUBLIC(int) xdb_search(xdb_searcher_t *xdb, unsigned int ip, char *region_buffer, size_t length) { 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, l, h, m, data_len; int il0, il1, idx, err, bytes, d_bytes;
unsigned int s_ptr, e_ptr, p, sip, eip, data_ptr; register int seg_index_size, l, h, m, p;
char vector_buffer[xdb_vector_index_size], segment_buffer[xdb_segment_index_size]; unsigned int s_ptr, e_ptr, data_ptr, data_len;
char vector_buffer[xdb_vector_index_size];
char segment_buffer[xdb_v6_index_size];
// reset the io counter // 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; xdb->io_count = 0;
// locate the segment index block based on the vector index // locate the segment index block based on the vector index
il0 = ((int) (ip >> 24)) & 0xFF; il0 = (int) (ip_bytes[0]);
il1 = ((int) (ip >> 16)) & 0xFF; il1 = (int) (ip_bytes[1]);
idx = il0 * xdb_vector_index_cols * xdb_vector_index_size + il1 * xdb_vector_index_size; idx = il0 * xdb_vector_index_cols * xdb_vector_index_size + il1 * xdb_vector_index_size;
if (xdb->v_index != NULL) { if (xdb->v_index != NULL) {
s_ptr = xdb_get_uint(xdb->v_index->buffer, idx); s_ptr = xdb_le_get_uint32(xdb->v_index->buffer, idx);
e_ptr = xdb_get_uint(xdb->v_index->buffer, idx + 4); e_ptr = xdb_le_get_uint32(xdb->v_index->buffer, idx + 4);
} else if (xdb->content != NULL) { } else if (xdb->content != NULL) {
s_ptr = xdb_get_uint(xdb->content->buffer, xdb_header_info_length + idx); s_ptr = xdb_le_get_uint32(xdb->content->buffer, xdb_header_info_length + idx);
e_ptr = xdb_get_uint(xdb->content->buffer, xdb_header_info_length + idx + 4); e_ptr = xdb_le_get_uint32(xdb->content->buffer, xdb_header_info_length + idx + 4);
} else { } else {
err = read(xdb, xdb_header_info_length + idx, vector_buffer, sizeof(vector_buffer)); err = read(xdb, xdb_header_info_length + idx, vector_buffer, sizeof(vector_buffer));
if (err != 0) { if (err != 0) {
return 10 + err; return 10 + err;
} }
s_ptr = xdb_get_uint(vector_buffer, 0); s_ptr = xdb_le_get_uint32(vector_buffer, 0);
e_ptr = xdb_get_uint(vector_buffer, 4); e_ptr = xdb_le_get_uint32(vector_buffer, 4);
} }
// printf("s_ptr=%u, e_ptr=%u\n", s_ptr, e_ptr); // printf("s_ptr=%u, e_ptr=%u\n", s_ptr, e_ptr);
// @Note: ptr validate, zero ptr means source data missing
// so we could just stop here and return an empty string.
if (s_ptr == 0 || e_ptr == 0) {
xdb_region_buffer_empty(region);
return err;
}
// binary search to get the final region info // 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; data_len = 0, data_ptr = 0;
l = 0, h = ((int) (e_ptr - s_ptr)) / xdb_segment_index_size; l = 0, h = ((int) (e_ptr - s_ptr)) / seg_index_size;
while (l <= h) { while (l <= h) {
m = (l + h) >> 1; m = (l + h) >> 1;
p = s_ptr + m * xdb_segment_index_size; p = s_ptr + m * seg_index_size;
// read the segment index item // read the segment index item
err = read(xdb, p, segment_buffer, sizeof(segment_buffer)); err = read(xdb, p, segment_buffer, seg_index_size);
if (err != 0) { if (err != 0) {
return 20 + err; return 20 + err;
} }
// decode the data fields as needed // decode the data fields as needed
sip = xdb_get_uint(segment_buffer, 0); if (xdb->version->ip_compare(ip_bytes, bytes, segment_buffer, 0) < 0) {
if (ip < sip) {
h = m - 1; h = m - 1;
} else { } else if (xdb->version->ip_compare(ip_bytes, bytes, segment_buffer, bytes) > 0) {
eip = xdb_get_uint(segment_buffer, 4);
if (ip > eip) {
l = m + 1; l = m + 1;
} else { } else {
data_len = xdb_get_ushort(segment_buffer, 8); data_len = xdb_le_get_uint16(segment_buffer, d_bytes);
data_ptr = xdb_get_uint(segment_buffer, 10); data_ptr = xdb_le_get_uint32(segment_buffer, d_bytes + 2);
break; break;
} }
} }
}
// printf("data_len=%u, data_ptr=%u\n", data_len, data_ptr); // printf("data_len=%u, data_ptr=%u\n", data_len, data_ptr);
if (data_len == 0) { if (data_len == 0) {
region_buffer[0] = '\0'; // return 100;
return 0; xdb_region_buffer_empty(region);
return err;
} }
// buffer length checking // buffer alloc checking
if (data_len >= (int) length) { err = xdb_region_buffer_alloc(region, data_len);
return 1; if (err != 0) {
return 100 + err;
} }
err = read(xdb, data_ptr, region_buffer, data_len); err = read(xdb, data_ptr, region->value, data_len);
if (err != 0) { if (err != 0) {
return 30 + err; return 30 + err;
} }
// auto append a NULL-end return err;
region_buffer[data_len] = '\0';
return 0;
} }
XDB_PRIVATE(int) read(xdb_searcher_t *xdb, long offset, char *buffer, size_t length) { XDB_PRIVATE(int) read(xdb_searcher_t *xdb, long offset, char *buffer, size_t length) {
@ -193,237 +271,10 @@ XDB_PRIVATE(int) read(xdb_searcher_t *xdb, long offset, char *buffer, size_t len
return 0; 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) { XDB_PUBLIC(int) xdb_get_io_count(xdb_searcher_t *xdb) {
return xdb->io_count; return xdb->io_count;
} }
// --- buffer load util functions
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_get_ushort(header->buffer, 0);
header->index_policy = (unsigned short) xdb_get_ushort(header->buffer, 2);
header->created_at = xdb_get_uint(header->buffer, 4);
header->start_index_ptr = xdb_get_uint(header->buffer, 8);
header->end_index_ptr = xdb_get_uint(header->buffer,12);
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_close_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_close_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_close_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);
}
}
// --- End
// get unsigned long (4bytes) from a specified buffer start from the specified offset
XDB_PUBLIC(unsigned int) xdb_get_uint(const char *buffer, int offset) {
return (
((buffer[offset ]) & 0x000000FF) |
((buffer[offset+1] << 8) & 0x0000FF00) |
((buffer[offset+2] << 16) & 0x00FF0000) |
((buffer[offset+3] << 24) & 0xFF000000)
);
}
// get unsigned short (2bytes) from a specified buffer start from the specified offset
XDB_PUBLIC(int) xdb_get_ushort(const char *buffer, int offset) {
return (
((buffer[offset ]) & 0x000000FF) |
((buffer[offset+1] << 8) & 0x0000FF00)
);
}
// string ip to unsigned int
static int shiftIndex[4] = {24, 16, 8, 0};
XDB_PUBLIC(int) xdb_check_ip(const char *src_ip, unsigned int *dst_ip) {
char c;
int i, n, ip = 0;
const char *ptr = src_ip;
for (i = 0; i < 4; i++) {
n = 0;
while (1) {
c = *ptr;
ptr++;
if (c >= '0' && c <= '9') {
n *= 10;
n += c - '0';
} else if ((i < 3 && c == '.') || i == 3) {
// stopping at the '.' but ignore the tailing chars
// after the 3rd one (auto clean the tailing none-integer ?).
break;
} else {
return 1;
}
}
if (n > 0xFF) {
return 2;
}
ip |= (n << shiftIndex[i]);
}
*dst_ip = ip;
return 0;
}
// unsigned int ip to string ip
XDB_PUBLIC(void) xdb_long2ip(unsigned int ip, char *buffer) {
sprintf(buffer, "%d.%d.%d.%d", (ip >> 24) & 0xFF, (ip >> 16) & 0xFF, (ip >> 8) & 0xFF, ip & 0xFF);
}
// get the middle ip of a and b
XDB_PUBLIC(unsigned int) xdb_mip(unsigned long a, unsigned long b) {
return (unsigned int) ((a + b) >> 1);
}
XDB_PUBLIC(long) xdb_now() {
struct timeval c_time;
gettimeofday(&c_time, NULL);
return c_time.tv_sec * (int)1e6 + c_time.tv_usec;
}

View File

@ -1,149 +0,0 @@
// 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_XDB_SEARCHER_H
#define C_XDB_SEARCHER_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
#elif ( defined(linux) || defined(_UNIX) || defined(__APPLE__) )
# define XDB_PUBLIC(type) extern type
# define XDB_PRIVATE(type) static inline type
# define XDB_LINUX
#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_header_info_length 256
#define xdb_vector_index_rows 256
#define xdb_vector_index_cols 256
#define xdb_vector_index_size 8
#define xdb_segment_index_size 14
// cache of vector_index_row × vector_index_rows × vector_index_size
#define xdb_vector_index_length 524288
// --- buffer load util 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;
// 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_close_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_close_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_close_content(void *);
// --- End buffer load
// xdb searcher structure
struct xdb_searcher_entry {
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_searcher_t *, const char *);
XDB_PUBLIC(int) xdb_new_with_vector_index(xdb_searcher_t *, const char *, const xdb_vector_index_t *);
XDB_PUBLIC(int) xdb_new_with_buffer(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 char *, char *, size_t);
XDB_PUBLIC(int) xdb_search(xdb_searcher_t *, unsigned int, char *, size_t);
XDB_PUBLIC(int) xdb_get_io_count(xdb_searcher_t *);
// get unsigned long (4bytes) from a specified buffer start from the specified offset with little-endian
XDB_PUBLIC(unsigned int) xdb_get_uint(const char *, int);
// get unsigned short (2bytes) from a specified buffer start from the specified offset with little-endian
XDB_PUBLIC(int) xdb_get_ushort(const char *, int);
// check the specified string ip and convert it to an unsigned int
XDB_PUBLIC(int) xdb_check_ip(const char *, unsigned int *);
// unsigned int ip to string ip
XDB_PUBLIC(void) xdb_long2ip(unsigned int, char *);
// get the middle ip of a and b
XDB_PUBLIC(unsigned int) xdb_mip(unsigned long, unsigned long);
// get the current time in microseconds
XDB_PUBLIC(long) xdb_now();
#endif //C_XDB_SEARCHER_H

525
binding/c/xdb_util.c Normal file
View File

@ -0,0 +1,525 @@
// 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) {
unsigned 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 long fileBytes = xdb_ftell(handle);
long long maxFilePtr = (1LL << (runtime_ptr_bytes * 8)) - 1;
// printf("fileBytes: %lld, maxFilePtr: %lld\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;
}
XDB_PUBLIC(int) xdb_fseek(FILE *handle, long long offset, int whence) {
// we may have to use the large file solution later
// #if defined(XDB_LINUX)
// return fseeko(handle, (off_t) offset, whence);
// #elif defined(XDB_WINDOWS)
// return _fseeki64(handle, (__int64) offset, whence)
// #else
// return fseek(handle, (long) offset, whence);
// #endif
return fseek(handle, (long) offset, whence);
}
XDB_PUBLIC(long long) xdb_ftell(FILE *handle) {
// we may have to use the large file solution later
// #if defined(XDB_LINUX)
// return (long long) ftello(handle);
// #elif defined(XDB_WINDOWS)
// return (long long) _ftelli64(handle);
// #else
// // report error ?
// return (long long) ftell(handle);
// #endif
return (long long) ftell(handle);
}

5
binding/cangjie/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
# build cache
.cache/
# dependency lock
cjpm.lock

172
binding/cangjie/README.md Normal file
View File

@ -0,0 +1,172 @@
# ip2region Cangjie Query Client
This implementation supports both `IPv4` and `IPv6`, and provides a `thread-safe` service layer.
## Architecture
```
binding/cangjie/
├── src/
│ ├── xdb/ # Low-level xdb query engine
│ │ ├── searcher.cj # Searcher (file-only / vector-index / content-buff modes)
│ │ ├── util.cj # IP parsing/comparison, byte readers
│ │ ├── header.cj # xdb header + version parsing
│ │ ├── version.cj # IPv4/IPv6 version definitions
│ │ ├── *_test.cj # Unit tests (24 cases)
│ ├── service/ # High-level thread-safe service layer
│ │ ├── config.cj # Config (cache policy, pool size, pre-loaded data)
│ │ ├── searcher_pool.cj # SearcherPool (Semaphore + ConcurrentLinkedQueue)
│ │ ├── ip2region.cj # Ip2Region unified API
│ │ ├── *_test.cj # Unit tests (17 cases)
├── example/ # Executable CLI demo (separate project)
│ ├── cjpm.toml
│ └── src/main.cj # 5 usage demos + benchmarks
├── cjpm.toml # Static library project
└── README.md
```
**Two API layers:**
- **`ip2region.xdb`** — Low-level Searcher. Not thread-safe (mutable `ioCount` + `File` seek/read state). Supports three cache modes.
- **`ip2region.service`** — High-level `Ip2Region` class wrapping `SearcherPool`. Thread-safe, handles v4/v6 dispatch automatically.
## Build
```bash
cd binding/cangjie
cjpm build # static library
cd example
cjpm build # example executable
```
## Test
41 tests across both packages (24 xdb + 17 service):
```bash
cd binding/cangjie
cjpm test
```
## Cache Policies
| Policy | Memory | Speed | Thread-safe* |
|--------|--------|-------|-------------|
| `FileOnly` (0) | ~0 MB | ~21 µs/op (v4) | Via SearcherPool |
| `VectorIndex` (1) | ~4 MB | ~20 µs/op (v4) | Via SearcherPool |
| `ContentBuff` (2) | ~full xdb | ~5 µs/op (v4) | Via SearcherPool |
\* Low-level `Searcher` is NOT thread-safe. The service layer (`SearcherPool` / `Ip2Region`) provides thread safety by pooling searchers (FileOnly/VectorIndex) or sharing a read-only buffer (ContentBuff).
## Benchmark Results
Benchmarked on Windows 11, AMD Ryzen 7, 487k IPv4 records / 638k IPv6 records:
### IPv4 (`ip2region_v4.xdb`, 487,167 records)
| Cache Policy | Total | Time | Avg |
|-------------|-------|------|-----|
| ContentBuff | 487,167 | 3,059 ms | **5 µs/op** |
| VectorIndex | 487,167 | 10,413 ms | **20 µs/op** |
| FileOnly | 487,167 | 11,271 ms | **21 µs/op** |
### IPv6 (`ip2region_v6.xdb`, 638,953 records)
| Cache Policy | Total | Time | Avg |
|-------------|-------|------|-----|
| ContentBuff | 638,953 | 37,080 ms | **56 µs/op** |
| VectorIndex | 638,953 | 38,443 ms | **58 µs/op** |
| FileOnly | 638,953 | 19,148 ms | **29 µs/op** |
Run your own:
```bash
cd example
cjpm run -- bench --db ../../../data/ip2region_v4.xdb --src ../../../data/ipv4_source.txt --cache-policy content
cjpm run -- bench --db ../../../data/ip2region_v6.xdb --src ../../../data/ipv6_source.txt --cache-policy file
```
## Usage
### Low-level API (`ip2region.xdb`)
```cangjie
import ip2region.xdb.*
// Content buffer mode (fastest)
let content = File.readFrom(Path("ip2region_v4.xdb"))
let header = newHeaderFromBytes(content)
let version = versionFromHeader(header)
let searcher = Searcher(version, content, "ip2region_v4.xdb")
let region = searcher.search(parseIP("220.181.108.183"))
searcher.close()
// Vector index mode
let vIndex = loadVectorIndex(content)
let searcher2 = Searcher(version, "ip2region_v4.xdb", vIndex)
searcher2.close()
// File only mode
let searcher3 = Searcher(version, "ip2region_v4.xdb")
searcher3.close()
```
### High-level API (`ip2region.service`)
```cangjie
import ip2region.service.*
// Via factory (VectorIndex, 20 searchers)
let region = newIp2Region("ip2region_v4.xdb", "ip2region_v6.xdb")
let result = region.search("220.181.108.183")
region.close()
// Custom config
let v4Cfg = Config(ContentBuff, IPv4, "ip2region_v4.xdb", 10)
let v6Cfg = Config(VectorIndex, IPv6, "ip2region_v6.xdb", 20)
let svc = Ip2Region(v4Cfg, v6Cfg)
let result = svc.search("2408:8266:100:1000::")
svc.close()
```
### Single-version API
```cangjie
import ip2region.service.*
// IPv4 only — Ip2Region(config, IPv4)
let v4Cfg = Config(ContentBuff, IPv4, "ip2region_v4.xdb", 10)
let v4 = Ip2Region(v4Cfg, IPv4)
let r4 = v4.search("220.181.108.183")
v4.close()
// IPv6 only — Ip2Region(config, IPv6)
let v6Cfg = Config(ContentBuff, IPv6, "ip2region_v6.xdb", 10)
let v6 = Ip2Region(v6Cfg, IPv6)
let r6 = v6.search("2408:8266:100:1000::")
v6.close()
// Factory functions
let v4Only = newIp2RegionV4(v4Cfg)
let v6Only = newIp2RegionV6(v6Cfg)
v4Only.close(); v6Only.close()
```
### CLI
```bash
cd example
cjpm run
```
## Thread Safety
- **`xdb.Searcher`** — NOT thread-safe. Keep one per thread or use a pool.
- **`service.SearcherPool`** — Thread-safe. Pre-allocates N searchers, uses `Semaphore` for backpressure and `ConcurrentLinkedQueue` for storage. `borrow()` blocks until a searcher is available.
- **`service.Ip2Region`** — Thread-safe. Delegates to pool or shared in-mem searcher based on cache policy. ContentBuff mode shares a single Searcher (Array<Byte> is read-only), FileOnly/VectorIndex use the pool.
- **`service.Config`** — Immutable after construction. Safe to share.
## Requirements
- Cangjie SDK 1.1.0+
- `cjpm` build tool

15
binding/cangjie/cjpm.toml Normal file
View File

@ -0,0 +1,15 @@
[package]
cjc-version = "1.1.0"
name = "ip2region"
organization = ""
description = "ip2region xdb searcher library for Cangjie"
version = "1.0.0"
target-dir = ""
script-dir = ""
output-type = "static"
compile-option = "-Woff unused"
override-compile-option = ""
link-option = ""
package-configuration = {}
[dependencies]

View File

@ -0,0 +1,10 @@
[package]
cjc-version = "1.1.0"
name = "ip2regionExample"
version = "1.0.0"
output-type = "executable"
compile-option = "-Woff unused"
description = "ip2region Cangjie CLI example"
[dependencies]
ip2region = { path = ".." }

View File

@ -0,0 +1,196 @@
package ip2regionExample
import std.fs.*
import std.time.*
import std.collection.*
import ip2region.xdb.*
import ip2region.service.*
// ip2region Cangjie 使用示例
//
// 构建: cjpm build
// 运行: cjpm run
//
// 默认从 ../../../data/ 查找 xdb 文件,
// 也可通过命令行参数指定路径:
// cjpm run -- --v4-db <path> --v6-db <path>
func printHelp() {
println("ip2region Cangjie 使用示例")
println("用法: cjpm run -- [options]")
println("选项:")
println(" --v4-db <path> IPv4 xdb 文件路径 (默认: ../../../data/ip2region_v4.xdb)")
println(" --v6-db <path> IPv6 xdb 文件路径 (默认: ../../../data/ip2region_v6.xdb)")
println(" --help 显示此帮助")
}
// ============================================================
// 方式一: 使用底层 Searcher API (ip2region.xdb)
// 可自行选择缓存策略,单线程使用
// ============================================================
func demoXdbSearcher(v4Path: String, v6Path: String) {
println("\n=== 方式一: 底层 Searcher API ===")
// 1. 读取 xdb 文件到内存
let v4Content = File.readFrom(Path(v4Path))
// 2. 解析文件头,获取版本信息
let v4Header = newHeaderFromBytes(v4Content)
let v4Version = versionFromHeader(v4Header)
// 3. 选择缓存策略创建 Searcher
// 支持三种模式: ContentBuff(最快), VectorIndex(均衡), FileOnly(省内存)
let vIndex = loadVectorIndex(v4Content)
let searcher = Searcher(v4Version, v4Path, vIndex) // VectorIndex 模式
// 4. 查询 IP
let ip = parseIP("220.181.108.183")
let region = searcher.search(ip)
println(" IP: 220.181.108.183")
println(" 结果: ${region}")
searcher.close()
}
// ============================================================
// 方式二: 使用 Ip2Region 服务 API (ip2region.service)
// 自动管理连接池,支持多线程安全访问
// ============================================================
func demoIp2Region(v4Path: String, v6Path: String) {
println("\n=== 方式二: Ip2Region 服务 API ===")
// 1. 创建配置 (缓存策略 + 连接池大小)
let v4Cfg = Config(VectorIndex, IPv4, v4Path, 10)
let v6Cfg = Config(VectorIndex, IPv6, v6Path, 10)
// 2. 创建服务 (同时支持 IPv4 + IPv6)
let region = Ip2Region(v4Cfg, v6Cfg)
// 3. 查询 IPv4
let r1 = region.search("220.181.108.183")
println(" IPv4: 220.181.108.183 -> ${r1}")
// 4. 查询 IPv6
let r2 = region.search("2408:8266:100:1000::")
println(" IPv6: 2408:8266:100:1000:: -> ${r2}")
region.close()
}
// ============================================================
// 方式三: 单版本服务 (仅 IPv4 或 仅 IPv6)
// 使用 init(config, ipVersion) 构造函数
// ============================================================
func demoSingleVersion(v4Path: String, v6Path: String) {
println("\n=== 方式三: 单版本服务 ===")
// IPv4 only
let v4Cfg = Config(ContentBuff, IPv4, v4Path, 3)
let v4Region = Ip2Region(v4Cfg, IPv4)
let r4 = v4Region.search("220.181.108.183")
println(" IPv4 only: 220.181.108.183 -> ${r4}")
v4Region.close()
// IPv6 only
let v6Cfg = Config(ContentBuff, IPv6, v6Path, 3)
let v6Region = Ip2Region(v6Cfg, IPv6)
let r6 = v6Region.search("2408:8266:100:1000::")
println(" IPv6 only: 2408:8266:100:1000:: -> ${r6}")
v6Region.close()
}
// ============================================================
// 方式四: 使用简便工厂函数 (ip2region.service)
// 一行创建,默认 VectorIndex + 20 连接池
// ============================================================
func demoFactory(v4Path: String, v6Path: String) {
println("\n=== 方式四: 简便工厂函数 ===")
let region = newIp2Region(v4Path, v6Path)
let result = region.search("220.181.108.183")
println(" 220.181.108.183 -> ${result}")
region.close()
}
// ============================================================
// 性能测试: 测试三种缓存策略的查询速度
// ============================================================
func bench(v4Path: String) {
println("\n=== 性能测试 (IPv4, 487k 条记录) ===")
let content = File.readFrom(Path(v4Path))
let header = newHeaderFromBytes(content)
let version = versionFromHeader(header)
let vIndex = loadVectorIndex(content)
// 从数据文件读取测试 IP 列表
let srcPath = v4Path.replace("ip2region_v4.xdb", "ipv4_source.txt")
let srcContent = File.readFrom(Path(srcPath))
let lines = String.fromUtf8(srcContent).split("\n")
var ipList = ArrayList<Array<Byte>>()
for (line in lines) {
let trimmed = line.trimAsciiStart().trimAsciiEnd()
if (trimmed.size == 0) { continue }
let parts = trimmed.split("|")
if (parts.size < 3) { continue }
try { ipList.add(parseIP(parts[0])) } catch (_) { continue }
}
println(" 加载了 ${ipList.size} 个 IP")
// 测试 ContentBuff 模式
println("\n [ContentBuff 模式]")
let s1 = Searcher(version, content, v4Path)
benchSearcher(s1, ipList)
// 测试 VectorIndex 模式
println(" [VectorIndex 模式]")
let s2 = Searcher(version, v4Path, vIndex)
benchSearcher(s2, ipList)
// 测试 FileOnly 模式
println(" [FileOnly 模式]")
let s3 = Searcher(version, v4Path)
benchSearcher(s3, ipList)
s1.close(); s2.close(); s3.close()
}
func benchSearcher(searcher: Searcher, ipList: ArrayList<Array<Byte>>) {
let count = ipList.size
let start = MonoTime.now()
for (i in 0..count) {
let _ = searcher.search(ipList[i])
}
let elapsed = MonoTime.now() - start
let avg = elapsed.toMicroseconds() / count
println(" 总量: ${count}, 耗时: ${elapsed.toMilliseconds()} ms, 平均: ${avg} us/op")
}
main(args: Array<String>): Int64 {
var v4Path = "../../../data/ip2region_v4.xdb"
var v6Path = "../../../data/ip2region_v6.xdb"
var i = 0
while (i < args.size) {
if (args[i] == "--v4-db" && i + 1 < args.size) {
v4Path = args[i + 1]; i = i + 2
} else if (args[i] == "--v6-db" && i + 1 < args.size) {
v6Path = args[i + 1]; i = i + 2
} else if (args[i] == "--help") {
printHelp(); return 0
} else { i = i + 1 }
}
println("ip2region Cangjie 使用示例")
println("xdb 路径: ${v4Path}")
demoXdbSearcher(v4Path, v6Path)
demoIp2Region(v4Path, v6Path)
demoSingleVersion(v4Path, v6Path)
demoFactory(v4Path, v6Path)
bench(v4Path)
println("\n完成!")
return 0
}

225
binding/cangjie/src/main.cj Normal file
View File

@ -0,0 +1,225 @@
package ip2region
import std.fs.*
import std.time.*
import std.env.*
import ip2region.xdb.*
func printHelp() {
println("ip2region xdb searcher")
println("Usage: ip2region [command] [command options]")
println("Command:")
println(" search search input test")
println(" bench search bench test")
}
func runSearch(args: Array<String>) {
var v4DbPath = "../../data/ip2region_v4.xdb"
var v6DbPath = "../../data/ip2region_v6.xdb"
var v4CachePolicy = "vectorIndex"
var v6CachePolicy = "vectorIndex"
var showHelp = false
var i = 1 // skip command name
while (i < args.size) {
let arg = args[i]
if (arg == "--v4-db" && i + 1 < args.size) {
v4DbPath = args[i + 1]
i = i + 2
} else if (arg == "--v6-db" && i + 1 < args.size) {
v6DbPath = args[i + 1]
i = i + 2
} else if (arg == "--v4-cache-policy" && i + 1 < args.size) {
v4CachePolicy = args[i + 1]
i = i + 2
} else if (arg == "--v6-cache-policy" && i + 1 < args.size) {
v6CachePolicy = args[i + 1]
i = i + 2
} else if (arg == "--help") {
showHelp = true
i = i + 1
} else {
i = i + 1
}
}
if (showHelp) {
println("ip2region search [command options]")
println("options:")
println(" --v4-db <path> ip2region v4 binary xdb file path")
println(" --v4-cache-policy <mode> v4 cache policy: file/vectorIndex/content")
println(" --v6-db <path> ip2region v6 binary xdb file path")
println(" --v6-cache-policy <mode> v6 cache policy: file/vectorIndex/content")
println(" --help print this help")
return
}
// Load v4 xdb
print("Loading v4 xdb ... ")
let v4Content = File.readFrom(Path(v4DbPath))
let v4Header = newHeaderFromBytes(v4Content)
let v4Version = versionFromHeader(v4Header)
let v4Searcher = createSearcher(v4Version, v4Content, v4DbPath, v4CachePolicy)
println("done")
// Load v6 xdb
print("Loading v6 xdb ... ")
let v6Content = File.readFrom(Path(v6DbPath))
let v6Header = newHeaderFromBytes(v6Content)
let v6Version = versionFromHeader(v6Header)
let v6Searcher = createSearcher(v6Version, v6Content, v6DbPath, v6CachePolicy)
println("done")
println("ip2region search service test program")
println("type 'quit' to exit")
let reader = getStdIn()
while (true) {
print("ip2region>> ")
let lineOpt = reader.readln()
if (lineOpt == None) {
break
}
let line = lineOpt.getOrThrow()
if (line == "quit" || line == "exit") {
break
}
if (line.size == 0) {
continue
}
let start = MonoTime.now()
let region = searchIP(line, v4Searcher, v6Searcher)
let elapsed = MonoTime.now() - start
println("${line} -> ${region} (took: ${elapsed.toMicroseconds()} us)")
}
v4Searcher.close()
v6Searcher.close()
}
func runBench(args: Array<String>) {
var dbFile = ""
var srcFile = ""
var cachePolicy = "content"
var i = 1 // skip command name
while (i < args.size) {
let arg = args[i]
if (arg == "--db" && i + 1 < args.size) {
dbFile = args[i + 1]
i = i + 2
} else if (arg == "--src" && i + 1 < args.size) {
srcFile = args[i + 1]
i = i + 2
} else if (arg == "--cache-policy" && i + 1 < args.size) {
cachePolicy = args[i + 1]
i = i + 2
} else {
i = i + 1
}
}
if (dbFile == "" || srcFile == "") {
println("ip2region bench [command options]")
println("options:")
println(" --db <path> ip2region binary xdb file path")
println(" --src <path> source ip text file path")
println(" --cache-policy <mode> cache policy: file/vectorIndex/content")
return
}
println("Loading xdb from: ${dbFile}")
let content = File.readFrom(Path(dbFile))
let header = newHeaderFromBytes(content)
let version = versionFromHeader(header)
let searcher = createSearcher(version, content, dbFile, cachePolicy)
println("Loading source data from: ${srcFile}")
let srcContent = File.readFrom(Path(srcFile))
let srcStr = String.fromUtf8(srcContent)
let lines = srcStr.split("\n")
var count: Int64 = 0
var totalCost: Int64 = 0
let tStart = MonoTime.now()
for (line in lines) {
let trimmed = line.trimAsciiStart().trimAsciiEnd()
if (trimmed.size == 0) {
continue
}
let parts = trimmed.split("|")
if (parts.size < 3) {
continue
}
let ipBytes = parseIP(parts[0])
let t0 = MonoTime.now()
let _ = searcher.search(ipBytes)
let t1 = MonoTime.now()
totalCost = totalCost + (t1 - t0).toNanoseconds()
count = count + 1
if (count % 10000 == 0) {
print(".")
}
}
let tEnd = MonoTime.now()
let totalTime = tEnd - tStart
println("")
println("Bench finished:")
println(" cachePolicy: ${cachePolicy}")
println(" total: ${count}")
println(" took: ${totalTime.toMilliseconds()} ms")
if (count > 0) {
println(" avg: ${totalCost / count / 1000} us/op")
}
searcher.close()
}
func createSearcher(version: Version, content: Array<Byte>, dbPath: String, cachePolicy: String): Searcher {
if (cachePolicy == "content") {
println("Using content buffer mode")
return Searcher(version, content, dbPath)
} else if (cachePolicy == "vectorIndex") {
println("Using vector index mode")
let vIndex = loadVectorIndex(content)
return Searcher(version, dbPath, vIndex)
} else {
println("Using file only mode")
return Searcher(version, dbPath)
}
}
func searchIP(ipStr: String, v4Searcher: Searcher, v6Searcher: Searcher): String {
let ipBytes = parseIP(ipStr)
if (ipBytes.size == 4) {
return v4Searcher.search(ipBytes)
} else if (ipBytes.size == 16) {
return v6Searcher.search(ipBytes)
}
return "Invalid IP"
}
main(args: Array<String>): Int64 {
if (args.size == 0) {
printHelp()
return 0
}
let cmd = args[0]
if (cmd == "search") {
runSearch(args)
} else if (cmd == "bench") {
runBench(args)
} else {
printHelp()
}
return 0
}

View File

@ -0,0 +1,72 @@
package ip2region.service
import std.fs.*
import ip2region.xdb.*
// Cache policy constants - match Searcher modes
public let FileOnly: Int64 = 0
public let VectorIndex: Int64 = 1
public let ContentBuff: Int64 = 2
// Config holds xdb file configuration and pre-loaded data for creating Searcher instances
public class Config {
public let cachePolicy: Int64
public let ipVersion: Version
public let xdbPath: String
public let header: Header
public let vIndex: Array<Byte>
public let cBuffer: Array<Byte>
public let searchers: Int64
public init(cachePolicy: Int64, ipVersion: Version, xdbPath: String, searchers: Int64) {
if (searchers <= 0) {
throw Exception("searchers must be > 0")
}
this.cachePolicy = cachePolicy
this.ipVersion = ipVersion
this.xdbPath = xdbPath
this.searchers = searchers
let content = File.readFrom(Path(xdbPath))
this.header = newHeaderFromBytes(content)
// Verify IP version matches
let detectedVersion = versionFromHeader(this.header)
if (detectedVersion.id != ipVersion.id) {
throw Exception("xdb file IP version mismatch: expected ${ipVersion.name}, got ${detectedVersion.name}")
}
if (cachePolicy == VectorIndex) {
this.vIndex = loadVectorIndex(content)
this.cBuffer = Array<Byte>(0, repeat: 0)
} else if (cachePolicy == ContentBuff) {
this.vIndex = Array<Byte>(0, repeat: 0)
this.cBuffer = content
} else {
this.vIndex = Array<Byte>(0, repeat: 0)
this.cBuffer = Array<Byte>(0, repeat: 0)
}
}
public init(cachePolicy: Int64, ipVersion: Version, xdbPath: String) {
this(cachePolicy, ipVersion, xdbPath, 20)
}
}
// Static factories for IPv4 and IPv6
public func newV4Config(cachePolicy: Int64, xdbPath: String, searchers: Int64): Config {
return Config(cachePolicy, IPv4, xdbPath, searchers)
}
public func newV6Config(cachePolicy: Int64, xdbPath: String, searchers: Int64): Config {
return Config(cachePolicy, IPv6, xdbPath, searchers)
}
public func newV4Config(cachePolicy: Int64, xdbPath: String): Config {
return Config(cachePolicy, IPv4, xdbPath, 20)
}
public func newV6Config(cachePolicy: Int64, xdbPath: String): Config {
return Config(cachePolicy, IPv6, xdbPath, 20)
}

View File

@ -0,0 +1,161 @@
package ip2region.service
import ip2region.xdb.*
// Ip2Region is the high-level API for searching IP addresses.
// It supports both IPv4 and IPv6 with thread-safe access via SearcherPool.
public class Ip2Region {
let v4Pool: SearcherPool
let v6Pool: SearcherPool
let v4InMemSearcher: Searcher
let v6InMemSearcher: Searcher
let hasV4: Bool
let hasV6: Bool
let v4IsInMem: Bool
let v6IsInMem: Bool
// Create Ip2Region with both v4 and v6 configurations.
public init(v4Config: Config, v6Config: Config) {
// v4 setup
if (v4Config.cachePolicy == ContentBuff) {
this.hasV4 = true
this.v4IsInMem = true
this.v4InMemSearcher = Searcher(v4Config.ipVersion, v4Config.cBuffer, v4Config.xdbPath)
this.v4Pool = SearcherPool(v4Config)
} else {
this.hasV4 = true
this.v4IsInMem = false
this.v4InMemSearcher = Searcher(v4Config.ipVersion, Array<Byte>(0, repeat: 0), v4Config.xdbPath)
this.v4Pool = SearcherPool(v4Config)
}
// v6 setup
if (v6Config.cachePolicy == ContentBuff) {
this.hasV6 = true
this.v6IsInMem = true
this.v6InMemSearcher = Searcher(v6Config.ipVersion, v6Config.cBuffer, v6Config.xdbPath)
this.v6Pool = SearcherPool(v6Config)
} else {
this.hasV6 = true
this.v6IsInMem = false
this.v6InMemSearcher = Searcher(v6Config.ipVersion, Array<Byte>(0, repeat: 0), v6Config.xdbPath)
this.v6Pool = SearcherPool(v6Config)
}
}
// Single-version: provide Config and IP version.
// Examples:
// Ip2Region(v4Config, IPv4) — IPv4 only
// Ip2Region(v6Config, IPv6) — IPv6 only
public init(config: Config, ipVersion: Version) {
if (ipVersion.id == IPv4VersionNo) {
this.hasV4 = true
this.v4IsInMem = (config.cachePolicy == ContentBuff)
if (config.cachePolicy == ContentBuff) {
this.v4InMemSearcher = Searcher(config.ipVersion, config.cBuffer, config.xdbPath)
} else {
this.v4InMemSearcher = Searcher(config.ipVersion, Array<Byte>(0, repeat: 0), config.xdbPath)
}
this.v4Pool = SearcherPool(config)
// IPv6 disabled
this.hasV6 = false
this.v6IsInMem = false
this.v6InMemSearcher = Searcher(IPv4, Array<Byte>(0, repeat: 0), config.xdbPath)
this.v6Pool = SearcherPool(Config(ContentBuff, IPv4, config.xdbPath, 1))
} else {
this.hasV6 = true
this.v6IsInMem = (config.cachePolicy == ContentBuff)
if (config.cachePolicy == ContentBuff) {
this.v6InMemSearcher = Searcher(config.ipVersion, config.cBuffer, config.xdbPath)
} else {
this.v6InMemSearcher = Searcher(config.ipVersion, Array<Byte>(0, repeat: 0), config.xdbPath)
}
this.v6Pool = SearcherPool(config)
// IPv4 disabled
this.hasV4 = false
this.v4IsInMem = false
this.v4InMemSearcher = Searcher(IPv6, Array<Byte>(0, repeat: 0), config.xdbPath)
this.v4Pool = SearcherPool(Config(ContentBuff, IPv6, config.xdbPath, 1))
}
}
// Search an IP string and return the region info.
public func search(ipStr: String): String {
try {
let ipBytes = parseIP(ipStr)
if (ipBytes.size == 4) {
return this.searchV4(ipBytes)
} else if (ipBytes.size == 16) {
return this.searchV6(ipBytes)
}
} catch (_) {
return "Invalid IP"
}
return "Invalid IP"
}
func searchV4(ip: Array<Byte>): String {
if (!this.hasV4) {
return ""
}
if (this.v4IsInMem) {
return this.v4InMemSearcher.search(ip)
}
let searcher = this.v4Pool.borrow()
let region = searcher.search(ip)
this.v4Pool.return_(searcher)
return region
}
func searchV6(ip: Array<Byte>): String {
if (!this.hasV6) {
return ""
}
if (this.v6IsInMem) {
return this.v6InMemSearcher.search(ip)
}
let searcher = this.v6Pool.borrow()
let region = searcher.search(ip)
this.v6Pool.return_(searcher)
return region
}
// close releases all resources.
public func close() {
if (this.hasV4) {
this.v4Pool.close()
}
if (this.hasV6) {
this.v6Pool.close()
}
}
// closeTimeout releases all resources with the specified timeout.
public func closeTimeout(timeout: Duration) {
if (this.hasV4) {
this.v4Pool.closeTimeout(timeout)
}
if (this.hasV6) {
this.v6Pool.closeTimeout(timeout)
}
}
}
// Create an Ip2Region instance using xdb file paths.
// Uses VectorIndex cache policy with 20 searchers by default.
public func newIp2Region(v4XdbPath: String, v6XdbPath: String): Ip2Region {
let v4Config = Config(VectorIndex, IPv4, v4XdbPath, 20)
let v6Config = Config(VectorIndex, IPv6, v6XdbPath, 20)
return Ip2Region(v4Config, v6Config)
}
// Create an IPv4-only Ip2Region service.
public func newIp2RegionV4(config: Config): Ip2Region {
return Ip2Region(config, IPv4)
}
// Create an IPv6-only Ip2Region service.
public func newIp2RegionV6(config: Config): Ip2Region {
return Ip2Region(config, IPv6)
}

View File

@ -0,0 +1,111 @@
package ip2region.service
import std.sync.*
import std.core.*
import std.time.*
import std.collection.concurrent.*
import ip2region.xdb.*
// SearcherPool is a thread-safe pool of xdb.Searcher instances.
// Uses Semaphore for backpressure and ConcurrentLinkedQueue for storage.
public class SearcherPool {
let config: Config
let queue: ConcurrentLinkedQueue<Searcher>
let semaphore: Semaphore
let closing: AtomicBool
let loanCnt: AtomicInt64
public init(config: Config) {
this.config = config
this.queue = ConcurrentLinkedQueue<Searcher>()
this.semaphore = Semaphore(config.searchers)
this.closing = AtomicBool(false)
this.loanCnt = AtomicInt64(0)
let poolSize = config.searchers
for (_ in 0..poolSize) {
let searcher = createPoolSearcher(config)
this.queue.add(searcher)
this.semaphore.release(amount: 1)
}
}
// borrow takes a Searcher from the pool, blocking until one is available.
// Throws if the pool is closing or closed.
public func borrow(): Searcher {
while (!this.closing.load()) {
if (this.semaphore.tryAcquire(amount: 1)) {
let opt = this.queue.remove()
if (opt.isNone()) {
this.semaphore.release(amount: 1)
continue
}
let s = opt.getOrThrow()
this.loanCnt.fetchAdd(1)
return s
}
sleep(Duration.millisecond * 5)
}
throw Exception("SearcherPool is closing")
}
// return_ returns a Searcher to the pool, or closes it if the pool is shutting down.
public func return_(searcher: Searcher) {
if (this.closing.load()) {
searcher.close()
} else {
this.queue.add(searcher)
this.semaphore.release(amount: 1)
}
this.loanCnt.fetchSub(1)
}
// close closes the pool with a default 10 second timeout.
public func close() {
this.closeTimeout(Duration.second * 10)
}
// closeTimeout closes the pool with the specified timeout.
public func closeTimeout(timeout: Duration) {
this.closing.store(true)
// Drain the queue: acquire all permits and close searchers
while (true) {
if (!this.semaphore.tryAcquire(amount: 1)) {
break
}
try {
let opt = this.queue.remove()
if (opt.isSome()) {
opt.getOrThrow().close()
}
} catch (_) {
break
}
}
// Wait for outstanding loans to return (with timeout)
let deadline = MonoTime.now() + timeout
while (this.loanCnt.load() > 0) {
if (MonoTime.now() >= deadline) {
break
}
sleep(Duration.millisecond * 10)
}
}
public func getLoanCount(): Int64 {
return this.loanCnt.load()
}
}
// Helper to create a Searcher based on Config's cache policy
func createPoolSearcher(config: Config): Searcher {
if (config.cachePolicy == ContentBuff) {
return Searcher(config.ipVersion, config.cBuffer, config.xdbPath)
} else if (config.cachePolicy == VectorIndex) {
return Searcher(config.ipVersion, config.xdbPath, config.vIndex)
} else {
return Searcher(config.ipVersion, config.xdbPath)
}
}

View File

@ -0,0 +1,50 @@
package ip2region.tests.service
import ip2region.xdb.*
import ip2region.service.*
@Test
class ConfigV4Tests {
@TestCase
func testCreateFileOnly() {
let cfg = Config(FileOnly, IPv4, "../../data/ip2region_v4.xdb", 5)
if (cfg.cachePolicy != FileOnly) { throw Exception("wrong cache policy") }
if (cfg.searchers != 5) { throw Exception("wrong searchers count") }
if (cfg.ipVersion.id != IPv4VersionNo) { throw Exception("wrong version") }
}
@TestCase
func testCreateVectorIndex() {
let cfg = Config(VectorIndex, IPv4, "../../data/ip2region_v4.xdb", 10)
if (cfg.vIndex.size == 0) { throw Exception("vIndex should be loaded") }
let expectedSize = VectorIndexRows * VectorIndexCols * VectorIndexSize
if (cfg.vIndex.size != expectedSize) {
throw Exception("vIndex size mismatch: ${cfg.vIndex.size} vs ${expectedSize}")
}
}
@TestCase
func testCreateContentBuff() {
let cfg = Config(ContentBuff, IPv4, "../../data/ip2region_v4.xdb", 3)
if (cfg.cBuffer.size == 0) { throw Exception("cBuffer should be loaded") }
}
@TestCase
func testFactoryV4() {
let cfg = newV4Config(VectorIndex, "../../data/ip2region_v4.xdb", 8)
if (cfg.ipVersion.id != IPv4VersionNo) { throw Exception("should be IPv4") }
if (cfg.searchers != 8) { throw Exception("wrong searchers") }
}
@TestCase
func testFactoryV6() {
let cfg = newV6Config(VectorIndex, "../../data/ip2region_v6.xdb", 8)
if (cfg.ipVersion.id != IPv6VersionNo) { throw Exception("should be IPv6") }
}
@TestCase
func testFactoryDefaultSearchers() {
let cfg = newV4Config(ContentBuff, "../../data/ip2region_v4.xdb")
if (cfg.searchers != 20) { throw Exception("default searchers should be 20") }
}
}

View File

@ -0,0 +1,108 @@
package ip2region.tests.service
import ip2region.xdb.*
import ip2region.service.*
@Test
class Ip2RegionBasicTests {
@TestCase
func testSearchWithContentBuff() {
let v4Cfg = Config(ContentBuff, IPv4, "../../data/ip2region_v4.xdb", 1)
let v6Cfg = Config(ContentBuff, IPv6, "../../data/ip2region_v6.xdb", 1)
let region = Ip2Region(v4Cfg, v6Cfg)
let r1 = region.search("220.181.108.183")
if (r1.size == 0) {
throw Exception("v4 search should return result")
}
let r2 = region.search("2408:8266:100:1000::")
if (r2.size == 0) {
throw Exception("v6 search should return result")
}
region.close()
}
@TestCase
func testSearchWithVectorIndex() {
let v4Cfg = Config(VectorIndex, IPv4, "../../data/ip2region_v4.xdb", 5)
let v6Cfg = Config(VectorIndex, IPv6, "../../data/ip2region_v6.xdb", 5)
let region = Ip2Region(v4Cfg, v6Cfg)
let r = region.search("220.181.108.183")
if (r.size == 0) {
throw Exception("v4 search should return result")
}
region.close()
}
@TestCase
func testInvalidIP() {
let v4Cfg = Config(ContentBuff, IPv4, "../../data/ip2region_v4.xdb", 1)
let v6Cfg = Config(ContentBuff, IPv6, "../../data/ip2region_v6.xdb", 1)
let region = Ip2Region(v4Cfg, v6Cfg)
let r = region.search("invalid")
if (r != "Invalid IP") {
throw Exception("expected 'Invalid IP', got '${r}'")
}
region.close()
}
@TestCase
func testClose() {
let v4Cfg = Config(ContentBuff, IPv4, "../../data/ip2region_v4.xdb", 1)
let v6Cfg = Config(ContentBuff, IPv6, "../../data/ip2region_v6.xdb", 1)
let region = Ip2Region(v4Cfg, v6Cfg)
region.search("220.181.108.183")
region.close()
}
@TestCase
func testV4OnlyInit() {
let cfg = Config(VectorIndex, IPv4, "../../data/ip2region_v4.xdb", 3)
let region = Ip2Region(cfg, IPv4)
let r = region.search("220.181.108.183")
if (r.size == 0) {
throw Exception("v4-only region should find v4 IP")
}
let r6 = region.search("2408:8266:100:1000::")
if (r6.size != 0) {
throw Exception("v4-only region should not find v6 IP")
}
region.close()
}
@TestCase
func testV6OnlyInit() {
let cfg = Config(VectorIndex, IPv6, "../../data/ip2region_v6.xdb", 3)
let region = Ip2Region(cfg, IPv6)
let r = region.search("2408:8266:100:1000::")
if (r.size == 0) {
throw Exception("v6-only region should find v6 IP")
}
let r4 = region.search("220.181.108.183")
if (r4.size != 0) {
throw Exception("v6-only region should not find v4 IP")
}
region.close()
}
@TestCase
func testNewV4Factory() {
let cfg = Config(ContentBuff, IPv4, "../../data/ip2region_v4.xdb", 2)
let region = newIp2RegionV4(cfg)
let r = region.search("220.181.108.183")
if (r.size == 0) {
throw Exception("newIp2RegionV4 should work")
}
region.close()
}
@TestCase
func testNewV6Factory() {
let cfg = Config(ContentBuff, IPv6, "../../data/ip2region_v6.xdb", 2)
let region = newIp2RegionV6(cfg)
let r = region.search("2408:8266:100:1000::")
if (r.size == 0) {
throw Exception("newIp2RegionV6 should work")
}
region.close()
}
}

View File

@ -0,0 +1,55 @@
package ip2region.tests.service
import ip2region.xdb.*
import ip2region.service.*
@Test
class SearcherPoolTests {
@TestCase
func testBorrowAndReturn() {
let cfg = Config(ContentBuff, IPv4, "../../data/ip2region_v4.xdb", 3)
let pool = SearcherPool(cfg)
let s = pool.borrow()
if (pool.getLoanCount() != 1) {
throw Exception("loan count should be 1, got ${pool.getLoanCount()}")
}
pool.return_(s)
if (pool.getLoanCount() != 0) {
throw Exception("loan count should be 0 after return")
}
pool.close()
}
@TestCase
func testBorrowAll() {
let cfg = Config(ContentBuff, IPv4, "../../data/ip2region_v4.xdb", 5)
let pool = SearcherPool(cfg)
let s1 = pool.borrow()
let s2 = pool.borrow()
let s3 = pool.borrow()
let s4 = pool.borrow()
let s5 = pool.borrow()
if (pool.getLoanCount() != 5) {
throw Exception("should have 5 loans, got ${pool.getLoanCount()}")
}
pool.return_(s5)
pool.return_(s4)
pool.return_(s3)
pool.return_(s2)
pool.return_(s1)
if (pool.getLoanCount() != 0) {
throw Exception("all should be returned, got ${pool.getLoanCount()}")
}
pool.close()
}
@TestCase
func testClosePool() {
let cfg = Config(ContentBuff, IPv4, "../../data/ip2region_v4.xdb", 2)
let pool = SearcherPool(cfg)
let s = pool.borrow()
pool.return_(s)
pool.close()
pool.close()
}
}

View File

@ -0,0 +1 @@
package ip2region.tests

View File

@ -0,0 +1,83 @@
package ip2region.tests.xdb
import ip2region.xdb.*
@Test
class HeaderTests {
func makeHeaderBytes(): Array<Byte> {
let buf = Array<Byte>(256, repeat: 0)
var v: Byte
v = 3; buf[0] = v; v = 0; buf[1] = v
v = 1; buf[2] = v; v = 0; buf[3] = v
v = 0xA0; buf[4] = v; v = 0x86; buf[5] = v; v = 0x01; buf[6] = v; v = 0x00; buf[7] = v
v = 0xE8; buf[8] = v; v = 0x03; buf[9] = v; v = 0x00; buf[10] = v; v = 0x00; buf[11] = v
v = 0xD0; buf[12] = v; v = 0x07; buf[13] = v; v = 0x00; buf[14] = v; v = 0x00; buf[15] = v
v = 4; buf[16] = v; v = 0; buf[17] = v
v = 0; buf[18] = v; v = 0; buf[19] = v
return buf
}
@TestCase
func testNewHeaderFromBytes() {
let buf = makeHeaderBytes()
let h = newHeaderFromBytes(buf)
if (h.version != UInt16(3)) {
throw Exception("expected version 3, got ${h.version}")
}
if (h.ipVersion != UInt16(4)) {
throw Exception("expected ipVersion 4, got ${h.ipVersion}")
}
}
@TestCase
func testHeaderToString() {
let buf = makeHeaderBytes()
let h = newHeaderFromBytes(buf)
let s = h.toString()
if (s.size == 0) {
throw Exception("toString should not be empty")
}
}
@TestCase
func testVersionFromHeaderV4() {
let buf = makeHeaderBytes()
let h = newHeaderFromBytes(buf)
let v = versionFromHeader(h)
if (v.id != IPv4VersionNo) {
throw Exception("expected IPv4, got ${v.name}")
}
}
@TestCase
func testVersionFromHeaderV6() {
let buf = makeHeaderBytes()
var v: Byte = 6; buf[16] = v
let h = newHeaderFromBytes(buf)
let ver = versionFromHeader(h)
if (ver.id != IPv6VersionNo) {
throw Exception("expected IPv6, got ${ver.name}")
}
}
@TestCase
func testVersionFromHeader20() {
let buf = makeHeaderBytes()
var v: Byte = 2; buf[0] = v
v = 0; buf[16] = v
let h = newHeaderFromBytes(buf)
let ver = versionFromHeader(h)
if (ver.id != IPv4VersionNo) {
throw Exception("expected IPv4 for v2.0")
}
}
@TestCase
func testHeaderConstants() {
if (HeaderInfoLength != 256) { throw Exception("expected 256") }
if (VectorIndexRows != 256) { throw Exception("expected 256") }
if (VectorIndexSize != 8) { throw Exception("expected 8") }
if (IPv4SegmentIndexSize != 14) { throw Exception("expected 14") }
if (IPv6SegmentIndexSize != 38) { throw Exception("expected 38") }
}
}

View File

@ -0,0 +1,132 @@
package ip2region.tests.xdb
import std.fs.*
import ip2region.xdb.*
@Test
class SearcherV4Tests {
var v4Content: Array<Byte> = Array<Byte>(0, repeat: 0)
var initialized: Bool = false
@BeforeAll
func setup() {
this.v4Content = File.readFrom(Path("../../data/ip2region_v4.xdb"))
this.initialized = true
}
@TestCase
func testSearchContentMode() {
if (!this.initialized) { return }
let header = newHeaderFromBytes(this.v4Content)
let version = versionFromHeader(header)
let searcher = Searcher(version, this.v4Content, "../../data/ip2region_v4.xdb")
let ip = parseIP("220.181.108.183")
let region = searcher.search(ip)
if (region.size == 0) {
throw Exception("should find region for known IP")
}
searcher.close()
}
@TestCase
func testSearchVectorIndexMode() {
if (!this.initialized) { return }
let header = newHeaderFromBytes(this.v4Content)
let version = versionFromHeader(header)
let vIndex = loadVectorIndex(this.v4Content)
let searcher = Searcher(version, "../../data/ip2region_v4.xdb", vIndex)
let ip = parseIP("220.181.108.183")
let region = searcher.search(ip)
if (region.size == 0) {
throw Exception("should find region for known IP")
}
searcher.close()
}
@TestCase
func testSearchFileMode() {
if (!this.initialized) { return }
let header = newHeaderFromBytes(this.v4Content)
let version = versionFromHeader(header)
let searcher = Searcher(version, "../../data/ip2region_v4.xdb")
let ip = parseIP("220.181.108.183")
let region = searcher.search(ip)
if (region.size == 0) {
throw Exception("should find region for known IP")
}
searcher.close()
}
@TestCase
func testAllModesSameResult() {
if (!this.initialized) { return }
let header = newHeaderFromBytes(this.v4Content)
let version = versionFromHeader(header)
let vIndex = loadVectorIndex(this.v4Content)
let s1 = Searcher(version, this.v4Content, "../../data/ip2region_v4.xdb")
let s2 = Searcher(version, "../../data/ip2region_v4.xdb", vIndex)
let s3 = Searcher(version, "../../data/ip2region_v4.xdb")
let ip = parseIP("1.2.3.4")
let r1 = s1.search(ip)
let r2 = s2.search(ip)
let r3 = s3.search(ip)
s1.close(); s2.close(); s3.close()
if (r1 != r2 || r2 != r3) {
throw Exception("all modes should return same result")
}
}
@TestCase
func testSearchByString() {
if (!this.initialized) { return }
let header = newHeaderFromBytes(this.v4Content)
let version = versionFromHeader(header)
let searcher = Searcher(version, this.v4Content, "../../data/ip2region_v4.xdb")
let region = searcher.searchByString("220.181.108.183")
if (region.size == 0) {
throw Exception("searchByString should find region")
}
searcher.close()
}
}
@Test
class SearcherV6Tests {
var v6Content: Array<Byte> = Array<Byte>(0, repeat: 0)
var initialized: Bool = false
@BeforeAll
func setup() {
this.v6Content = File.readFrom(Path("../../data/ip2region_v6.xdb"))
this.initialized = true
}
@TestCase
func testSearchV6ContentMode() {
if (!this.initialized) { return }
let header = newHeaderFromBytes(this.v6Content)
let version = versionFromHeader(header)
let searcher = Searcher(version, this.v6Content, "../../data/ip2region_v6.xdb")
let ip = parseIP("2408:8266:100:1000::")
let region = searcher.search(ip)
if (region.size == 0) {
throw Exception("should find region for known IPv6")
}
searcher.close()
}
@TestCase
func testSearchV6VectorIndexMode() {
if (!this.initialized) { return }
let header = newHeaderFromBytes(this.v6Content)
let version = versionFromHeader(header)
let vIndex = loadVectorIndex(this.v6Content)
let searcher = Searcher(version, "../../data/ip2region_v6.xdb", vIndex)
let ip = parseIP("2408:8266:100:1000::")
let region = searcher.search(ip)
if (region.size == 0) {
throw Exception("should find region for known IPv6")
}
searcher.close()
}
}

View File

@ -0,0 +1,133 @@
package ip2region.tests.xdb
import ip2region.xdb.*
@Test
class UtilTests {
@TestCase
func testParseIPv4() {
let ip = parseIP("1.2.3.4")
if (ip.size != 4) {
throw Exception("expected 4 bytes, got ${ip.size}")
}
if (Int64(ip[0]) != 1 || Int64(ip[1]) != 2 || Int64(ip[2]) != 3 || Int64(ip[3]) != 4) {
throw Exception("IPv4 parse mismatch")
}
}
@TestCase
func testParseIPv6() {
let ip = parseIP("::1")
if (ip.size != 16) {
throw Exception("expected 16 bytes for IPv6, got ${ip.size}")
}
}
@TestCase
func testIPCompareEqual() {
let a = Array<Byte>(4, repeat: 0)
var v: Byte = 1; a[0] = v; v = 2; a[1] = v; v = 3; a[2] = v; v = 4; a[3] = v
let b = Array<Byte>(4, repeat: 0)
v = 1; b[0] = v; v = 2; b[1] = v; v = 3; b[2] = v; v = 4; b[3] = v
let r = ipCompare(a, b)
if (r != 0) {
throw Exception("expected 0, got ${r}")
}
}
@TestCase
func testIPCompareLess() {
let a = Array<Byte>(4, repeat: 0)
var v: Byte = 1; a[0] = v; v = 2; a[1] = v; v = 3; a[2] = v; v = 4; a[3] = v
let b = Array<Byte>(4, repeat: 0)
v = 1; b[0] = v; v = 2; b[1] = v; v = 3; b[2] = v; v = 5; b[3] = v
let r = ipCompare(a, b)
if (r != -1) {
throw Exception("expected -1, got ${r}")
}
}
@TestCase
func testIPCompareGreater() {
let a = Array<Byte>(4, repeat: 0)
var v: Byte = 1; a[0] = v; v = 2; a[1] = v; v = 3; a[2] = v; v = 5; a[3] = v
let b = Array<Byte>(4, repeat: 0)
v = 1; b[0] = v; v = 2; b[1] = v; v = 3; b[2] = v; v = 4; b[3] = v
let r = ipCompare(a, b)
if (r != 1) {
throw Exception("expected 1, got ${r}")
}
}
@TestCase
func testReadLEUint32() {
let buf = Array<Byte>(4, repeat: 0)
var v: Byte = 0x78; buf[0] = v; v = 0x56; buf[1] = v; v = 0x34; buf[2] = v; v = 0x12; buf[3] = v
let val = readLEUint32(buf, 0)
let expected: Int64 = 0x12345678
if (val != expected) {
throw Exception("expected 0x12345678, got ${val}")
}
}
@TestCase
func testReadLEUint16() {
let buf = Array<Byte>(2, repeat: 0)
var v: Byte = 0x34; buf[0] = v; v = 0x12; buf[1] = v
let val = readLEUint16(buf, 0)
if (val != 0x1234) {
throw Exception("expected 0x1234, got ${val}")
}
}
@TestCase
func testIpToString() {
let ip = Array<Byte>(4, repeat: 0)
var v: Byte = 127; ip[0] = v; v = 0; ip[1] = v; v = 0; ip[2] = v; v = 1; ip[3] = v
let s = ipToString(ip)
if (s != "127.0.0.1") {
throw Exception("expected 127.0.0.1, got ${s}")
}
}
@TestCase
func testCompareIPv4() {
let ip = Array<Byte>(4, repeat: 0)
var v: Byte = 1; ip[0] = v; v = 2; ip[1] = v; v = 3; ip[2] = v; v = 4; ip[3] = v
let entry = Array<Byte>(8, repeat: 0)
v = 4; entry[0] = v; v = 3; entry[1] = v; v = 2; entry[2] = v; v = 1; entry[3] = v
v = 8; entry[4] = v; v = 7; entry[5] = v; v = 6; entry[6] = v; v = 5; entry[7] = v
let r = compareIPv4(ip, entry, 0)
if (r != 0) {
throw Exception("expected equal, got ${r}")
}
}
@TestCase
func testCompareIPv6() {
let ip = Array<Byte>(16, repeat: 0)
var v: Byte = 0x20; ip[0] = v; v = 0x01; ip[1] = v; v = 0x0d; ip[2] = v; v = 0xb8; ip[3] = v
let entry = Array<Byte>(16, repeat: 0)
v = 0x20; entry[0] = v; v = 0x01; entry[1] = v; v = 0x0d; entry[2] = v; v = 0xb8; entry[3] = v
let r = compareIPv6(ip, entry, 0)
if (r != 0) {
throw Exception("expected equal, got ${r}")
}
}
@TestCase
func testLoadVectorIndex() {
let viSize = VectorIndexRows * VectorIndexCols * VectorIndexSize
let cBuff = Array<Byte>(HeaderInfoLength + viSize, repeat: 0)
var v: Byte = 0x42
cBuff[HeaderInfoLength] = v
let vi = loadVectorIndex(cBuff)
if (vi.size != viSize) {
throw Exception("expected size ${viSize}, got ${vi.size}")
}
let firstVal = Int64(vi[0])
if (firstVal != 0x42) {
throw Exception("expected first byte 0x42, got ${firstVal}")
}
}
}

View File

@ -0,0 +1,77 @@
package ip2region.xdb
// xdb file format constants
public let HeaderInfoLength: Int64 = 256
public let VectorIndexRows: Int64 = 256
public let VectorIndexCols: Int64 = 256
public let VectorIndexSize: Int64 = 8
public let IPv4SegmentIndexSize: Int64 = 14 // 4 + 4 + 2 + 4
public let IPv6SegmentIndexSize: Int64 = 38 // 16 + 16 + 2 + 4
public let Structure20: Int64 = 2
public let Structure30: Int64 = 3
// xdb header parsed from the first 256 bytes of the xdb file
public class Header {
public let version: UInt16
public let indexPolicy: UInt16
public let createdAt: UInt32
public let startIndexPtr: UInt32
public let endIndexPtr: UInt32
public let ipVersion: UInt16
public let runtimePtrBytes: UInt16
public init(
ver: UInt16,
idxPolicy: UInt16,
createAt: UInt32,
sPtr: UInt32,
ePtr: UInt32,
ipVer: UInt16,
rtPtrBytes: UInt16
) {
this.version = ver
this.indexPolicy = idxPolicy
this.createdAt = createAt
this.startIndexPtr = sPtr
this.endIndexPtr = ePtr
this.ipVersion = ipVer
this.runtimePtrBytes = rtPtrBytes
}
public func toString(): String {
return "{version: ${this.version}, index_policy: ${this.indexPolicy}, " +
"created_at: ${this.createdAt}, start_index_ptr: ${this.startIndexPtr}, " +
"end_index_ptr: ${this.endIndexPtr}, ip_version: ${this.ipVersion}, " +
"runtime_ptr_bytes: ${this.runtimePtrBytes}}"
}
}
// Read a little-endian UInt16 from byte array at offset
func readHeaderUInt16(buf: Array<Byte>, offset: Int64): UInt16 {
let b0 = UInt16(buf[offset])
let b1 = UInt16(buf[offset + 1]) << 8
return b0 | b1
}
// Read a little-endian UInt32 from byte array at offset
func readHeaderUInt32(buf: Array<Byte>, offset: Int64): UInt32 {
let b0 = UInt32(buf[offset])
let b1 = UInt32(buf[offset + 1]) << 8
let b2 = UInt32(buf[offset + 2]) << 16
let b3 = UInt32(buf[offset + 3]) << 24
return b0 | b1 | b2 | b3
}
// Create a Header from raw byte buffer (first 256 bytes of xdb file)
public func newHeaderFromBytes(buf: Array<Byte>): Header {
return Header(
readHeaderUInt16(buf, 0),
readHeaderUInt16(buf, 2),
readHeaderUInt32(buf, 4),
readHeaderUInt32(buf, 8),
readHeaderUInt32(buf, 12),
readHeaderUInt16(buf, 16),
readHeaderUInt16(buf, 18)
)
}

View File

@ -0,0 +1,146 @@
package ip2region.xdb
import std.fs.*
import std.io.SeekPosition
// Searcher for ip2region xdb database
public class Searcher {
let version: Version
let handle: File
var ioCount: Int64
let vectorIndex: Array<Byte>
let contentBuff: Array<Byte>
let mode: Int64
public let FileOnlyMode: Int64 = 0
public let VectorIndexMode: Int64 = 1
public let ContentBuffMode: Int64 = 2
public init(ver: Version, dbFile: String) {
this.version = ver
this.handle = File(dbFile, Read)
this.vectorIndex = Array<Byte>(0, repeat: 0)
this.contentBuff = Array<Byte>(0, repeat: 0)
this.mode = FileOnlyMode
this.ioCount = 0
}
public init(ver: Version, dbFile: String, vIndex: Array<Byte>) {
this.version = ver
this.handle = File(dbFile, Read)
this.vectorIndex = vIndex
this.contentBuff = Array<Byte>(0, repeat: 0)
this.mode = VectorIndexMode
this.ioCount = 0
}
public init(ver: Version, cBuff: Array<Byte>, dbPath: String) {
this.version = ver
this.handle = File(dbPath, Read)
this.vectorIndex = Array<Byte>(0, repeat: 0)
this.contentBuff = cBuff
this.mode = ContentBuffMode
this.ioCount = 0
}
public func close() {
this.handle.close()
}
public func getIOCount(): Int64 {
return this.ioCount
}
public func searchByString(ipStr: String): String {
let ipBytes = parseIP(ipStr)
return this.search(ipBytes)
}
public func search(ip: Array<Byte>): String {
this.ioCount = 0
let il0 = Int64(ip[0])
let il1 = Int64(ip[1])
let idx = il0 * VectorIndexCols * VectorIndexSize + il1 * VectorIndexSize
var sPtr: Int64 = 0
var ePtr: Int64 = 0
if (this.mode == VectorIndexMode) {
sPtr = readLEUint32(this.vectorIndex, idx)
ePtr = readLEUint32(this.vectorIndex, idx + 4)
} else if (this.mode == ContentBuffMode) {
sPtr = readLEUint32(this.contentBuff, HeaderInfoLength + idx)
ePtr = readLEUint32(this.contentBuff, HeaderInfoLength + idx + 4)
} else {
let buff = Array<Byte>(VectorIndexSize, repeat: 0)
this.readFromFile(HeaderInfoLength + idx, buff)
sPtr = readLEUint32(buff, 0)
ePtr = readLEUint32(buff, 4)
}
if (sPtr == 0 || ePtr == 0) {
return ""
}
let segIndexSize = this.version.segmentIndexSize
let bytes = this.version.bytes
let dBytes = bytes * 2
var dataLen: Int64 = 0
var dataPtr: Int64 = 0
var l: Int64 = 0
var h: Int64 = (ePtr - sPtr) / segIndexSize
let buff = Array<Byte>(segIndexSize, repeat: 0)
while (l <= h) {
let m = (l + h) / 2
let p = sPtr + m * segIndexSize
this.read(p, buff)
this.ioCount = this.ioCount + 1
if (this.compareIP(ip, buff, 0) < 0) {
h = m - 1
} else if (this.compareIP(ip, buff, bytes) > 0) {
l = m + 1
} else {
dataLen = readLEUint16(buff, dBytes)
dataPtr = readLEUint32(buff, dBytes + 2)
break
}
}
if (dataLen == 0) {
return ""
}
let regionBuff = Array<Byte>(dataLen, repeat: 0)
this.read(dataPtr, regionBuff)
return String.fromUtf8(regionBuff)
}
func read(offset: Int64, buff: Array<Byte>) {
if (this.mode == ContentBuffMode) {
for (i in 0..buff.size) {
buff[i] = this.contentBuff[offset + i]
}
} else {
this.readFromFile(offset, buff)
()
}
}
func readFromFile(offset: Int64, buff: Array<Byte>) {
this.handle.seek(SeekPosition.Begin(offset))
this.handle.read(buff)
()
}
func compareIP(ip: Array<Byte>, entry: Array<Byte>, offset: Int64): Int64 {
if (this.version.id == IPv4VersionNo) {
return compareIPv4(ip, entry, offset)
}
return compareIPv6(ip, entry, offset)
}
}

View File

@ -0,0 +1,108 @@
package ip2region.xdb
import std.net.IPAddress
// --- IP address parsing and formatting
// Parse IP string to byte array
// For IPv4, returns 4 bytes in big-endian order (network byte order)
// For IPv6, returns 16 bytes in big-endian order
public func parseIP(ip: String): Array<Byte> {
let addr = IPAddress.parse(ip)
return addr.getAddressBytes()
}
// Convert IP byte array back to string
public func ipToString(ip: Array<Byte>): String {
if (ip.size == 4) {
return "${Int64(ip[0])}.${Int64(ip[1])}.${Int64(ip[2])}.${Int64(ip[3])}"
}
// IPv6 simplified format
var result = ""
for (i in 0..ip.size) {
if (i > 0 && i % 2 == 0) {
result = result + ":"
}
result = result + "${Int64(ip[i])}"
}
return result
}
// Compare two IP byte arrays.
// Returns: -1 if ip1 < ip2, 0 if equal, 1 if ip1 > ip2
public func ipCompare(ip1: Array<Byte>, ip2: Array<Byte>): Int64 {
var len = ip1.size
if (ip2.size < len) {
len = ip2.size
}
for (i in 0..len) {
if (ip1[i] < ip2[i]) {
return -1
} else if (ip1[i] > ip2[i]) {
return 1
}
}
if (ip1.size < ip2.size) {
return -1
} else if (ip1.size > ip2.size) {
return 1
}
return 0
}
// --- Binary data reading helpers for xdb format
// Read a little-endian UInt32 from a byte buffer at the given offset
public func readLEUint32(buf: Array<Byte>, offset: Int64): Int64 {
let b0 = Int64(buf[offset])
let b1 = Int64(buf[offset + 1]) << 8
let b2 = Int64(buf[offset + 2]) << 16
let b3 = Int64(buf[offset + 3]) << 24
return b0 | b1 | b2 | b3
}
// Read a little-endian UInt16 from a byte buffer at the given offset
public func readLEUint16(buf: Array<Byte>, offset: Int64): Int64 {
let b0 = Int64(buf[offset])
let b1 = Int64(buf[offset + 1]) << 8
return b0 | b1
}
// Compare IPv4 address (big-endian) with xdb stored IPv4 (little-endian)
// xdb stores IPv4 in little-endian, so we compare ip[i] with xdbEntry[3-i]
public func compareIPv4(ip: Array<Byte>, xdbEntry: Array<Byte>, entryOffset: Int64): Int64 {
for (i in 0..4) {
let ipByte = Int64(ip[i])
let xdbByte = Int64(xdbEntry[entryOffset + 3 - i])
if (ipByte < xdbByte) {
return -1
} else if (ipByte > xdbByte) {
return 1
}
}
return 0
}
// Load the vector index from a content buffer
// The vector index starts at HeaderInfoLength and has VectorIndexRows * VectorIndexCols * VectorIndexSize bytes
public func loadVectorIndex(cBuff: Array<Byte>): Array<Byte> {
let start = HeaderInfoLength
let len = VectorIndexRows * VectorIndexCols * VectorIndexSize
let vi = Array<Byte>(len, repeat: 0)
for (i in 0..len) {
vi[i] = cBuff[start + i]
}
return vi
}
// Compare IPv6 address (big-endian) with xdb stored IPv6 (big-endian)
public func compareIPv6(ip: Array<Byte>, xdbEntry: Array<Byte>, entryOffset: Int64): Int64 {
for (i in 0..16) {
if (ip[i] < xdbEntry[entryOffset + i]) {
return -1
} else if (ip[i] > xdbEntry[entryOffset + i]) {
return 1
}
}
return 0
}

View File

@ -0,0 +1,41 @@
package ip2region.xdb
// IP version definitions for ip2region xdb searcher
public class Version {
public let id: Int64
public let name: String
public let bytes: Int64
public let segmentIndexSize: Int64
public init(id: Int64, name: String, bytes: Int64, segmentIndexSize: Int64) {
this.id = id
this.name = name
this.bytes = bytes
this.segmentIndexSize = segmentIndexSize
}
public func toString(): String {
return "{id: ${this.id}, name: ${this.name}, bytes: ${this.bytes}, " +
"segment_index_size: ${this.segmentIndexSize}}"
}
}
public let IPv4VersionNo: Int64 = 4
public let IPv6VersionNo: Int64 = 6
// Pre-defined IP versions
public let IPv4 = Version(4, "IPv4", 4, 14)
public let IPv6 = Version(6, "IPv6", 16, 38)
// Determine IP version from header info
public func versionFromHeader(header: Header): Version {
// Old structure (2.0) with IPv4 only
if (Int64(header.version) == Structure20) {
return IPv4
}
// Structure 3.0+
if (Int64(header.ipVersion) == IPv4VersionNo) {
return IPv4
}
return IPv6
}

1
binding/cpp/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
bin/

View File

@ -1,11 +1,27 @@
all: xdb_search xdb_bench all: bin header search bench make edit
xdb_search: xdb_search.cc xdb_search_test.cc FILES=$(wildcard src/*.cc)
g++ -std=c++11 -O2 $^ -o $@
xdb_bench: xdb_search.cc xdb_bench.cc xdb_bench_test.cc bin:
g++ -std=c++11 -O2 $^ -o $@ 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: clean:
rm -f xdb_search xdb_bench rm -rf bin

137
binding/cpp/README.md Normal file
View File

@ -0,0 +1,137 @@
:globe_with_meridians: [中文简体](README_zh.md) | [English](README.md)
# ip2region C++ query client
## 0. File Description
```
Makefile --------- Build
src ------------------ Source directory
src/base.* ----------- Constants and utility functions
src/ip.* ------------- IP processing implementation
src/header.* --------- xdb header parsing implementation
src/search.* --------- xdb search implementation
src/bench.* ---------- Search benchmarking implementation
src/make.* ----------- xdb file generation implementation
src/edit.* ----------- Raw data editing implementation
test ---------------- Test directory
test/header.cc ------ Test header
test/search.cc ------ Test search
test/bench.cc ------- Benchmarking
test/make.cc -------- Generate xdb file
test/edit_v4.cc ----- Test raw data editing (ipv4)
test/edit_v6.cc ----- Test raw data editing (ipv6)
bin --------------- Executable directory (generated via make)
bin/header -------- Test header
bin/search -------- Test search
bin/bench --------- Benchmarking
bin/make ---------- Generate xdb file
bin/edit_v4 ------- Test raw data editing (ipv4)
bin/edit_v6 ------- Test raw data editing (ipv6)
readme.md --------- readme
```
## 1. Compilation
```
$ make
```
## 2. Search
### 2.1 Example
```cpp
#include "src/search.h"
// IP Version: xdb::ipv4 xdb::ipv6
// Policy: xdb::policy_file xdb::policy_vector xdb::policy_content
// No cache Partial cache Full cache
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 --- Compile
// $ ./a.out ------------- Test
// Japan|Tokyo|Asagaya-minami|WIDE Project|JP
```
### 2.2 Test xdb Header
```
$ ./bin/header
Test IPv4
Version: 3
Cache Policy: 1
File Generation Time: 2025-09-06 02:24:16
Index Start Address: 955933
Index End Address: 11042415
IP Version: 4
Pointer Bytes: 4
Test IPv6
Version: 3
Cache Policy: 1
File Generation Time: 2025-10-17 04:41:04
Index Start Address: 3094259
Index End Address: 36258303
IP Version: 6
Pointer Bytes: 4
```
### 2.3 Test Search
```
$ ./bin/search
Test IPv4 No cache: Success
Test IPv4 Partial cache: Success
Test IPv4 Full cache: Success
Test IPv6 No cache: Success
Test IPv6 Partial cache: Success
Test IPv6 Full cache: Success
```
## 3. Benchmarking and Correctness Verification
```
./bin/bench
Test IPv4, No cache, total: 3910284, took: 27.60s, cost: 6.59μs/op, io count: 28227147
Test IPv4, Partial cache, total: 3910284, took: 21.85s, cost: 5.15μs/op, io count: 24316863
Test IPv4, Full cache, total: 3910284, took: 2.26s, cost: 0.25μs/op, io count: 0
Test IPv6, No cache, total: 4792520, took: 100.40s, cost: 20.22μs/op, io count: 80758866
Test IPv6, Partial cache, total: 4792520, took: 93.06s, cost: 18.71μs/op, io count: 75966346
Test IPv6, Full cache, total: 4792520, took: 6.24s, cost: 0.81μs/op, io count: 0
```
## 4. Generate xdb File
### 4.1 Generate xdb File
```
$ ./bin/make
Generate ipv4 xdb file, took: 0.57s
Generate ipv6 xdb file, took: 1.24s
```
## 5. Raw Data Editing
### 5.1. Instructions for Use
* New IP attribution files can contain empty lines
* New IP attribution files can be out of order; the program will automatically sort them
* New IP attribution files can overlap; as long as there is no ambiguity, the program will automatically merge them
* The final result will automatically merge adjacent lines with the same attribution
* For the following tests, the original file uses the data file provided in the repository, and the new file uses 1.txt in the current directory

125
binding/cpp/README_zh.md Normal file
View File

@ -0,0 +1,125 @@
:globe_with_meridians: [中文简体](README_zh.md) | [English](README.md)
# ip2region 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 示例
```cpp
#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 ------------- 测试
// Japan|Tokyo|Asagaya-minami|WIDE Project|JP
```
### 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
```
## 5. 原始数据编辑
### 5.1. 使用说明
* 新的IP归属地文件可以包含空行
* 新的IP归属地文件顺序可以乱序, 程序会自动排序
* 新的IP归属地文件顺序可以重叠, 只要无二义性, 程序会自动合并
* 最终的结果会将相邻的且归属地相同的行自动合并
* 以下测试, 原文件使用仓库自带的数据文件, 新文件使用当前目录下的 1.txt

View File

@ -1,108 +0,0 @@
# ip2region xdb C++ 查询客户端实现
## 使用方式
### 完全基于文件的查询
```
#include <iostream>
#include "xdb_search.h"
int main(int argc, char* argv[]) {
char file_name[] = "../../data/ip2region.xdb";
char ip[] = "1.2.3.4";
xdb_search_t xdb(file_name);
xdb.init_file();
std::cout << xdb.search(ip) << std::endl;
return 0;
}
```
### 缓存 `vector_index` 索引
```
#include <iostream>
#include "xdb_search.h"
int main(int argc, char* argv[]) {
char file_name[] = "../../data/ip2region.xdb";
char ip[] = "1.2.3.4";
xdb_search_t xdb(file_name);
xdb.init_vector_index();
std::cout << xdb.search(ip) << std::endl;
return 0;
}
```
### 缓存整个 `xdb` 数据
```
#include <iostream>
#include "xdb_search.h"
int main(int argc, char* argv[]) {
char file_name[] = "../../data/ip2region.xdb";
char ip[] = "1.2.3.4";
xdb_search_t xdb(file_name);
xdb.init_content();
std::cout << xdb.search(ip) << std::endl;
return 0;
}
```
## 测试程序编译
1. 切换到当前目录
2. 编译
```
$ make
g++ -std=c++11 -O2 xdb_search.cc xdb_search_test.cc -o xdb_search
g++ -std=c++11 -O2 xdb_search.cc xdb_bench.cc xdb_bench_test.cc -o xdb_bench
```
## 测试查询
### 说明
```
$ ./xdb_search --help
./xdb_search [command options]
options:
--db string ip2region binary xdb file path
--cache-policy string cache policy: file/vector_index/content
--help print help
```
### 测试
```
$ ./xdb_search --db ../../data/ip2region.xdb --cache-policy vector_index
cache policy : vector_index
ip2region>> 1.2.3.4
美国|0|华盛顿|0|谷歌
```
## bench 测试
### 说明
```
$ ./xdb_bench --help
./xdb_bench [command options]
options:
--db string ip2region binary xdb file path
--src string source ip text file path
--cache-policy string cache policy: file/vector_index/content
--help print help
```
### 测试
```
$ ./xdb_bench --db ../../data/ip2region.xdb --src ../../data/ip.merge.txt --cache-policy content
total: 3419220, took: 3.44 s, cost: 0.27 μs/op, io count: 0
$ ./xdb_bench --db ../../data/ip2region.xdb --src ../../data/ip.merge.txt --cache-policy vector_index
total: 3419220, took: 45.99 s, cost: 12.24 μs/op, io count: 21739300
$ ./xdb_bench --db ../../data/ip2region.xdb --src ../../data/ip.merge.txt --cache-policy file
total: 3419220, took: 60.39 s, cost: 16.32 μs/op, io count: 25158520
```

74
binding/cpp/src/base.cc Normal file
View File

@ -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

58
binding/cpp/src/base.h Normal file
View File

@ -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

65
binding/cpp/src/bench.cc Normal file
View File

@ -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

27
binding/cpp/src/bench.h Normal file
View File

@ -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

170
binding/cpp/src/edit.cc Normal file
View File

@ -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

24
binding/cpp/src/edit.h Normal file
View File

@ -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

41
binding/cpp/src/header.cc Normal file
View File

@ -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

27
binding/cpp/src/header.h Normal file
View File

@ -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

158
binding/cpp/src/ip.cc Normal file
View File

@ -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

52
binding/cpp/src/ip.h Normal file
View File

@ -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

167
binding/cpp/src/make.cc Normal file
View File

@ -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

35
binding/cpp/src/make.h Normal file
View File

@ -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

137
binding/cpp/src/search.cc Normal file
View File

@ -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 &region_len,
int &region_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

49
binding/cpp/src/search.h Normal file
View File

@ -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 &region_len,
int &region_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

31
binding/cpp/test/bench.cc Normal file
View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

26
binding/cpp/test/make.cc Normal file
View File

@ -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;
}

View File

@ -0,0 +1,46 @@
#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", "Reserved|Reserved|Reserved|0|0");
test(s, "1.2.3.4", "Australia|Queensland|Brisbane|0|AU");
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::", "Japan|Tokyo|Asagaya-minami|WIDE Project|JP");
test(s, "2001:200:124::", "Japan|Tokyo|Asagaya-minami|WIDE Project|JP");
test(s, "240e:3b7:3273:51d0:cd38:8ae1:e3c0:b708", "中国|广东省|深圳市|电信|CN");
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;
}

View File

@ -1,135 +0,0 @@
#include "xdb_bench.h"
#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include <iostream>
#include <vector>
static void log_exit(const std::string &msg) {
std::cout << msg << std::endl;
exit(-1);
}
static unsigned long long get_time() {
struct timeval tv1;
gettimeofday(&tv1, NULL);
return (unsigned long long)tv1.tv_sec * 1000 * 1000 + tv1.tv_usec;
}
static bool ip2uint(const char *buf, unsigned int &ip) {
struct in_addr addr;
if (inet_pton(AF_INET, buf, &addr) == 0)
return false;
// 网络字节序为大端存储, 在此转换为小端存储
ip = (((addr.s_addr >> 0) & 0xFF) << 24) |
(((addr.s_addr >> 8) & 0xFF) << 16) |
(((addr.s_addr >> 16) & 0xFF) << 8) |
(((addr.s_addr >> 24) & 0xFF) << 0);
return true;
}
static std::string uint2ip(unsigned int ip) {
char buf[16];
snprintf(buf,
sizeof(buf),
"%d.%d.%d.%d",
(ip >> 24) & 0xFF,
(ip >> 16) & 0xFF,
(ip >> 8) & 0xFF,
ip & 0xFF);
return std::string(buf);
}
xdb_bench_t::xdb_bench_t(const std::string &file_name) : xdb_search(file_name) {
}
void xdb_bench_t::init_file() {
xdb_search.init_file();
}
void xdb_bench_t::init_vector_index() {
xdb_search.init_vector_index();
}
void xdb_bench_t::init_content() {
xdb_search.init_content();
}
void xdb_bench_t::bench_test_one(unsigned int ip_uint, const char *region) {
if (xdb_search.search(uint2ip(ip_uint)) != region)
log_exit("failed: " + uint2ip(ip_uint));
sum_io_count += xdb_search.get_io_count();
sum_cost_time += xdb_search.get_cost_time();
sum_count++;
}
void xdb_bench_t::bench_test_line(char *buf) {
size_t buf_len = strlen(buf);
if (buf_len == 0)
return;
buf[buf_len - 1] = '\0'; // 去掉换行符
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';
unsigned int ip1, ip2;
if (!ip2uint(buf, ip1) || !ip2uint(pos1 + 1, ip2) || ip1 > ip2) {
*pos1 = *pos2 = '|';
log_exit(std::string("invalid data: ") + buf);
}
const char *region = pos2 + 1;
unsigned int ip_mid = ip1 + (ip2 - ip1) / 2;
std::vector<unsigned int> ip_vec;
ip_vec.push_back(ip1);
ip_vec.push_back(ip1 + (ip_mid - ip1) / 2);
ip_vec.push_back(ip_mid);
ip_vec.push_back(ip_mid + (ip2 - ip_mid) / 2);
ip_vec.push_back(ip2);
for (auto &d : ip_vec)
bench_test_one(d, region);
}
void xdb_bench_t::bench_test_file(const std::string &file_name) {
FILE *f = fopen(file_name.data(), "r");
if (f == NULL)
log_exit("can't open " + file_name);
char buf[1024];
while (fgets(buf, sizeof(buf), f) != NULL)
bench_test_line(buf);
}
void xdb_bench_t::bench(const std::string &file_name) {
sum_io_count = 0;
sum_cost_time = 0;
sum_count = 0;
unsigned long long tv1 = get_time();
bench_test_file(file_name);
unsigned long long tv2 = get_time();
double took = (tv2 - tv1) * 1.0 / 1000 / 1000;
double cost = sum_cost_time * 1.0 / sum_count;
printf(
"total: %llu, took: %.2f s, cost: %.2f μs/op, io "
"count: "
"%llu\n",
sum_count,
took,
cost,
sum_io_count);
}

View File

@ -1,28 +0,0 @@
#ifndef XDB_BENCH_H
#define XDB_BENCH_H
#include "xdb_search.h"
class xdb_bench_t {
public:
xdb_bench_t(const std::string &file_name);
void init_file();
void init_vector_index();
void init_content();
void bench(const std::string &file_name);
private:
void bench_test_one(unsigned int ip_uint, const char *region);
void bench_test_line(char *buf);
void bench_test_file(const std::string &file_name);
xdb_search_t xdb_search;
unsigned long long sum_io_count;
unsigned long long sum_cost_time;
unsigned long long sum_count;
};
#endif

View File

@ -1,70 +0,0 @@
#include "xdb_bench.h"
#include <getopt.h>
#include <iostream>
void print_help(int argc, char* argv[]) {
printf("./xdb_bench [command options]\n");
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/vector_index/content\n");
printf(" --help print help\n");
exit(-1);
}
int main(int argc, char* argv[]) {
struct option long_options[] = {
{"db", required_argument, 0, 'd'},
{"cache-policy", required_argument, 0, 't'},
{"src", required_argument, 0, 's'},
{"help", no_argument, 0, 'h'},
{0, 0, 0, 0 }
};
std::string db_file_name = "../../data/ip2region.xdb";
std::string src_file_name = "../../data/ip.merge.txt";
std::string cache_policy = "vector_index";
while (1) {
int c = getopt_long(argc, argv, "", long_options, NULL);
if (c == -1)
break;
switch (c) {
case 'd':
db_file_name = optarg;
break;
case 'h':
print_help(argc, argv);
break;
case 't':
cache_policy = optarg;
break;
case 's':
src_file_name = optarg;
break;
case '?':
exit(-1);
}
}
xdb_bench_t xdb(db_file_name);
if (cache_policy == "content")
xdb.init_content();
else if (cache_policy == "vector_index")
xdb.init_vector_index();
else if (cache_policy == "file")
xdb.init_file();
else {
std::cout << "invalid cache policy: " << cache_policy << std::endl;
exit(-1);
}
xdb.bench(src_file_name);
return 0;
}

View File

@ -1,190 +0,0 @@
#include "xdb_search.h"
#include <arpa/inet.h>
#include <sys/time.h>
#include <iostream>
static void log_exit(const std::string &msg) {
std::cout << msg << std::endl;
exit(-1);
}
static unsigned long long get_time() {
struct timeval tv1;
gettimeofday(&tv1, NULL);
return (unsigned long long)tv1.tv_sec * 1000 * 1000 + tv1.tv_usec;
}
static 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__);
}
static unsigned int read_uint(const char *buf) {
return ((buf[0]) & 0x000000FF) | ((buf[1] << 8) & 0x0000FF00) |
((buf[2] << 16) & 0x00FF0000) | ((buf[3] << 24) & 0xFF000000);
}
static unsigned short read_ushort(const char *buf) {
return ((buf[0]) & 0x000000FF) | ((buf[1] << 8) & 0x0000FF00);
}
static bool ip2uint(const char *buf, unsigned int &ip) {
struct in_addr addr;
if (inet_pton(AF_INET, buf, &addr) == 0)
return false;
// 网络字节序为大端存储, 在此转换为小端存储
ip = (((addr.s_addr >> 0) & 0xFF) << 24) |
(((addr.s_addr >> 8) & 0xFF) << 16) |
(((addr.s_addr >> 16) & 0xFF) << 8) |
(((addr.s_addr >> 24) & 0xFF) << 0);
return true;
}
void xdb_search_t::get_content_index(unsigned int ip,
unsigned int &left,
unsigned int &right) {
unsigned int ip_1 = (ip >> 24) & 0xFF;
unsigned int ip_2 = (ip >> 16) & 0xFF;
unsigned int index = (ip_1 * vector_index_cols + ip_2) * vector_index_size;
if (content != NULL) {
left = read_uint(content + index + header_length);
right = read_uint(content + index + header_length + 4);
} else if (vector_index != NULL) {
left = read_uint(vector_index + index);
right = read_uint(vector_index + index + 4);
} else {
++io_count;
char buf[8];
read_bin(header_length + index, buf, sizeof(buf), db);
left = read_uint(buf);
right = read_uint(buf + 4);
}
}
void xdb_search_t::get_content(unsigned int index,
unsigned int &ip_left,
unsigned int &ip_right,
unsigned short &region_len,
unsigned int &region_index) {
char buf[segment_index_size]; // 4 + 4 + 2 + 4
const char *p;
if (content != NULL) {
p = content + index;
} else {
++io_count;
read_bin(index, buf, sizeof(buf), db);
p = buf;
}
ip_left = read_uint(p);
ip_right = read_uint(p + 4);
region_len = read_ushort(p + 8);
region_index = read_uint(p + 10);
}
std::string xdb_search_t::get_region(unsigned int index, unsigned short len) {
if (content != NULL) {
return std::string(content + index, len);
} else {
++io_count;
char *buf = (char *)malloc(sizeof(char) * len);
read_bin(index, buf, len, db);
std::string res(buf, len);
free(buf);
return res;
}
}
xdb_search_t::xdb_search_t(const std::string &file_name) {
db = fopen(file_name.data(), "r");
vector_index = NULL;
content = NULL;
if (db == NULL)
log_exit("can't open " + file_name);
}
void xdb_search_t::init_file() {
}
void xdb_search_t::init_vector_index() {
vector_index = (char *)malloc(vector_index_length);
read_bin(header_length, vector_index, vector_index_length, db);
}
void xdb_search_t::init_content() {
fseek(db, 0, SEEK_END);
unsigned int size = ftell(db);
content = (char *)malloc(size);
read_bin(0, content, size, db);
}
xdb_search_t::~xdb_search_t() {
if (db != NULL) {
fclose(db);
db = NULL;
}
if (vector_index != NULL) {
free(vector_index);
vector_index = NULL;
}
if (content != NULL) {
free(content);
content = NULL;
}
}
unsigned long long xdb_search_t::get_io_count() {
return io_count;
}
unsigned long long xdb_search_t::get_cost_time() {
return cost_time;
}
std::string xdb_search_t::search(const std::string &ip_str) {
unsigned long long t1 = get_time();
unsigned int ip_uint;
if (!ip2uint(ip_str.data(), ip_uint))
return "invalid ip: " + ip_str;
std::string region = search(ip_uint);
unsigned long long t2 = get_time();
cost_time = t2 - t1;
return region;
}
std::string xdb_search_t::search(unsigned int ip_uint) {
io_count = 0;
unsigned int content_index_left, content_index_right;
get_content_index(ip_uint, content_index_left, content_index_right);
unsigned int left, right, mid;
unsigned int ip_left, ip_right;
unsigned short region_len;
unsigned int region_index;
unsigned int mid_index;
left = 0;
right = (content_index_right - content_index_left) / segment_index_size;
for (;;) {
mid = left + (right - left) / 2;
mid_index = content_index_left + mid * segment_index_size;
get_content(mid_index, ip_left, ip_right, region_len, region_index);
if (ip_left > ip_uint)
right = mid - 1;
else if (ip_right < ip_uint)
left = mid + 1;
else
return get_region(region_index, region_len);
}
}

View File

@ -1,50 +0,0 @@
#ifndef XDB_SEARCH_H
#define XDB_SEARCH_H
#include <string>
class xdb_search_t {
public:
xdb_search_t(const std::string &file_name);
~xdb_search_t();
void init_file();
void init_vector_index();
void init_content();
unsigned long long get_io_count();
unsigned long long get_cost_time();
std::string search(const std::string &ip);
private:
void get_content_index(unsigned int ip,
unsigned int &left,
unsigned int &right);
void get_content(unsigned int index,
unsigned int &ip_left,
unsigned int &ip_right,
unsigned short &region_len,
unsigned int &region_index);
std::string get_region(unsigned int index, unsigned short len);
std::string search(unsigned int ip_uint);
FILE *db;
char *vector_index;
char *content;
unsigned long long io_count;
unsigned long long cost_time;
static constexpr int header_length = 256;
static constexpr int vector_index_rows = 256;
static constexpr int vector_index_cols = 256;
static constexpr int vector_index_size = 8;
static constexpr int vector_index_length =
vector_index_rows * vector_index_cols * vector_index_size;
static constexpr int segment_index_size = 14;
};
#endif

View File

@ -1,73 +0,0 @@
#include "xdb_search.h"
#include <getopt.h>
#include <iostream>
void print_help(int argc, char* argv[]) {
printf("./xdb_search [command options]\n");
printf("options:\n");
printf(" --db string ip2region binary xdb file path\n");
printf(
" --cache-policy string cache policy: "
"file/vector_index/content\n");
printf(" --help print help\n");
exit(-1);
}
int main(int argc, char* argv[]) {
struct option long_options[] = {
{"db", required_argument, 0, 'd'},
{"cache-policy", required_argument, 0, 't'},
{"help", no_argument, 0, 'h'},
{0, 0, 0, 0 }
};
std::string db_file_name = "../../data/ip2region.xdb";
std::string cache_policy = "vector_index";
while (1) {
int c = getopt_long(argc, argv, "", long_options, NULL);
if (c == -1)
break;
switch (c) {
case 'd':
db_file_name = optarg;
break;
case 'h':
print_help(argc, argv);
break;
case 't':
cache_policy = optarg;
break;
case '?':
exit(-1);
}
}
xdb_search_t xdb(db_file_name);
if (cache_policy == "content")
xdb.init_content();
else if (cache_policy == "vector_index")
xdb.init_vector_index();
else if (cache_policy == "file")
xdb.init_file();
else {
std::cout << "invalid cache policy: " << cache_policy << std::endl;
exit(-1);
}
std::string ip;
for (;;) {
std::cout << "ip2region>> ";
std::getline(std::cin, ip);
if (ip.empty())
continue;
if (ip == "exit" || ip == "quit")
break;
std::cout << xdb.search(ip) << std::endl;
}
return 0;
}

View File

@ -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

View File

@ -2,7 +2,10 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
## [Unreleased] ## [3.0.0] - 2025-11-22
- 支持 .NET 10.0
- 增加 IPv6 支持
- 修复若干 bug
## [2.0.1] - 2023-07-30 ## [2.0.1] - 2023-07-30

View File

@ -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);
}

View File

@ -2,14 +2,14 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.13.2" /> <PackageReference Include="BenchmarkDotNet" Version="0.15.6" />
<PackageReference Include="BenchmarkDotNet.Annotations" Version="0.13.2" /> <PackageReference Include="BenchmarkDotNet.Annotations" Version="0.15.6" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@ -17,8 +17,12 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Content Include="..\..\..\data\ip2region.xdb"> <Content Include="..\..\..\data\ip2region_v4.xdb">
<Link>IP2Region/ip2region.xdb</Link> <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> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
</ItemGroup> </ItemGroup>

View File

@ -1,29 +1,11 @@
using BenchmarkDotNet.Attributes; // 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 BenchmarkDotNet.Running;
using IP2Region.Net.Abstractions; using IP2Region.Net.BenchMark;
using IP2Region.Net.XDB;
BenchmarkRunner.Run(typeof(Program).Assembly); BenchmarkRunner.Run<Benchmarks>();
public class CachePolicyCompare
{
private static readonly string XdbPath = Path.Combine(AppContext.BaseDirectory, "IP2Region", "ip2region.xdb");
private readonly ISearcher _contentSearcher = new Searcher(CachePolicy.Content, XdbPath);
private readonly ISearcher _vectorSearcher = new Searcher(CachePolicy.VectorIndex,XdbPath);
private readonly ISearcher _fileSearcher = new Searcher(CachePolicy.File,XdbPath);
private readonly string _testIpAddress = "114.114.114.114";
[Benchmark]
[BenchmarkCategory(nameof(CachePolicy.Content))]
public void CachePolicy_Content() => _contentSearcher.Search(_testIpAddress);
[Benchmark]
[BenchmarkCategory(nameof(CachePolicy.VectorIndex))]
public void CachePolicy_VectorIndex() => _vectorSearcher.Search(_testIpAddress);
[Benchmark]
[BenchmarkCategory(nameof(CachePolicy.File))]
public void CachePolicy_File() => _fileSearcher.Search(_testIpAddress);
}

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
@ -9,11 +9,20 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
<PackageReference Include="NUnit" Version="3.13.3" /> <PackageReference Include="xunit" Version="2.*" />
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" /> <PackageReference Include="xunit.runner.visualstudio" Version="3.*">
<PackageReference Include="NUnit.Analyzers" Version="3.3.0" /> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PackageReference Include="coverlet.collector" Version="3.1.2" /> <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>
<ItemGroup> <ItemGroup>
@ -21,12 +30,20 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Content Include="..\..\..\data\ip.merge.txt"> <Content Include="..\..\..\data\ipv4_source.txt">
<Link>TestData/ip.merge.txt</Link> <Link>TestData/ipv4_source.txt</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="..\..\..\data\ip2region.xdb"> <Content Include="..\..\..\data\ip2region_v4.xdb">
<Link>TestData/ip2region.xdb</Link> <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> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
</ItemGroup> </ItemGroup>

View File

@ -1,80 +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 IP2Region.Net.XDB;
using Microsoft.Extensions.DependencyInjection;
using System.Net;
using Xunit;
namespace IP2Region.Net.Test; namespace IP2Region.Net.Test;
[TestFixture]
public class SearcherTest public class SearcherTest
{ {
private readonly string _xdbPath = Path.Combine(AppContext.BaseDirectory, "TestData", "ip2region.xdb"); private readonly string _xdbPathV4 = Path.Combine(AppContext.BaseDirectory, "TestData", "ip2region_v4.xdb");
private readonly string _xdbPathV6 = Path.Combine(AppContext.BaseDirectory, "TestData", "ip2region_v6.xdb");
public static IEnumerable<string> Ips() [Theory]
[InlineData("58.251.27.201", "中国|广东省|深圳市|联通|CN", "v4")]
[InlineData("114.114.114.114", "中国|江苏省|南京市|0|CN", "v4")]
[InlineData("119.29.29.29", "中国|北京|北京市|腾讯|CN", "v4")]
[InlineData("223.5.5.5", "中国|浙江省|杭州市|阿里|CN", "v4")]
[InlineData("180.76.76.76", "中国|北京|北京市|百度|CN", "v4")]
[InlineData("8.8.8.8", "United States|California|0|Google LLC|US", "v4")]
[InlineData("240e:3b7:3272:d8d0:db09:c067:8d59:539e", "中国|广东省|深圳市|电信|CN", "v6")]
public void TestSearchCacheContent(string ip, string expected, string version)
{ {
yield return "114.114.114.114"; var _xdbPath = version == "v4" ? _xdbPathV4 : _xdbPathV6;
yield return "119.29.29.29"; var contentSearcher = new Searcher(CachePolicy.Content, _xdbPath);
yield return "223.5.5.5";
yield return "180.76.76.76";
yield return "8.8.8.8";
}
[TestCaseSource(nameof(Ips))]
[Parallelizable(ParallelScope.All)]
public void TestSearchCacheContent(string ip)
{
var contentSearcher = new Searcher(CachePolicy.Content,_xdbPath);
var region = contentSearcher.Search(ip); var region = contentSearcher.Search(ip);
Console.WriteLine(region); Assert.Equal(expected, region);
} }
[TestCaseSource(nameof(Ips))] [Theory]
[Parallelizable(ParallelScope.All)] [InlineData("58.251.27.201", "中国|广东省|深圳市|联通|CN", "v4")]
public void TestSearchCacheVector(string ip) [InlineData("114.114.114.114", "中国|江苏省|南京市|0|CN", "v4")]
[InlineData("119.29.29.29", "中国|北京|北京市|腾讯|CN", "v4")]
[InlineData("223.5.5.5", "中国|浙江省|杭州市|阿里|CN", "v4")]
[InlineData("180.76.76.76", "中国|北京|北京市|百度|CN", "v4")]
[InlineData("8.8.8.8", "United States|California|0|Google LLC|US", "v4")]
[InlineData("240e:3b7:3272:d8d0:db09:c067:8d59:539e", "中国|广东省|深圳市|电信|CN", "v6")]
public void TestSearchCacheVector(string ip, string expected, string version)
{ {
var vectorSearcher = new Searcher(CachePolicy.VectorIndex,_xdbPath); var _xdbPath = version == "v4" ? _xdbPathV4 : _xdbPathV6;
var vectorSearcher = new Searcher(CachePolicy.VectorIndex, _xdbPath);
var region = vectorSearcher.Search(ip); var region = vectorSearcher.Search(ip);
Console.WriteLine(region); Assert.Equal(expected, region);
} }
[TestCaseSource(nameof(Ips))] [Theory]
[Parallelizable(ParallelScope.All)] [InlineData("58.251.0.0", "中国|广东省|深圳市|联通|CN", "v4")]
public void TestSearchCacheFile(string ip) [InlineData("58.251.255.255", "中国|广东省|深圳市|联通|CN", "v4")]
[InlineData("58.251.27.201", "中国|广东省|深圳市|联通|CN", "v4")]
[InlineData("114.114.114.114", "中国|江苏省|南京市|0|CN", "v4")]
[InlineData("119.29.29.29", "中国|北京|北京市|腾讯|CN", "v4")]
[InlineData("223.5.5.5", "中国|浙江省|杭州市|阿里|CN", "v4")]
[InlineData("180.76.76.76", "中国|北京|北京市|百度|CN", "v4")]
[InlineData("8.8.8.8", "United States|California|0|Google LLC|US", "v4")]
[InlineData("240e:3b7:3272:d8d0:db09:c067:8d59:539e", "中国|广东省|深圳市|电信|CN", "v6")]
[InlineData("240e:044d:2d00:0000:0000:0000:0000:0000", "中国|云南|楚雄|电信|CN", "v6")]
public void TestSearchCacheFile(string ip, string expected, string version)
{ {
var fileSearcher = new Searcher(CachePolicy.File,_xdbPath); var _xdbPath = version == "v4" ? _xdbPathV4 : _xdbPathV6;
var fileSearcher = new Searcher(CachePolicy.File, _xdbPath);
var region = fileSearcher.Search(ip); var region = fileSearcher.Search(ip);
Console.WriteLine(region); Assert.Equal(expected, region);
} }
[TestCase(CachePolicy.Content)] [Fact]
[TestCase(CachePolicy.VectorIndex)] public void IoCount_File_Ok()
[TestCase(CachePolicy.File)]
public void TestBenchSearch(CachePolicy cachePolicy)
{ {
Searcher searcher = new Searcher(cachePolicy,_xdbPath); var searcher = new Searcher(CachePolicy.File, _xdbPathV4);
var srcPath = Path.Combine(AppContext.BaseDirectory, "TestData", "ip.merge.txt"); 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", "中国|广东省|深圳市|联通|CN")]
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", "中国|广东省|深圳市|联通|CN")]
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)) foreach (var line in File.ReadLines(srcPath))
{ {
var ps = line.Trim().Split("|", 3); var ps = line.Trim().Split("|", 3);
var sip = ps[0];
var eip = ps[1];
if (ps.Length != 3) var s1 = searcher.Search(sip);
{ var s2 = searcher.Search(eip);
throw new ArgumentException($"invalid ip segment line {line}", nameof(line)); Assert.Equal(s1, ps[2]);
} Assert.Equal(s2, ps[2]);
var sip = Util.IpAddressToUInt32(ps[0]);
var eip = Util.IpAddressToUInt32(ps[1]);
var mip = Util.GetMidIp(sip, eip);
uint[] temp = { sip, Util.GetMidIp(sip, mip), mip, Util.GetMidIp(mip, eip), eip };
foreach (var ip in temp)
{
var region = searcher.Search(ip);
if (region != ps[2])
{
throw new Exception($"failed search {ip} with ({region}!={ps[2]})");
}
}
} }
} }
} }

View File

@ -1 +0,0 @@
global using NUnit.Framework;

View File

@ -1,14 +1,27 @@
using IP2Region.Net.XDB; // 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; namespace IP2Region.Net.Test;
[TestFixture]
public class UtilTest public class UtilTest
{ {
[TestCase("114.114.114.114")] [Fact]
public void TestIpAddressToUInt32(string value) public void IpAddressToUInt32_Ok()
{ {
var uintIp = XDB.Util.IpAddressToUInt32(value); var uintIp = XDB.Util.IpAddressToUInt32("114.114.114.114");
Console.WriteLine(uintIp); Assert.Equal((uint)1920103026, uintIp);
}
[Fact]
public void GetMidIp_Ok()
{
var uintIp = XDB.Util.GetMidIp(1, 10);
Assert.Equal((uint)5, uintIp);
} }
} }

View File

@ -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")));
}
}

View File

@ -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>

View File

@ -1,20 +1,40 @@
// Copyright 2023 The Ip2Region Authors. All rights reserved. // Copyright 2025 The Ip2Region Authors. All rights reserved.
// Use of this source code is governed by a Apache2.0-style // Use of this source code is governed by a Apache2.0-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// @Author Alan <lzh.shap@gmail.com> // @Author Alan <lzh.shap@gmail.com>
// @Date 2023/07/25 // @Date 2023/07/25
// Updated by Argo Zhang <argo@live.ca> at 2025/11/21
using System.Net; using System.Net;
namespace IP2Region.Net.Abstractions; namespace IP2Region.Net.Abstractions;
public interface ISearcher /// <summary>
/// IP 转化为地理位置搜索器接口
/// </summary>
public interface ISearcher : IDisposable
{ {
/// <summary>
/// 搜索方法
/// </summary>
/// <param name="ipStr">IP 地址字符串 如 192.168.0.1</param>
/// <returns></returns>
string? Search(string ipStr); string? Search(string ipStr);
/// <summary>
/// 搜索方法
/// </summary>
string? Search(IPAddress ipAddress); 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); string? Search(uint ipAddress);
/// <summary>
/// 获得 内部 IO 访问次数
/// </summary>
int IoCount { get; } int IoCount { get; }
} }

View File

@ -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;
}
}

View File

@ -2,9 +2,9 @@
<PropertyGroup> <PropertyGroup>
<id>IP2Region.Net</id> <id>IP2Region.Net</id>
<version>2.0.2</version> <version>3.0.2</version>
<title>IP2Region.Net</title> <title>IP2Region.Net</title>
<authors>Alan Lee</authors> <authors>Alan Lee;Argo Zhang(argo@live.ca)</authors>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression> <PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
<PackageReadmeFile>README.md</PackageReadmeFile> <PackageReadmeFile>README.md</PackageReadmeFile>
<PackageProjectUrl>https://github.com/lionsoul2014/ip2region/tree/master/binding/csharp</PackageProjectUrl> <PackageProjectUrl>https://github.com/lionsoul2014/ip2region/tree/master/binding/csharp</PackageProjectUrl>
@ -15,17 +15,48 @@
<RepositoryType>git</RepositoryType> <RepositoryType>git</RepositoryType>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<TargetFrameworks>netstandard2.0;netstandard2.1;net6.0;net7.0</TargetFrameworks> <TargetFrameworks>netstandard2.0;netstandard2.1;net6.0;net7.0;net8.0;net9.0;net10.0</TargetFrameworks>
<LangVersion>10.0</LangVersion> <LangVersion>latest</LangVersion>
<UserSecretsId>c2f07fe1-a300-4de3-8200-1278ed8cb5b7</UserSecretsId> <UserSecretsId>c2f07fe1-a300-4de3-8200-1278ed8cb5b7</UserSecretsId>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<None Include="..\README.md" Pack="true" PackagePath="\" /> <None Include="..\README.md" Pack="true" PackagePath="\" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'"> <ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
<PackageReference Include="System.Buffers" Version="4.5.1" /> <PackageReference Include="System.Buffers" Version="4.5.1" />
<PackageReference Include="System.Memory" Version="4.5.5" /> <PackageReference Include="System.Memory" Version="4.5.5" />
</ItemGroup> </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> <ItemGroup>
<None Include="..\CHANGELOG.md"> <None Include="..\CHANGELOG.md">
<Link>CHANGELOG.md</Link> <Link>CHANGELOG.md</Link>

View File

@ -1,64 +0,0 @@
// Copyright 2023 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
using System.Buffers;
namespace IP2Region.Net.Internal.Abstractions;
internal abstract class AbstractCacheStrategy
{
protected const int HeaderInfoLength = 256;
protected const int VectorIndexRows = 256;
protected const int VectorIndexCols = 256;
protected const int VectorIndexSize = 8;
protected readonly FileStream XdbFileStream;
private const int BufferSize = 4096;
internal int IoCount { get; private set; }
protected AbstractCacheStrategy(string xdbPath)
{
XdbFileStream = new FileStream(xdbPath, FileMode.Open, FileAccess.Read, FileShare.Read, BufferSize,
useAsync: true);
}
protected int GetVectorIndexStartPos(uint ip)
{
var il0 = ip >> 24 & 0xFF;
var il1 = ip >> 16 & 0xFF;
var idx = il0 * VectorIndexCols * VectorIndexSize + il1 * VectorIndexSize;
return (int)idx;
}
internal abstract ReadOnlyMemory<byte> GetVectorIndex(uint ip);
internal virtual ReadOnlyMemory<byte> GetData(int offset, int length)
{
byte[] buffer = ArrayPool<byte>.Shared.Rent(length);
int totalBytesRead = 0;
try
{
XdbFileStream.Seek(offset, SeekOrigin.Begin);
int bytesRead;
do
{
int bytesToRead = Math.Min(BufferSize, length - totalBytesRead);
bytesRead = XdbFileStream.Read(buffer, totalBytesRead, bytesToRead);
totalBytesRead += bytesRead;
IoCount++;
} while (bytesRead > 0 && totalBytesRead < length);
}
finally
{
ArrayPool<byte>.Shared.Return(buffer);
}
return new ReadOnlyMemory<byte>(buffer, 0, totalBytesRead);
}
}

View File

@ -1,31 +1,20 @@
// Copyright 2023 The Ip2Region Authors. All rights reserved. // Copyright 2025 The Ip2Region Authors. All rights reserved.
// Use of this source code is governed by a Apache2.0-style // Use of this source code is governed by a Apache2.0-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// @Author Alan <lzh.shap@gmail.com> // @Author Alan <lzh.shap@gmail.com>
// @Date 2023/07/25 // @Date 2023/07/25
// Updated by Argo Zhang <argo@live.ca> at 2025/11/21
using IP2Region.Net.Internal.Abstractions;
using IP2Region.Net.XDB; using IP2Region.Net.XDB;
namespace IP2Region.Net.Internal; namespace IP2Region.Net.Internal;
internal class CacheStrategyFactory static class CacheStrategyFactory
{ {
private readonly string _xdbPath; public static ICacheStrategy CreateCacheStrategy(CachePolicy cachePolicy, string xdbPath) => cachePolicy switch
public CacheStrategyFactory(string xdbPath)
{ {
_xdbPath = xdbPath; CachePolicy.Content => new ContentCacheStrategy(xdbPath),
} CachePolicy.VectorIndex => new VectorIndexCacheStrategy(xdbPath),
_ => new FileCacheStrategy(xdbPath),
public AbstractCacheStrategy CreateCacheStrategy(CachePolicy cachePolicy)
{
return cachePolicy switch
{
CachePolicy.Content => new ContentCacheStrategy(_xdbPath),
CachePolicy.VectorIndex => new VectorIndexCacheStrategy(_xdbPath),
CachePolicy.File => new FileCacheStrategy(_xdbPath),
_ => throw new ArgumentException(nameof(cachePolicy))
}; };
}
} }

View File

@ -1,32 +1,34 @@
// Copyright 2023 The Ip2Region Authors. All rights reserved. // Copyright 2025 The Ip2Region Authors. All rights reserved.
// Use of this source code is governed by a Apache2.0-style // Use of this source code is governed by a Apache2.0-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// @Author Alan <lzh.shap@gmail.com> // @Author Alan <lzh.shap@gmail.com>
// @Date 2023/07/25 // @Date 2023/07/25
// Updated by Wong <vcd.hai@outlook.com> at 2025/12/31
using IP2Region.Net.Internal.Abstractions;
namespace IP2Region.Net.Internal; namespace IP2Region.Net.Internal;
internal class ContentCacheStrategy : AbstractCacheStrategy class ContentCacheStrategy(string xdbPath) : ICacheStrategy
{ {
private readonly ReadOnlyMemory<byte> _cacheData; // TODO: these constants can be moved to the interface as defaults when using .NET 10
private const int HeaderInfoLength = 256;
private const int VectorIndexSize = 8;
public ContentCacheStrategy(string xdbPath) : base(xdbPath) private readonly ReadOnlyMemory<byte> _cacheData = File.ReadAllBytes(xdbPath);
public int IoCount => 0;
public void ResetIoCount()
{ {
_cacheData = base.GetData(0, (int)XdbFileStream.Length); // Do nothing
XdbFileStream.Close();
XdbFileStream.Dispose();
} }
internal override ReadOnlyMemory<byte> GetVectorIndex(uint ip) public ReadOnlyMemory<byte> GetVectorIndex(int offset)
{ => _cacheData.Slice(HeaderInfoLength + offset, VectorIndexSize);
int idx = GetVectorIndexStartPos(ip);
return _cacheData.Slice(HeaderInfoLength + idx, VectorIndexSize);
}
internal override ReadOnlyMemory<byte> GetData(int offset, int length) public ReadOnlyMemory<byte> GetData(long offset, int length) => _cacheData.Slice((int)offset, length);
public void Dispose()
{ {
return _cacheData.Slice(offset, length); // Do nothing
} }
} }

View File

@ -1,22 +1,85 @@
// Copyright 2023 The Ip2Region Authors. All rights reserved. // Copyright 2025 The Ip2Region Authors. All rights reserved.
// Use of this source code is governed by a Apache2.0-style // Use of this source code is governed by a Apache2.0-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// @Author Alan <lzh.shap@gmail.com> // @Author Alan <lzh.shap@gmail.com>
// @Date 2023/07/25 // @Date 2023/07/25
// Updated by Argo Zhang <argo@live.ca> at 2025/11/21
using IP2Region.Net.Internal.Abstractions; using System.Buffers;
namespace IP2Region.Net.Internal; namespace IP2Region.Net.Internal;
internal class FileCacheStrategy : AbstractCacheStrategy class FileCacheStrategy(string xdbPath) : ICacheStrategy
{ {
public FileCacheStrategy(string xdbPath) : base(xdbPath) 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;
} }
internal override ReadOnlyMemory<byte> GetVectorIndex(uint ip) public virtual ReadOnlyMemory<byte> GetVectorIndex(int offset) => GetData(HeaderInfoLength + offset, VectorIndexSize);
public virtual ReadOnlyMemory<byte> GetData(long offset, int length)
{ {
var idx = GetVectorIndexStartPos(ip); var buffer = ArrayPool<byte>.Shared.Rent(length);
return GetData(HeaderInfoLength + idx, VectorIndexSize); 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);
} }
} }

View File

@ -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);
}

View File

@ -1,26 +1,23 @@
// Copyright 2023 The Ip2Region Authors. All rights reserved. // Copyright 2025 The Ip2Region Authors. All rights reserved.
// Use of this source code is governed by a Apache2.0-style // Use of this source code is governed by a Apache2.0-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// @Author Alan <lzh.shap@gmail.com> // @Author Alan <lzh.shap@gmail.com>
// @Date 2023/07/25 // @Date 2023/07/25
// Updated by Argo Zhang <argo@live.ca> at 2025/11/21
using IP2Region.Net.Internal.Abstractions;
namespace IP2Region.Net.Internal; namespace IP2Region.Net.Internal;
internal class VectorIndexCacheStrategy : AbstractCacheStrategy class VectorIndexCacheStrategy : FileCacheStrategy
{ {
private readonly ReadOnlyMemory<byte> _vectorIndex; private const int VectorIndexRows = 256;
private const int VectorIndexCols = 256;
private readonly ReadOnlyMemory<byte> _vectorCache;
public VectorIndexCacheStrategy(string xdbPath) : base(xdbPath) public VectorIndexCacheStrategy(string xdbPath) : base(xdbPath)
{ {
var vectorLength = VectorIndexRows * VectorIndexCols * VectorIndexSize; _vectorCache = GetData(HeaderInfoLength, VectorIndexRows * VectorIndexCols * VectorIndexSize);
_vectorIndex = base.GetData(HeaderInfoLength, vectorLength);
} }
internal override ReadOnlyMemory<byte> GetVectorIndex(uint ip) public override ReadOnlyMemory<byte> GetVectorIndex(int offset) => _vectorCache.Slice(offset, VectorIndexSize);
{
var idx = GetVectorIndexStartPos(ip);
return _vectorIndex.Slice(idx, VectorIndexSize);
}
} }

View File

@ -1,15 +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.XDB; namespace IP2Region.Net.XDB;
/// <summary>
/// 缓存策略枚举
/// </summary>
public enum CachePolicy public enum CachePolicy
{ {
/// <summary> /// <summary>
/// no cache /// no cache
/// </summary> /// </summary>
File, File,
/// <summary> /// <summary>
/// cache vector index , reduce the number of IO operations /// cache vector index , reduce the number of IO operations
/// </summary> /// </summary>
VectorIndex, VectorIndex,
/// <summary> /// <summary>
/// default cache policy , cache whole xdb file /// default cache policy , cache whole xdb file
/// </summary> /// </summary>

View File

@ -1,85 +1,167 @@
// Copyright 2023 The Ip2Region Authors. All rights reserved. // Copyright 2025 The Ip2Region Authors. All rights reserved.
// Use of this source code is governed by a Apache2.0-style // Use of this source code is governed by a Apache2.0-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// @Author Alan Lee <lzh.shap@gmail.com> // @Author Alan <lzh.shap@gmail.com>
// @Date 2023/07/23 // @Date 2023/07/25
// Updated by Argo Zhang <argo@live.ca> at 2025/11/21
using System.Net;
using System.Runtime.InteropServices;
using System.Text;
using IP2Region.Net.Abstractions; using IP2Region.Net.Abstractions;
using IP2Region.Net.Internal; using IP2Region.Net.Internal;
using IP2Region.Net.Internal.Abstractions; using System.Buffers.Binary;
using System.Diagnostics.CodeAnalysis;
using System.Net;
using System.Text;
namespace IP2Region.Net.XDB; namespace IP2Region.Net.XDB;
public class Searcher : ISearcher /// <summary>
/// <see cref="ISearcher"/> 实现类
/// </summary>
/// <remarks>
/// <inheritdoc/>
/// </remarks>
public class Searcher(CachePolicy cachePolicy, string xdbPath) : ISearcher
{ {
const int SegmentIndexSize = 14; private readonly ICacheStrategy _cacheStrategy = CacheStrategyFactory.CreateCacheStrategy(cachePolicy, xdbPath);
private readonly AbstractCacheStrategy _cacheStrategy; /// <summary>
/// <inheritdoc/>
/// </summary>
public int IoCount => _cacheStrategy.IoCount; public int IoCount => _cacheStrategy.IoCount;
public Searcher(CachePolicy cachePolicy, string dbPath) /// <summary>
{ /// <inheritdoc/>
var factory = new CacheStrategyFactory(dbPath); /// </summary>
_cacheStrategy = factory.CreateCacheStrategy(cachePolicy);
}
public string? Search(string ipStr) public string? Search(string ipStr)
{ {
var ip = Util.IpAddressToUInt32(ipStr); var ipAddress = IPAddress.Parse(ipStr);
return Search(ip); return SearchCore(ipAddress.GetAddressBytes());
} }
public string? Search(IPAddress ipAddress) /// <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 ip = Util.IpAddressToUInt32(ipAddress); var bytes = BitConverter.GetBytes(ipAddress);
return Search(ip); Array.Reverse(bytes);
return SearchCore(bytes);
} }
public string? Search(uint ip) string? SearchCore(byte[] ipBytes)
{ {
var index = _cacheStrategy.GetVectorIndex(ip); // 重置 IO 计数器
uint sPtr = MemoryMarshal.Read<uint>(index.Span); _cacheStrategy.ResetIoCount();
uint ePtr = MemoryMarshal.Read<uint>(index.Span.Slice(4));
// 每个 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));
// @Note: ptr validate, zero ptr means source data missing
// so we could just stop here and return an empty string.
if (sPtr == 0 || ePtr == 0)
{
return "";
}
var length = ipBytes.Length;
var indexSize = length * 2 + 6;
var l = 0;
var h = (ePtr - sPtr) / indexSize;
var dataLen = 0; var dataLen = 0;
uint dataPtr = 0; long dataPtr = 0;
uint l = 0;
uint h = (ePtr -sPtr) / SegmentIndexSize;
while (l <= h) while (l <= h)
{ {
var mid = Util.GetMidIp(l, h); int m = (int)(l + h) >> 1;
var pos = sPtr + mid * SegmentIndexSize;
var buffer = _cacheStrategy.GetData((int)pos, SegmentIndexSize); var p = sPtr + m * indexSize;
uint sip = MemoryMarshal.Read<uint>(buffer.Span); var buff = _cacheStrategy.GetData(p, indexSize);
uint eip = MemoryMarshal.Read<uint>(buffer.Span.Slice(4));
if (ip < sip) var s = buff.Span.Slice(0, length);
var e = buff.Span.Slice(length, length);
if (ByteCompare(ipBytes, s) < 0)
{ {
h = mid - 1; h = m - 1;
} }
else if (ip > eip) else if (ByteCompare(ipBytes, e) > 0)
{ {
l = mid + 1; l = m + 1;
} }
else else
{ {
dataLen = MemoryMarshal.Read<ushort>(buffer.Span.Slice(8)); dataLen = BinaryPrimitives.ReadUInt16LittleEndian(buff.Span.Slice(length * 2, 2));
dataPtr = MemoryMarshal.Read<uint>(buffer.Span.Slice(10)); dataPtr = BinaryPrimitives.ReadUInt32LittleEndian(buff.Span.Slice(length * 2 + 2, 4));
break; break;
} }
} }
if (dataLen == 0) var regionBuff = _cacheStrategy.GetData(dataPtr, dataLen);
{
return default;
}
var regionBuff = _cacheStrategy.GetData((int)dataPtr,dataLen);
return Encoding.UTF8.GetString(regionBuff.Span.ToArray()); 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);
}
} }

View File

@ -1,8 +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 System.Buffers;
using System.Buffers.Binary;
using System.Net; using System.Net;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace IP2Region.Net.XDB; namespace IP2Region.Net.XDB;
/// <summary>
/// 工具类
/// </summary>
public static class Util public static class Util
{ {
public static uint IpAddressToUInt32(string ipAddress) public static uint IpAddressToUInt32(string ipAddress)
@ -20,4 +32,60 @@ public static class Util
public static uint GetMidIp(uint x, uint y) public static uint GetMidIp(uint x, uint y)
=> (x & y) + ((x ^ y) >> 1); => (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;
}
} }

View File

@ -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; }
}

View File

@ -24,34 +24,71 @@ ISearcher searcher = new Searcher(CachePolicy , "your xdb file path");
| CachePolicy.Content | Cache the entire `xdb` data. | Yes | | 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.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 | | CachePolicy.File | Completely file-based queries | Yes |
### XDB File Description ### 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 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 ## ASP.NET Core Usage
```csharp ```csharp
services.AddSingleton<ISearcher>(new Searcher(CachePolicy , "your xdb file path")); 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 ## Performance
// * Summary *
``` ini 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
BenchmarkDotNet=v0.13.2, OS=macOS 13.4.1 (c) (22F770820d) [Darwin 22.5.0] .NET SDK 10.0.100
Apple M1, 1 CPU, 8 logical and 8 physical cores [Host] : .NET 10.0.0 (10.0.0, 10.0.25.52411), X64 RyuJIT x86-64-v3
.NET SDK=7.0.306 DefaultJob : .NET 10.0.0 (10.0.0, 10.0.25.52411), X64 RyuJIT x86-64-v3
[Host] : .NET 6.0.20 (6.0.2023.32017), Arm64 RyuJIT AdvSIMD
DefaultJob : .NET 6.0.20 (6.0.2023.32017), Arm64 RyuJIT AdvSIMD
``` | Method | Mean | Error | StdDev | Gen0 | Allocated |
| Method | Mean | Error | StdDev | |------------ |-------------:|-----------:|-----------:|-------:|----------:|
|-------------------------|-----------:|---------:|---------:| | ContentIPv4 | 53.70 ns | 0.296 ns | 0.277 ns | 0.0086 | 136 B |
| CachePolicy_Content | 155.7 ns | 0.46 ns | 0.39 ns | | VectorIPv4 | 4,446.04 ns | 18.673 ns | 15.593 ns | 0.0076 | 232 B |
| CachePolicy_File | 2,186.8 ns | 34.27 ns | 32.06 ns | | FileIPv4 | 6,712.40 ns | 15.718 ns | 13.934 ns | 0.0153 | 264 B |
| CachePolicy_VectorIndex | 1,570.3 ns | 27.53 ns | 22.99 ns | | 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 ## Contributing
Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.

18
binding/erlang/Makefile Normal file
View File

@ -0,0 +1,18 @@
.PHONY: all compile test clean bench-v4 bench-v6
all: compile
compile:
rebar3 compile
test:
rebar3 eunit
clean:
rebar3 clean
bench-v4:
sh benchmarks/xdb-benchmark.sh ipv4
bench-v6:
sh benchmarks/xdb-benchmark.sh ipv6

View File

@ -1,129 +1,180 @@
# ip2region xdb erlang 查询客户端 :globe_with_meridians: [中文简体](README_zh.md) | [English](README.md)
### 简介 # ip2region Erlang query client
该bingding以erlang语言实现xdb查询客户端基于Erlang OTP Application查询逻辑由ip2region_worker工作进程实现支持配多个工作进程来进行负载均衡。
### 应用配置 ### Introduction
该应用可配置的参数在ip2region.app.src中,如下:
``` erlang This binding implements the xdb query client in `Erlang`, based on the `Erlang OTP Application`. The query logic is implemented by the `ip2region_worker` worker process, supporting multiple worker processes for load balancing.
### Application Configuration
The configurable parameters for this application are in `ip2region.app.src`, as follows:
```erlang
{env,[ {env,[
{poolargs, [ {poolargs, [
{size, 1}, %% 工作进程默认数量 {size, 1}, %% Default number of worker processes
{max_overflow, 5} %% 工作进程最大数量 {max_overflow, 5} %% Maximum number of worker processes
]},
{db, [
{ipv4, "ip2region.xdb"} %% Default IPv4 xdb file
]} ]}
]} ]}
``` ```
### 编译 ### Dual-stack configuration (IPv4 + IPv6)
To enable IPv6 queries, add the `ipv6` entry to the `db` list and place both xdb files under `priv/`:
```erlang
{env,[
{poolargs, [
{size, 1},
{max_overflow, 5}
]},
{db, [
{ipv4, "ip2region.xdb"},
{ipv6, "ip2region_v6.xdb"}
]}
]}
``` ```
The `xdb:search/1` interface automatically detects IPv4 and IPv6 inputs and routes them to the correct worker pool.
### Compile
```bash
$ rebar3 compile $ rebar3 compile
``` ```
### 运行
将xdb文件放到priv目录下然后启动erlang节点
```
$ rebar3 shell
### Run
Place the xdb file in the `priv` directory, then start the Erlang node:
```bash
$ rebar3 shell
``` ```
在erlang shell中调用xdb:search/1接口查询Ip地址信息, 该接口支持以list格式字符串、binary格式字符串、tuple和整数表示的IP地址如下
``` Call the `xdb:search/1` interface in the Erlang shell to query IP address information. This interface supports IP addresses represented as list strings, binary strings, tuples, and integers:
```erlang
1> xdb:search("1.0.8.0"). 1> xdb:search("1.0.8.0").
[20013,22269,124,48,124,24191,19996,30465,124,24191,24030, [20013,22269,124,24191,19996,30465,124,24191,24030,24066,
24066,124,30005,20449] 124,20013,22269,30005,20449,124,67,78]
2> 2>
3> io:format("~ts~n", [xdb:search("1.0.8.0")]). 3> io:format("~ts~n", [xdb:search("1.0.8.0")]).
中国|0|广东省|广州市|电信 中国|广东省|广州市|中国电信|CN
io:format("~ts~n", [xdb:search(<<"1.0.8.0">>)]). 4> io:format("~ts~n", [xdb:search(<<"1.0.8.0">>)]).
中国|0|广东省|广州市|电信 中国|广东省|广州市|中国电信|CN
4> io:format("~ts~n", [xdb:search({1,0,8,0})]). 5> io:format("~ts~n", [xdb:search({1,0,8,0})]).
中国|0|广东省|广州市|电信 中国|广东省|广州市|中国电信|CN
6> io:format("~ts~n", [xdb:search(16779264)]). 6> io:format("~ts~n", [xdb:search(16779264)]).
中国|0|广东省|广州市|电信 中国|广东省|广州市|中国电信|CN
``` ```
### 使用方法 With dual-stack enabled, IPv6 addresses are supported in the same way:
* 在rebar.config中引入依赖
```erlang
1> io:format("~ts~n", [xdb:search("2001:4860:4860::8888")]).
United States|Florida|Miami|Google LLC|US
2> io:format("~ts~n", [xdb:search(<<"2001:4860:4860::8888">>)]).
United States|Florida|Miami|Google LLC|US
3> io:format("~ts~n", [xdb:search({8193,18528,18528,0,0,0,0,34952})]).
United States|Florida|Miami|Google LLC|US
``` ```
### Usage
- Add the dependency in `rebar.config`
```erlang
{deps, [ {deps, [
ip2region ip2region
]}. ]}.
```
* 启动ip2region Application
```
......
application:ensure_started(ip2region),
......
``` ```
* 调用xdb:search/1接口查询IP信息 - Start the ip2region Application
```
......
ip2region:search("1.0.8.0"), ```erlang
{ok, _} = application:ensure_all_started(ip2region).
......
``` ```
### 单元测试 - Call the `xdb:search/1` interface to query IP information
```erlang
xdb:search("1.0.8.0").
``` ```
### Unit Test
```bash
$ rebar3 eunit $ rebar3 eunit
===> Verifying dependencies... ===> Verifying dependencies...
===> Analyzing applications... ===> Analyzing applications...
===> Compiling ip2region ===> Compiling ip2region
===> Performing EUnit tests... ===> Performing EUnit tests...
=INFO REPORT==== 17-Jan-2023::11:52:59.920155 === =INFO REPORT==== 28-Jun-2026::04:53:28 ===
XdbFile:/home/admin/erl-workspace/ip2region/binding/erlang/_build/test/lib/ip2region/priv/ip2region.xdb XdbFile:/Users/nana/Documents/code/ip2region/.worktrees/erlang-ipv6/binding/erlang/_build/test/lib/ip2region/priv/ip2region.xdb
.... ....
Finished in 0.074 seconds Finished in 0.192 seconds
4 tests, 0 failures 63 tests, 0 failures
``` ```
### 基准测试 ### Benchmark
```
Both IPv4 and IPv6 benchmarks share the same script. Run it with the desired IP version:
> `cold` = first pass over the source file: each IP triggers a real search and the
> result is written into the ETS cache. `warm` = second pass over the same list,
> where every lookup is served directly from the ETS cache.
```bash
$ cd benchmarks/ $ cd benchmarks/
$ sh xdb-benchmark.sh $ sh xdb-benchmark.sh ipv4
===> Verifying dependencies... ```
===> Analyzing applications...
===> Compiling ip2region For IPv6:
Erlang/OTP 24 [erts-12.3.2.2] [source] [64-bit] [smp:2:2] [ds:2:2:10] [async-threads:1] [jit]
```bash
Eshell V12.3.2.2 (abort with ^G) $ sh xdb-benchmark.sh ipv6
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
Or use the Makefile targets from the `binding/erlang` directory:
===> Booted ip2region
===> Evaluating: "xdb_benchmark:main(\"../../data/ip.merge.txt\"), init:stop()." ```bash
CPU info: $ make bench-v4
model name : AMD EPYC 7K62 48-Core Processor $ make bench-v6
cache size : 512 KB ```
cpu MHz : 2595.124
bogomips : 5190.24 #### IPv4 benchmark example
cores/threads : 2
```bash
Erlang info: System:
system_version:Erlang/OTP 24 [erts-12.3.2.2] [source] [64-bit] [smp:2:2] [ds:2:2:10] [async-threads:1] [jit] CPU : Apple M4
load test data use 4.835593s Cores : 10 cores / 10 threads
Erlang : Erlang/OTP 29 [erts-17.0.2] [source] [64-bit] [smp:10:10] [ds:10:10:10] [async-threads:1] [jit] [dtrace]
start run benchmark tests Loaded : 487169 IPs in 1.335 s
search from file: Benchmarks:
ip count:683844, cold total= 9.601s count= 487169 qps= 50740.66 avg= 0.019708 ms/op (19.708 us/op)
total time: 28.201699s, warm total= 0.160s count= 487169 qps= 3053164.29 avg= 0.000328 ms/op ( 0.328 us/op)
search 24248.326315375536 times per second,
use 41.23995969841075 micro second per search Done.
```
search from cache:
ip count:683844, #### IPv6 benchmark example
total time: 0.671801s,
search 1017926.4395259906 times per second, ```bash
use 0.9823892583688677 micro second per search System:
CPU : Apple M4
benchmark test finish Cores : 10 cores / 10 threads
Erlang : Erlang/OTP 29 [erts-17.0.2] [source] [64-bit] [smp:10:10] [ds:10:10:10] [async-threads:1] [jit] [dtrace]
Loaded : 638953 IPs in 2.949 s
Benchmarks:
cold total= 20.504s count= 638953 qps= 31162.52 avg= 0.032090 ms/op (32.090 us/op)
warm total= 0.444s count= 638953 qps= 1437781.56 avg= 0.000696 ms/op ( 0.696 us/op)
Done.
``` ```

179
binding/erlang/README_zh.md Normal file
View File

@ -0,0 +1,179 @@
:globe_with_meridians: [中文简体](README_zh.md) | [English](README.md)
# ip2region Erlang 查询客户端
### 简介
该 bingding 以 `Erlang` 语言实现 xdb 查询客户端,基于 `Erlang OTP Application`,查询逻辑由 `ip2region_worker` 工作进程实现,支持配多个工作进程来进行负载均衡。
### 应用配置
该应用可配置的参数在 `ip2region.app.src` 中, 如下:
```erlang
{env,[
{poolargs, [
{size, 1}, %% 工作进程默认数量
{max_overflow, 5} %% 工作进程最大数量
]},
{db, [
{ipv4, "ip2region.xdb"} %% 默认 IPv4 xdb 文件
]}
]}
```
### 双栈配置IPv4 + IPv6
如需启用 IPv6 查询,在 `db` 列表中加入 `ipv6` 项,并将两个 xdb 文件放到 `priv/` 目录下:
```erlang
{env,[
{poolargs, [
{size, 1},
{max_overflow, 5}
]},
{db, [
{ipv4, "ip2region.xdb"},
{ipv6, "ip2region_v6.xdb"}
]}
]}
```
`xdb:search/1` 接口会自动识别 IPv4 与 IPv6 输入,并将其路由到对应的工作进程池。
### 编译
```bash
$ rebar3 compile
```
### 运行
将 xdb 文件放到 `priv` 目录下,然后启动 Erlang 节点:
```bash
$ rebar3 shell
```
在 Erlang shell 中调用 `xdb:search/1` 接口查询 IP 地址信息。该接口支持 list 字符串、binary 字符串、tuple 和整数表示的 IP 地址:
```erlang
1> xdb:search("1.0.8.0").
[20013,22269,124,24191,19996,30465,124,24191,24030,24066,
124,20013,22269,30005,20449,124,67,78]
2>
3> io:format("~ts~n", [xdb:search("1.0.8.0")]).
中国|广东省|广州市|中国电信|CN
4> io:format("~ts~n", [xdb:search(<<"1.0.8.0">>)]).
中国|广东省|广州市|中国电信|CN
5> io:format("~ts~n", [xdb:search({1,0,8,0})]).
中国|广东省|广州市|中国电信|CN
6> io:format("~ts~n", [xdb:search(16779264)]).
中国|广东省|广州市|中国电信|CN
```
启用双栈后IPv6 地址也按同样方式支持:
```erlang
1> io:format("~ts~n", [xdb:search("2001:4860:4860::8888")]).
United States|Florida|Miami|Google LLC|US
2> io:format("~ts~n", [xdb:search(<<"2001:4860:4860::8888">>)]).
United States|Florida|Miami|Google LLC|US
3> io:format("~ts~n", [xdb:search({8193,18528,18528,0,0,0,0,34952})]).
United States|Florida|Miami|Google LLC|US
```
### 使用方法
- 在 `rebar.config` 中引入依赖
```erlang
{deps, [
ip2region
]}.
```
- 启动 ip2region Application
```erlang
{ok, _} = application:ensure_all_started(ip2region).
```
- 调用 `xdb:search/1` 接口查询 IP 信息
```erlang
xdb:search("1.0.8.0").
```
### 单元测试
```bash
$ rebar3 eunit
===> Verifying dependencies...
===> Analyzing applications...
===> Compiling ip2region
===> Performing EUnit tests...
=INFO REPORT==== 28-Jun-2026::04:53:28 ===
XdbFile:/Users/nana/Documents/code/ip2region/.worktrees/erlang-ipv6/binding/erlang/_build/test/lib/ip2region/priv/ip2region.xdb
....
Finished in 0.192 seconds
63 tests, 0 failures
```
### 基准测试
IPv4 与 IPv6 共用一个脚本,通过参数指定版本:
> `cold` = 第一次遍历源文件:每个 IP 都会触发真实查询,并把结果写入 ETS 缓存。
> `warm` = 第二次遍历同一列表,所有查询都直接命中 ETS 缓存。
```bash
$ cd benchmarks/
$ sh xdb-benchmark.sh ipv4
```
IPv6
```bash
$ sh xdb-benchmark.sh ipv6
```
也可以在 `binding/erlang` 目录直接用 Makefile
```bash
$ make bench-v4
$ make bench-v6
```
#### IPv4 基准测试示例
```bash
System:
CPU : Apple M4
Cores : 10 cores / 10 threads
Erlang : Erlang/OTP 29 [erts-17.0.2] [source] [64-bit] [smp:10:10] [ds:10:10:10] [async-threads:1] [jit] [dtrace]
Loaded : 487169 IPs in 1.335 s
Benchmarks:
cold total= 9.601s count= 487169 qps= 50740.66 avg= 0.019708 ms/op (19.708 us/op)
warm total= 0.160s count= 487169 qps= 3053164.29 avg= 0.000328 ms/op ( 0.328 us/op)
Done.
```
#### IPv6 基准测试示例
```bash
System:
CPU : Apple M4
Cores : 10 cores / 10 threads
Erlang : Erlang/OTP 29 [erts-17.0.2] [source] [64-bit] [smp:10:10] [ds:10:10:10] [async-threads:1] [jit] [dtrace]
Loaded : 638953 IPs in 2.949 s
Benchmarks:
cold total= 20.504s count= 638953 qps= 31162.52 avg= 0.032090 ms/op (32.090 us/op)
warm total= 0.444s count= 638953 qps= 1437781.56 avg= 0.000696 ms/op ( 0.696 us/op)
Done.
```

View File

@ -1,5 +1,37 @@
#!/bin/bash #!/bin/bash
set -e
cd .. # Run from binding/erlang so paths and rebar3 artifacts are correct.
cd "$(dirname "$0")/.."
rebar3 shell --eval="xdb_benchmark:main(\"../../data/ip.merge.txt\"), init:stop()." VERSION="${1:-ipv4}"
PROJECT_ROOT="$(cd ../.. && pwd)"
if [ "$VERSION" = "ipv6" ]; then
DB_FILE="${PROJECT_ROOT}/data/ip2region_v6.xdb"
DATA_FILE="${PROJECT_ROOT}/data/ipv6_source.txt"
DB_CONFIG="[{ipv6, \"${DB_FILE}\"}]"
elif [ "$VERSION" = "ipv4" ]; then
DATA_FILE="${PROJECT_ROOT}/data/ipv4_source.txt"
DB_CONFIG="[{ipv4, \"ip2region.xdb\"}]"
else
echo "Usage: $0 [ipv4|ipv6]"
exit 1
fi
rebar3 compile
# shellcheck disable=SC2089
EVAL="application:load(ip2region), \
application:set_env(ip2region, db, ${DB_CONFIG}), \
application:set_env(ip2region, poolargs, [{size,4},{max_overflow,8}]), \
{ok,_}=application:ensure_all_started(ip2region), \
xdb_benchmark:main(\"${DATA_FILE}\"), \
init:stop()."
# shellcheck disable=SC2090
erl -pa _build/default/lib/poolboy/ebin \
-pa _build/default/lib/ip2region/ebin \
-kernel logger_level error \
-noshell \
-eval "${EVAL}"

View File

@ -4,23 +4,39 @@
-define(NONE, none). -define(NONE, none).
-define(APP_NAME, ip2region). -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_HEADER_SIZE, 256).
-define(XDB_VECTOR_COLS, 256). -define(XDB_VECTOR_COLS, 256).
-define(XDB_VECTOR_INDEX_SIZE, 8). -define(XDB_VECTOR_INDEX_SIZE, 8).
-define(XDB_VECTOR_INDEX_COUNT, (16#10000)). %% 256*256 -define(XDB_VECTOR_INDEX_COUNT, (16#10000)). %% 256*256
-define(XDB_SEGMENT_INDEX_SIZE, 14). %% IP versions as stored in xdb header
-define(IP_VERSION_4, 4).
-define(IP_VERSION_6, 6).
-define(IP2REGION_POOL, ip2region_pool). %% Segment index sizes (bytes): start_ip + end_ip + data_len + data_ptr
-define(XDB_SEGMENT_INDEX_SIZE_V4, 14). %% 4 + 4 + 2 + 4
-define(XDB_SEGMENT_INDEX_SIZE_V6, 38). %% 16 + 16 + 2 + 4
-ifndef(IF). %% Per-version ETS tables
-define(IF(C, T, F), case (C) of true -> (T); false -> (F) end). -define(XDB_VECTOR_INDEX_V4, ets_xdb_vector_index_v4).
-define(IF(C, T), ?IF(C, T, skip)). -define(XDB_VECTOR_INDEX_V6, ets_xdb_vector_index_v6).
-endif. -define(XDB_SEGMENT_INDEX_V4, ets_xdb_segment_index_v4).
-define(XDB_SEGMENT_INDEX_V6, ets_xdb_segment_index_v6).
-define(IP2REGION_CACHE_V4, ets_ip2region_cache_v4).
-define(IP2REGION_CACHE_V6, ets_ip2region_cache_v6).
%% Per-version pool names.
-define(IP2REGION_POOL_V4, ip2region_pool_v4).
-define(IP2REGION_POOL_V6, ip2region_pool_v6).
-record(xdb_header, {
version :: non_neg_integer(),
index_policy :: non_neg_integer(),
created_at :: non_neg_integer(),
start_index_ptr :: non_neg_integer(),
end_index_ptr :: non_neg_integer(),
ip_version :: non_neg_integer(),
runtime_ptr_bytes :: non_neg_integer()
}).
-endif. -endif.

View File

@ -18,7 +18,7 @@
{ex_doc, [ {ex_doc, [
{extras, ["README.md"]}, {extras, ["README.md"]},
{main, "README.md"}, {main, "README.md"},
{source_url, "https://github.com/leihua996/ip2region/tree/master/binding/erlang"} {source_url, "https://github.com/lionsoul2014/ip2region/tree/master/binding/erlang"}
]}. ]}.
{hex, [{doc, ex_doc}]}. {hex, [{doc, ex_doc}]}.

View File

@ -5,16 +5,20 @@
{mod, {ip2region_app, []}}, {mod, {ip2region_app, []}},
{applications, {applications,
[kernel, [kernel,
stdlib stdlib,
poolboy
]}, ]},
{env,[ {env,[
{poolargs, [ {poolargs, [
{size, 1}, {size, 1},
{max_overflow, 5} {max_overflow, 5}
]},
{db, [
{ipv4, "ip2region.xdb"}
]} ]}
]}, ]},
{modules, []}, {modules, []},
{licenses, ["Apache-2.0"]}, {licenses, ["Apache-2.0"]},
{links, [{"Github", "https://github.com/leihua996/ip2region/tree/master/binding/erlang"}]} {links, [{"Github", "https://github.com/lionsoul2014/ip2region/tree/master/binding/erlang"}]}
]}. ]}.

Some files were not shown because too many files have changed in this diff Show More