ts-ip2region2 1.1.2 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -69,6 +69,35 @@ new Ip2Region(dbPath: string, options?: Ip2RegionOptions)
69
69
  - `Ip2Region.verify(dbPath: string): boolean` - Verify xdb file
70
70
  - `Ip2Region.verifyDetailed(dbPath: string): VerifyResult` - Verify with detailed info
71
71
 
72
+ ## Performance Benchmark
73
+
74
+ Benchmark results on Windows x64 with Node.js v22.14.0 (10,000 iterations):
75
+
76
+ | Cache Strategy | Avg Time (μs/op) | QPS |
77
+ |---------------|------------------|-----|
78
+ | file | ~31 | ~32,000 |
79
+ | vectorIndex | ~22 | ~45,000 |
80
+ | content | ~1.3 | ~750,000 |
81
+
82
+ **Performance Improvements:**
83
+ - vectorIndex is ~40% faster than file mode
84
+ - content is ~95% faster than vectorIndex mode
85
+ - content is ~96% faster than file mode
86
+
87
+ **Comparison with Native C:**
88
+ - Native C (vectorIndex): ~5 μs/op
89
+ - Node.js Addon (vectorIndex): ~22 μs/op
90
+ - Overhead: ~4.4x (mainly from N-API call overhead)
91
+
92
+ Despite the N-API overhead, the performance is still excellent for most use cases, achieving 45,000+ QPS with vectorIndex mode.
93
+
94
+ **Recommendation:** Use `vectorIndex` for most scenarios as it provides excellent performance with moderate memory usage.
95
+
96
+ Run benchmark yourself:
97
+ ```bash
98
+ npm run benchmark
99
+ ```
100
+
72
101
  ## License
73
102
 
74
103
  Apache-2.0
package/addon.cpp CHANGED
@@ -10,6 +10,33 @@ extern "C" {
10
10
  #include "ip2region/xdb_api.h"
11
11
  }
12
12
 
13
+ #ifdef XDB_WINDOWS
14
+ // Windows UTF-8 path support
15
+ extern "C" {
16
+ __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int, unsigned long, const char*, int, wchar_t*, int);
17
+ }
18
+ #define CP_UTF8 65001
19
+
20
+ static FILE* fopen_utf8(const char* path, const char* mode) {
21
+ int path_len = MultiByteToWideChar(CP_UTF8, 0, path, -1, nullptr, 0);
22
+ if (path_len == 0) return nullptr;
23
+
24
+ wchar_t* wpath = new wchar_t[path_len];
25
+ MultiByteToWideChar(CP_UTF8, 0, path, -1, wpath, path_len);
26
+
27
+ int mode_len = MultiByteToWideChar(CP_UTF8, 0, mode, -1, nullptr, 0);
28
+ wchar_t* wmode = new wchar_t[mode_len];
29
+ MultiByteToWideChar(CP_UTF8, 0, mode, -1, wmode, mode_len);
30
+
31
+ FILE* file = _wfopen(wpath, wmode);
32
+ delete[] wpath;
33
+ delete[] wmode;
34
+ return file;
35
+ }
36
+ #else
37
+ #define fopen_utf8 fopen
38
+ #endif
39
+
13
40
  class Ip2RegionSearcher : public Napi::ObjectWrap<Ip2RegionSearcher> {
14
41
  public:
15
42
  static Napi::Object Init(Napi::Env env, Napi::Object exports);
@@ -26,10 +53,59 @@ private:
26
53
  xdb_vector_index_t* v_index;
27
54
  xdb_content_t* c_buffer;
28
55
  bool is_initialized;
56
+
57
+ static xdb_vector_index_t* load_vector_index_utf8(const char* path);
58
+ static xdb_content_t* load_content_utf8(const char* path);
59
+ static int new_with_file_only_utf8(xdb_version_t* version, xdb_searcher_t* searcher, const char* path);
60
+ static int new_with_vector_index_utf8(xdb_version_t* version, xdb_searcher_t* searcher, const char* path, const xdb_vector_index_t* v_index);
29
61
  };
30
62
 
31
63
  Napi::FunctionReference Ip2RegionSearcher::constructor;
32
64
 
65
+ xdb_vector_index_t* Ip2RegionSearcher::load_vector_index_utf8(const char* path) {
66
+ FILE* file = fopen_utf8(path, "rb");
67
+ if (!file) return nullptr;
68
+ xdb_vector_index_t* v_index = xdb_load_vector_index(file);
69
+ fclose(file);
70
+ return v_index;
71
+ }
72
+
73
+ xdb_content_t* Ip2RegionSearcher::load_content_utf8(const char* path) {
74
+ FILE* file = fopen_utf8(path, "rb");
75
+ if (!file) return nullptr;
76
+ xdb_content_t* content = xdb_load_content(file);
77
+ fclose(file);
78
+ return content;
79
+ }
80
+
81
+ int Ip2RegionSearcher::new_with_file_only_utf8(xdb_version_t* version, xdb_searcher_t* searcher, const char* path) {
82
+ FILE* file = fopen_utf8(path, "rb");
83
+ if (!file) return -1;
84
+
85
+ memset(searcher, 0, sizeof(xdb_searcher_t));
86
+ searcher->version = version;
87
+ searcher->handle = file;
88
+ searcher->v_index = nullptr;
89
+ searcher->content = nullptr;
90
+ searcher->io_count = 0;
91
+
92
+ return 0;
93
+ }
94
+
95
+ int Ip2RegionSearcher::new_with_vector_index_utf8(xdb_version_t* version, xdb_searcher_t* searcher, const char* path, const xdb_vector_index_t* v_index) {
96
+ FILE* file = fopen_utf8(path, "rb");
97
+ if (!file) return -1;
98
+
99
+ memset(searcher, 0, sizeof(xdb_searcher_t));
100
+ searcher->version = version;
101
+ searcher->handle = file;
102
+ searcher->v_index = v_index;
103
+ searcher->content = nullptr;
104
+ searcher->io_count = 0;
105
+
106
+ return 0;
107
+ }
108
+
33
109
  Napi::Object Ip2RegionSearcher::Init(Napi::Env env, Napi::Object exports) {
34
110
  Napi::HandleScope scope(env);
35
111
 
@@ -91,17 +167,17 @@ Ip2RegionSearcher::Ip2RegionSearcher(const Napi::CallbackInfo& info)
91
167
 
92
168
  // Initialize searcher based on cache strategy
93
169
  if (cache_policy == "file") {
94
- err = xdb_new_with_file_only(version, &searcher, db_path.c_str());
170
+ err = new_with_file_only_utf8(version, &searcher, db_path.c_str());
95
171
  } else if (cache_policy == "vectorIndex") {
96
- v_index = xdb_load_vector_index_from_file(db_path.c_str());
172
+ v_index = load_vector_index_utf8(db_path.c_str());
97
173
  if (!v_index) {
98
174
  xdb_clean_winsock();
99
175
  Napi::Error::New(env, "Failed to load vector index from database file").ThrowAsJavaScriptException();
100
176
  return;
101
177
  }
102
- err = xdb_new_with_vector_index(version, &searcher, db_path.c_str(), v_index);
178
+ err = new_with_vector_index_utf8(version, &searcher, db_path.c_str(), v_index);
103
179
  } else if (cache_policy == "content") {
104
- c_buffer = xdb_load_content_from_file(db_path.c_str());
180
+ c_buffer = load_content_utf8(db_path.c_str());
105
181
  if (!c_buffer) {
106
182
  xdb_clean_winsock();
107
183
  Napi::Error::New(env, "Failed to load database content into memory").ThrowAsJavaScriptException();
@@ -213,19 +289,11 @@ Napi::Value VerifyXdb(const Napi::CallbackInfo& info) {
213
289
 
214
290
  const std::string db_path = info[0].As<Napi::String>().Utf8Value();
215
291
 
216
- int error_code = 0;
217
- FILE* file_handle = std::fopen(db_path.c_str(), "rb");
218
-
219
- if (!file_handle) {
220
- error_code = -1; // File not found or cannot be opened
221
- } else {
222
- xdb_header_t* header = xdb_load_header(file_handle);
223
- if (!header) {
224
- error_code = 1; // Invalid file format
225
- } else {
226
- xdb_free_header(header);
227
- }
228
- std::fclose(file_handle);
292
+ FILE* file = fopen_utf8(db_path.c_str(), "rb");
293
+ int error_code = -1;
294
+ if (file) {
295
+ error_code = xdb_verify(file);
296
+ fclose(file);
229
297
  }
230
298
 
231
299
  Napi::Object result = Napi::Object::New(env);
@@ -0,0 +1,353 @@
1
+ # ip2region xdb c 查询客户端实现
2
+
3
+
4
+ # 使用方式
5
+
6
+ ### 关于查询 API
7
+ 查询 API 的原型如下:
8
+ ```c
9
+ // 通过字符串 IP 进行查询
10
+ int xdb_search_by_string(xdb_searcher_t *, const string_ip_t *, xdb_region_buffer_t *);
11
+ // 通过 xdb_parse_ip 返回的二进制 IP 进行查询
12
+ int xdb_search(xdb_searcher_t *, const bytes_ip_t *, int, xdb_region_buffer_t *);
13
+ ```
14
+ 如果查询失败将会返回非 `0` 的错误代码,如果查询成功 xdb_region_buffer_t 可以获取到字符串的 `region` 信息,如果输入的 IP 找不到相关的信息,xdb_region_buffer_t 将会得到一个空的字符串 `""`。
15
+
16
+ ### 关于 IPv4 和 IPv6
17
+ 该 xdb 查询客户端实现同时支持对 IPv4 和 IPv6 的查询,使用方式如下:
18
+ ```c
19
+ #include "xdb_api.h";
20
+
21
+ // 如果是 IPv4: 设置 xdb 路径为 v4 的 xdb 文件,IP版本指定为 IPv4
22
+ const char *db_path = "../../data/ip2region_v4.xdb"; // 或者你的 ipv4 xdb 的路径
23
+ xdb_version_t *version = XDB_IPv4;
24
+
25
+ // 如果是 IPv6: 设置 xdb 路径为 v6 的 xdb 文件,IP版本指定为 IPv6
26
+ const char *db_path = "../../data/ip2region_v6.xdb"; // 或者你的 ipv6 xdb 路径
27
+ xdb_version_t *version = XDB_IPv6;
28
+
29
+ // db_path 指定的 xdb 的 IP 版本必须和 version 指定的一致,不然查询执行的时候会报错
30
+ // 备注:以下演示直接使用 db_path 和 version 变量
31
+ ```
32
+
33
+ ### XDB 文件验证
34
+ 建议您主动去验证 xdb 文件的适用性,因为后期的一些新功能可能会导致目前的 Searcher 版本无法适用你使用的 xdb 文件,验证可以避免运行过程中的一些不可预测的错误。 你不需要每次都去验证,例如在服务启动的时候,或者手动调用命令验证确认版本匹配即可,不要在每次创建的 Searcher 的时候运行验证,这样会影响查询的响应速度,尤其是高并发的使用场景。
35
+ ```c
36
+ #include "xdb_api.h";
37
+
38
+ int errcode = xdb_verify(db_path);
39
+ if ($err != 0) {
40
+ // 适用性验证失败!!!
41
+ // 当前查询客户端实现不适用于 db_path 指定的 xdb 文件的查询.
42
+ // 应该停止启动服务,使用合适的 xdb 文件或者升级到适合 db_path 的 Searcher 实现。
43
+ printf("failed to verify xdb file `%s`, errcode: %d\n", db_path, errcode);
44
+ return;
45
+ }
46
+
47
+ // 验证通过,当前使用的 Searcher 可以安全的用于对 dbPath 指向的 xdb 的查询操作
48
+ ```
49
+
50
+ ### 完全基于文件的查询
51
+
52
+ ```c
53
+ #include <stdio.h>
54
+ #include "xdb_api.h"
55
+
56
+ int main(int argc, char *argv[]) {
57
+ xdb_searcher_t searcher;
58
+ char region_buffer[512] = {'\0'};
59
+ xdb_region_buffer_t region;
60
+
61
+ // 使用栈空间的 region_buffer 初始化 region_buffer_t
62
+ int err = xdb_region_buffer_init(&region, region_buffer, sizeof(region_buffer));
63
+ if (err != 0) {
64
+ printf("failed to init the region buffer with errcode=%d\n", err);
65
+ return 1;
66
+ }
67
+
68
+ // 在服务启动的时候初始化 winsock,不需要重复调用,只需要在 windows 系统下调用
69
+ err = xdb_init_winsock();
70
+ if (err != 0) {
71
+ printf("failed to init the winsock with errno=%d\n", err);
72
+ return 1;
73
+ }
74
+
75
+ // 1、从 db_path 初始化 xdb 查询对象.
76
+ // @Note: 使用顶部描述的 db_path 和 version 来创建 searcher
77
+ err = xdb_new_with_file_only(version, &searcher, db_path);
78
+ if (err != 0) {
79
+ printf("failed to create xdb searcher from `%s` with errno=%d\n", db_path, err);
80
+ return 1;
81
+ }
82
+
83
+ // 2、调用 search API 查询,IPv4 和 IPv6 都支持.
84
+ const char *ip_string = "1.2.3.4";
85
+ // ip_string = "2001:4:112:ffff:ffff:ffff:ffff:ffff"; // IPv6
86
+
87
+ long cost_time = 0, s_time = xdb_now();
88
+ err = xdb_search_by_string(&searcher, ip_string, &region);
89
+ cost_time = (int) (xdb_now() - s_time);
90
+ if (err != 0) {
91
+ printf("failed search(%s) with errno=%d\n", ip_string, err);
92
+ } else {
93
+ printf("{region: %s, took: %d μs}", region.value, cost_time);
94
+ }
95
+
96
+ // 清理 region 信息的内存资源,每次 search 之后都得调用
97
+ xdb_region_buffer_free(&region);
98
+
99
+ // 备注:并发使用,每一个线程需要单独定义并且初始化一个 searcher 查询对象。
100
+
101
+ // 3、关闭 xdb 查询器
102
+ xdb_close(&searcher);
103
+ xdb_clean_winsock(); // windows 下调用
104
+ return 0;
105
+ }
106
+ ```
107
+
108
+ ### 缓存 `VectorIndex` 索引
109
+
110
+ 我们可以提前从 xdb 文件中加载出来 VectorIndex 数据,然后全局缓存,每次创建 Searcher 对象的时候使用全局的 VectorIndex 缓存可以减少一次固定的 IO 操作,从而加速查询,减少 IO 压力。
111
+ ```c
112
+ #include <stdio.h>
113
+ #include "xdb_api.h"
114
+
115
+ int main(int argc, char *argv[]) {
116
+ xdb_vector_index_t *v_index;
117
+ xdb_searcher_t searcher;
118
+ xdb_region_buffer_t region;
119
+
120
+ // 使用 NULL 初始化 region_buffer,让其自动管理内存的分配
121
+ int err = xdb_region_buffer_init(&region, NULL, 0);
122
+ if (err != 0) {
123
+ printf("failed to init the region buffer with errcode=%d\n", err);
124
+ return 0;
125
+ }
126
+
127
+ // 在服务启动的时候初始化 winsock,不需要重复调用,只需要在 windows 系统下调用
128
+ err = xdb_init_winsock();
129
+ if (err != 0) {
130
+ printf("failed to init the winsock with errno=%d\n", err);
131
+ return 1;
132
+ }
133
+
134
+ // 1、从顶部描述的 db_path 加载 VectorIndex 索引。
135
+ // 得到 v_index 做成全局缓存,便于后续反复使用。
136
+ // 注意:v_index 不需要每次都加载,建议在服务启动的时候加载一次,然后做成全局资源。
137
+ v_index = xdb_load_vector_index_from_file(db_path);
138
+ if (v_index == NULL) {
139
+ printf("failed to load vector index from `%s`\n", db_path);
140
+ return 1;
141
+ }
142
+
143
+ // 2、使用全局的 VectorIndex 变量创建带 VectorIndex 缓存的 xdb 查询对象.
144
+ // @Note: 使用顶部描述的 db_path 和 version 来创建 searcher
145
+ err = xdb_new_with_vector_index(version, &searcher, db_path, v_index);
146
+ if (err != 0) {
147
+ printf("failed to create vector index cached searcher with errcode=%d\n", err);
148
+ return 2;
149
+ }
150
+
151
+
152
+ // 3、调用 search API 查询,IPv4 和 IPv6 都支持
153
+ const char *ip_string = "1.2.3.4";
154
+ // ip_string = "2001:4:112:ffff:ffff:ffff:ffff:ffff"; // IPv6
155
+
156
+ long cost_time = 0, s_time = xdb_now();
157
+ err = xdb_search_by_string(&searcher, ip_string, &region);
158
+ cost_time = (int) (xdb_now() - s_time);
159
+ if (err != 0) {
160
+ printf("failed search(%s) with errno=%d\n", ip_string, err);
161
+ } else {
162
+ printf("{region: %s, took: %d μs}", region.value, cost_time);
163
+ }
164
+
165
+ // 清理 region 信息的内存资源,每次 search 之后都得调用
166
+ xdb_region_buffer_free(&region);
167
+
168
+
169
+ // 备注:并发使用,每一个线程需要单独定义并且初始化一个 searcher 查询对象。
170
+
171
+ // 4、关闭 xdb 查询器,如果是要关闭服务,也需要释放 v_index 的内存。
172
+ xdb_close(&searcher);
173
+ xdb_close_vector_index(v_index);
174
+ xdb_clean_winsock();
175
+ return 0;
176
+ }
177
+ ```
178
+
179
+ ### 缓存整个 `xdb` 数据
180
+
181
+ 我们也可以预先加载整个 xdb 文件到内存,然后基于这个数据创建查询对象来实现完全基于内存的查询,类似之前的 memory search。
182
+ ```c
183
+ #include <stdio.h>
184
+ #include "xdb_api.h"
185
+
186
+ int main(int argc, char *argv[]) {
187
+ xdb_content_t *c_buffer;
188
+ xdb_searcher_t searcher;
189
+ xdb_region_buffer_t region;
190
+
191
+ // 使用 NULL 初始化 region_buffer,让其自动管理内存的分配
192
+ int err = xdb_region_buffer_init(&region, NULL, 0);
193
+ if (err != 0) {
194
+ printf("failed to init the region buffer with errcode=%d\n", err);
195
+ return 0;
196
+ }
197
+
198
+
199
+ // 在服务启动的时候初始化 winsock,不需要重复调用,只需要在 windows 系统下调用
200
+ err = xdb_init_winsock();
201
+ if (err != 0) {
202
+ printf("failed to init the winsock with errno=%d\n", err);
203
+ return 1;
204
+ }
205
+
206
+ // 1、从 顶部描述的 db_path 加载整个 xdb 的数据。
207
+ c_buffer = xdb_load_content_from_file(db_path);
208
+ if (v_index == NULL) {
209
+ printf("failed to load xdb content from `%s`\n", db_path);
210
+ return 1;
211
+ }
212
+
213
+ // 2、使用全局的 c_buffer 变量创建一个完全基于内存的 xdb 查询对象.
214
+ // @Note: 使用顶部描述的 version 来创建 searcher.
215
+ err = xdb_new_with_buffer(version, &searcher, c_buffer);
216
+ if (err != 0) {
217
+ printf("failed to create content cached searcher with errcode=%d\n", err);
218
+ return 2;
219
+ }
220
+
221
+ // 3、调用 search API 查询,IPv4 和 IPv6 都支持
222
+ const char *ip_string = "1.2.3.4";
223
+ // ip_string = "2001:4:112:ffff:ffff:ffff:ffff:ffff"; // IPv6
224
+
225
+ long cost_time = 0, s_time = xdb_now();
226
+ err = xdb_search_by_string(&searcher, ip_string, &region);
227
+ cost_time = (int) (xdb_now() - s_time);
228
+ if (err != 0) {
229
+ printf("failed search(%s) with errno=%d\n", ip_string, err);
230
+ } else {
231
+ printf("{region: %s, took: %d μs}", region.value, cost_time);
232
+ }
233
+
234
+ // 清理 region 信息的内存资源,每次 search 之后都得调用
235
+ xdb_region_buffer_free(&region);
236
+
237
+
238
+ // 备注:并发使用,使用这种方式创建的 xdb 查询对象可以安全用于并发。
239
+ // 建议在服务启动的时候创建好,然后一直安全并发使用,直到服务关闭。
240
+
241
+ // 4、关闭 xdb 查询器,关闭服务的时候需要释放 c_buffer 的内存。
242
+ xdb_close(&searcher);
243
+ xdb_close_content(c_buffer);
244
+ xdb_clean_winsock();
245
+ return 0;
246
+ }
247
+ ```
248
+
249
+ ### 关于定位信息的存储
250
+ 在旧版本的实现中,search相关的函数都是依靠指定一个 `region_buffer` 内存来用于存储地域信息,这种方式还是有很大的局限性。
251
+ 新的实现提供了一个 `xdb_region_buffer_t` 对象来管理这些内存的分配,你依然可以指定一个固定的 `region_buffer` 来创建 region 的内存管理,这个情况适合当你的地域信息的最大长度是可知的,这种方式可以减少运行过程中内存的碎片堆积。如果地域信息的长度不确定或者你的程序不适合提前分配一块内存来管理,你可以通过指定 NULL 的方式来初始化 `xdb_region_buffer_t`,这样对象会自动管理内存的分配,也适合任意长度的地域信息的存储,不过这种方式在长期的运行过程中肯定会增加内存碎片的堆积。
252
+ ```c
253
+ // 1, 通过指定一块内存来创建 region_buffer
254
+ char buffer[512];
255
+ xdb_region_buffer_t region;
256
+ int err = xdb_region_buffer_init(&region, buffer, sizeof(buffer));
257
+ if (err != 0) {
258
+ // 初始化失败
259
+ printf("failed to init region buffer width errcode=%d", err);
260
+ return;
261
+ }
262
+
263
+ // 2,通过指定 NULL 来创建 region_buffer,让其自动按需分配内存
264
+ xdb_region_buffer_t region;
265
+ int err = xdb_region_buffer_init(&region, NULL, 0);
266
+ if (err != 0) {
267
+ // 初始化失败
268
+ printf("failed to init region buffer width errcode=%d", err);
269
+ return;
270
+ }
271
+
272
+
273
+ // 备注:在每次调用 search 完成 IP 定位信息的查询后,你需要手动调用函数来释放内存 .
274
+ // search 函数使用未经清理的 region 信息会报错。
275
+ xdb_region_buffer_free(&region);
276
+ ```
277
+
278
+
279
+ # 测试程序编译
280
+
281
+ 通过如下方式编译得到 xdb_searcher 可执行程序:
282
+ ```bash
283
+ # cd 到 c binding 根目录
284
+ ➜ c git:(fr_c_ipv6) ✗ make
285
+ gcc -O2 -I./ xdb_util.c xdb_searcher.c main.c -o xdb_searcher
286
+ gcc -O2 -I./ xdb_util.c test_util.c -o test_util
287
+ ```
288
+
289
+
290
+ # 查询测试
291
+
292
+ 通过 `xdb_searcher search` 命令来测试对 xdb 的查询:
293
+ ```bash
294
+ ➜ c git:(fr_c_ipv6) ✗ ./xdb_searcher search
295
+ ./xdb_searcher search [command options]
296
+ options:
297
+ --db string ip2region binary xdb file path
298
+ --cache-policy string cache policy: file/vectorIndex/content
299
+ ```
300
+
301
+ 例如:使用默认的 data/ip2region_v4.xdb 进行 IPv4 查询测试:
302
+ ```bash
303
+ ➜ c git:(fr_c_ipv6) ✗ ./xdb_searcher search --db=../../data/ip2region_v4.xdb
304
+ ip2region xdb searcher test program
305
+ source xdb: ../../data/ip2region_v4.xdb (IPv4, vectorIndex)
306
+ type 'quit' to exit
307
+ ip2region>> 120.229.45.2
308
+ {region: 中国|广东省|深圳市|移动, io_count: 3, took: 29 μs}
309
+ ```
310
+
311
+ 例如:使用默认的 data/ip2region_v6.xdb 进行 IPv6 查询测试:
312
+ ```bash
313
+ ➜ c git:(fr_c_ipv6) ✗ ./xdb_searcher search --db=../../data/ip2region_v6.xdb
314
+ ip2region xdb searcher test program
315
+ source xdb: ../../data/ip2region_v6.xdb (IPv6, vectorIndex)
316
+ type 'quit' to exit
317
+ ip2region>> ::
318
+ {region: , io_count: 1, took: 38 μs}
319
+ ip2region>> 2604:bc80:8001:11a4:ffff:ffff:ffff:ffff
320
+ {region: 美国|特拉华州|刘易斯|数据中心, io_count: 14, took: 77 μs}
321
+ ip2region>> 240e:3b7:3272:d8d0:db09:c067:8d59:539e
322
+ {region: 中国|广东省|深圳市|家庭宽带, io_count: 8, took: 46 μs}
323
+ ```
324
+
325
+ 输入 ip 即可进行查询,输入 quit 即可退出测试程序。也可以分别设置 `cache-policy` 为 file/vectorIndex/content 来测试三种不同的缓存实现的效率。
326
+
327
+
328
+
329
+ # bench 测试
330
+
331
+ 通过 `xdb_searcher bench` 命令来进行 bench 测试,一方面确保查询程序和 `xdb` 文件没有错误,另一方面可以通过大量的查询得到评价的查询性能:
332
+ ```bash
333
+ ➜ c git:(fr_c_ipv6) ✗ ./xdb_searcher bench
334
+ ./xdb_searcher bench [command options]
335
+ options:
336
+ --db string ip2region binary xdb file path
337
+ --src string source ip text file path
338
+ --cache-policy string cache policy: file/vectorIndex/content
339
+ ```
340
+
341
+ 例如:通过默认的 data/ip2region_v4.xdb 和 data/ipv4_source.txt 来进行 IPv4 的 bench 测试:
342
+ ```bash
343
+ ➜ c git:(fr_c_ipv6) ✗ ./xdb_searcher bench --db=../../data/ip2region_v4.xdb --src=../../data/ipv4_source.txt
344
+ Bench finished, {cache_policy: vectorIndex, total: 1367686, took: 7.640s, cost: 5 μs/op}
345
+ ```
346
+
347
+ 例如:通过默认的 data/ip2region_v6.xdb 和 data/ipv6_source.txt 来进行 IPv6 的 bench 测试:
348
+ ```bash
349
+ ➜ c git:(fr_c_ipv6) ✗ ./xdb_searcher bench --db=../../data/ip2region_v6.xdb --src=../../data/ipv6_source.txt
350
+ Bench finished, {cache_policy: vectorIndex, total: 34159862, took: 857.750s, cost: 24 μs/op}
351
+ ```
352
+
353
+ 可以设置 `cache-policy` 参数来分别测试 file/vectorIndex/content 不同缓存实现机制的效率。 @Note:请注意 bench 使用的 src 文件需要是生成对应的 xdb 文件相同的源文件。
@@ -24,13 +24,16 @@
24
24
  #include <ws2tcpip.h>
25
25
  #include <windows.h>
26
26
  #pragma comment(lib, "ws2_32.lib")
27
- #elif ( defined(linux) || defined(_UNIX) || defined(__APPLE__) )
27
+ #elif (defined(linux) || defined(_UNIX) || defined(__APPLE__) || defined(unix) || defined(__unix) || defined(__unix__) || defined(__linux__) || defined(linux) || defined(__linux))
28
28
  # define XDB_PUBLIC(type) extern type
29
29
  # define XDB_PRIVATE(type) static inline type
30
30
  # define XDB_LINUX
31
31
  #include <sys/socket.h>
32
32
  #include <netinet/in.h>
33
33
  #include <arpa/inet.h>
34
+ #else
35
+ # define XDB_PUBLIC(type) type
36
+ # define XDB_PRIVATE(type) static type
34
37
  #endif
35
38
 
36
39
  #define xdb_calloc( _blocks, _bytes ) calloc( _blocks, _bytes )
@@ -224,6 +227,10 @@ XDB_PUBLIC(int) xdb_region_buffer_init(xdb_region_buffer_t *, char *, size_t);
224
227
  // returns: 0 for ok or failed
225
228
  XDB_PUBLIC(int) xdb_region_buffer_alloc(xdb_region_buffer_t *, int);
226
229
 
230
+ // empty alloc - empty string
231
+ // returns: 0 - always
232
+ XDB_PUBLIC(int) xdb_region_buffer_empty(xdb_region_buffer_t *);
233
+
227
234
  XDB_PUBLIC(void) xdb_region_buffer_free(xdb_region_buffer_t *);
228
235
 
229
236
  // xdb searcher structure
@@ -269,4 +276,4 @@ XDB_PUBLIC(int) xdb_get_io_count(xdb_searcher_t *);
269
276
 
270
277
  // --- END xdb searcher api
271
278
 
272
- #endif // C_IP2REGION_XDB_H
279
+ #endif // C_IP2REGION_XDB_H
@@ -57,9 +57,38 @@ XDB_PUBLIC(int) xdb_region_buffer_alloc(xdb_region_buffer_t *region, int length)
57
57
  return 0;
58
58
  }
59
59
 
60
+ // fixed internal empty string ptr
61
+ static char * _empty_region_string = "\0";
62
+
63
+ XDB_PUBLIC(int) xdb_region_buffer_empty(xdb_region_buffer_t *region) {
64
+ // no allocation supports for the buffer wapper
65
+ if (region->type == xdb_region_buffer_wrapper) {
66
+ region->value[0] = '\0';
67
+ return 0;
68
+ }
69
+
70
+ // ensure that the value were freed
71
+ // by calling #xdb_region_buffer_free
72
+ if (region->value != NULL) {
73
+ return 3;
74
+ }
75
+
76
+ region->value = _empty_region_string;
77
+ region->length = 0;
78
+ return 0;
79
+ }
80
+
60
81
  XDB_PUBLIC(void) xdb_region_buffer_free(xdb_region_buffer_t *region) {
61
82
  if (region->type == xdb_region_buffer_auto) {
62
- xdb_free(region->value);
83
+ // empty string interception
84
+ if (region->length == 0
85
+ || region->value == _empty_region_string) {
86
+ // do nothing for empty string
87
+ } else {
88
+ xdb_free(region->value);
89
+ }
90
+
91
+ // reset the value
63
92
  region->value = NULL;
64
93
  }
65
94
  }
@@ -196,7 +225,9 @@ XDB_PUBLIC(int) xdb_search(xdb_searcher_t *xdb, const bytes_ip_t *ip_bytes, int
196
225
 
197
226
  // printf("data_len=%u, data_ptr=%u\n", data_len, data_ptr);
198
227
  if (data_len == 0) {
199
- return 100;
228
+ // return 100;
229
+ xdb_region_buffer_empty(region);
230
+ return err;
200
231
  }
201
232
 
202
233
  // buffer alloc checking
@@ -239,4 +270,4 @@ XDB_PUBLIC(xdb_version_t *) xdb_get_version(xdb_searcher_t *xdb) {
239
270
 
240
271
  XDB_PUBLIC(int) xdb_get_io_count(xdb_searcher_t *xdb) {
241
272
  return xdb->io_count;
242
- }
273
+ }
@@ -245,9 +245,14 @@ XDB_PUBLIC(int) xdb_verify_from_header(FILE *handle, xdb_header_t *header) {
245
245
  }
246
246
 
247
247
  long int fileBytes = ftell(handle);
248
- long int maxFilePtr = (1L << (runtime_ptr_bytes * 8)) - 1;
249
- // printf("fileBytes: %ld, maxFilePtr: %ld\n", fileBytes, maxFilePtr);
250
- if (fileBytes > maxFilePtr) {
248
+ if (fileBytes < 0) {
249
+ return 3;
250
+ }
251
+
252
+ // Use unsigned long long for maxFilePtr to avoid overflow
253
+ unsigned long long maxFilePtr = (1ULL << (runtime_ptr_bytes * 8)) - 1;
254
+ // printf("fileBytes: %ld, maxFilePtr: %llu\n", fileBytes, maxFilePtr);
255
+ if ((unsigned long long)fileBytes > maxFilePtr) {
251
256
  return 4;
252
257
  }
253
258
 
@@ -392,10 +397,12 @@ XDB_PUBLIC(int) xdb_le_get_uint16(const char *buffer, int offset) {
392
397
  }
393
398
 
394
399
  XDB_PUBLIC(xdb_version_t *) xdb_parse_ip(const string_ip_t *ip_string, bytes_ip_t *buffer, size_t length) {
400
+ char *d_ptr = strchr(ip_string, '.');
401
+ char *c_ptr = strchr(ip_string, ':');
395
402
  // version check
396
- if (strchr(ip_string, '.') != NULL && strchr(ip_string, ':') == NULL) {
403
+ if (d_ptr != NULL && c_ptr == NULL) {
397
404
  return xdb_parse_v4_ip(ip_string, buffer, length);
398
- } else if (strchr(ip_string, ':') != NULL) {
405
+ } else if (c_ptr != NULL) {
399
406
  return xdb_parse_v6_ip(ip_string, buffer, length);
400
407
  }
401
408
 
@@ -493,4 +500,4 @@ XDB_PUBLIC(int) xdb_ip_sub_compare(const bytes_ip_t *ip1, int bytes, const char
493
500
  }
494
501
  }
495
502
  return 0;
496
- }
503
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts-ip2region2",
3
- "version": "1.1.2",
3
+ "version": "1.2.1",
4
4
  "description": "TypeScript-first ip2region implementation with native Node.js addon. Ultra-fast IPv4/IPv6 geolocation queries.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -11,6 +11,7 @@
11
11
  "rebuild": "node-gyp rebuild",
12
12
  "clean": "node-gyp clean",
13
13
  "test": "npm run build:all && node test.js",
14
+ "benchmark": "node benchmark.js",
14
15
  "example": "npm run build:all && node example.js",
15
16
  "prepublishOnly": "npm run build:all"
16
17
  },