Compare commits
1225 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
2955431edb | |
|
|
c0d85cc71d | |
|
|
d5e3616d8e | |
|
|
cd40e3a1d5 | |
|
|
ee19ac95ca | |
|
|
a99989343e | |
|
|
324bfdf35d | |
|
|
f4d81dd3dd | |
|
|
ba96deaecf | |
|
|
c4eb1a14ce | |
|
|
dbef88e337 | |
|
|
30ed0cf028 | |
|
|
d06690f651 | |
|
|
daf6749c34 | |
|
|
e50ead2cba | |
|
|
b1eb00e25e | |
|
|
fac3f73193 | |
|
|
d1dda93abd | |
|
|
44013251b6 | |
|
|
546ce589a9 | |
|
|
507b7bddc1 | |
|
|
25513cb246 | |
|
|
445eb4cb8e | |
|
|
a1667d1182 | |
|
|
d189535bb8 | |
|
|
06d54d8084 | |
|
|
8849cc9130 | |
|
|
c911ca1290 | |
|
|
241831a6f0 | |
|
|
2f0dc5f33f | |
|
|
c1db56c369 | |
|
|
211c374ee2 | |
|
|
a3b8d817ee | |
|
|
b7bb22589f | |
|
|
1068bf05a3 | |
|
|
fc46254325 | |
|
|
7b320846c9 | |
|
|
e360f3d031 | |
|
|
e7c240a8d9 | |
|
|
d638015592 | |
|
|
3eb65a2657 | |
|
|
fac3d0376a | |
|
|
a8b9d542c6 | |
|
|
82a73fa60d | |
|
|
8b79b69524 | |
|
|
c5fb71facb | |
|
|
62e1f98497 | |
|
|
2af5ee176c | |
|
|
0ce74b8873 | |
|
|
f4fcd5e900 | |
|
|
072b6fed24 | |
|
|
f939363a19 | |
|
|
353524325a | |
|
|
59cf547107 | |
|
|
3147144a7f | |
|
|
d4299a1d1b | |
|
|
18ecd7ae85 | |
|
|
5fbdef93cd | |
|
|
a90191e8d8 | |
|
|
593e028677 | |
|
|
72c657c378 | |
|
|
19a8816827 | |
|
|
787bd7ae07 | |
|
|
5b10989ac2 | |
|
|
90e827633a | |
|
|
4dd0a1e006 | |
|
|
77134c4315 | |
|
|
fedf5aaf03 | |
|
|
d28a10b0c1 | |
|
|
aab908c923 | |
|
|
c314ef54ca | |
|
|
69d267d85d | |
|
|
a603a75ab7 | |
|
|
d730db816a | |
|
|
d36edc6e15 | |
|
|
1d9ffabfeb | |
|
|
03cc433717 | |
|
|
3562b7af8e | |
|
|
165746ba38 | |
|
|
79243eb0ba | |
|
|
be71b92f20 | |
|
|
d380827871 | |
|
|
1a08f5e568 | |
|
|
36c9de10bb | |
|
|
61ef15cff0 | |
|
|
d6444e740f | |
|
|
e3094c8ef2 | |
|
|
5244ebad08 | |
|
|
4d391acac2 | |
|
|
5cb17713a7 | |
|
|
e1f0d5126a | |
|
|
d9fc17727e | |
|
|
dabe7798f6 | |
|
|
8f841e6237 | |
|
|
d7b60f41e4 | |
|
|
83d6fafaa0 | |
|
|
02f6b31894 | |
|
|
97e647234a | |
|
|
1209b72452 | |
|
|
62aa42e05e | |
|
|
e76f995ceb | |
|
|
edb29aea3b | |
|
|
8a7b088216 | |
|
|
897ee5c0a9 | |
|
|
1eba8ece3b | |
|
|
007cb0808a | |
|
|
7d6ea00303 | |
|
|
e183607da5 | |
|
|
b0466348da | |
|
|
04e51e2182 | |
|
|
c63b1dc5db | |
|
|
1a29562c2d | |
|
|
29c7048f93 | |
|
|
fa16a4a45d | |
|
|
8adec5a3c8 | |
|
|
51e194a105 | |
|
|
b965ab8be9 | |
|
|
a720267842 | |
|
|
112dfb99b5 | |
|
|
ca665cfd2e | |
|
|
0ad8031754 | |
|
|
fb0efface4 | |
|
|
e373f92312 | |
|
|
3d21a8247c | |
|
|
d5e10e1383 | |
|
|
1a8d01d967 | |
|
|
e87f580510 | |
|
|
962335954a | |
|
|
2c01e01d2c | |
|
|
79886f77e7 | |
|
|
132221b494 | |
|
|
aef4f91348 | |
|
|
9d4b165409 | |
|
|
4b55d14fca | |
|
|
b030661865 | |
|
|
537e6f9306 | |
|
|
aaad26244d | |
|
|
a31a966546 | |
|
|
832e3fd77d | |
|
|
ef51b901ee | |
|
|
e8242a24ea | |
|
|
30e5f2732d | |
|
|
1c6d299efa | |
|
|
dce0bb582f | |
|
|
b3115ba7aa | |
|
|
81885a16cf | |
|
|
2b61de20d6 | |
|
|
7a1970729a | |
|
|
5a53add68b | |
|
|
c9e8acaffc | |
|
|
ce92aabc11 | |
|
|
3f0480be1f | |
|
|
a694cfa8d2 | |
|
|
8a44c98f99 | |
|
|
cab8fbc4ab | |
|
|
7d11be2ce7 | |
|
|
8302525ae8 | |
|
|
e926501591 | |
|
|
87418d879a | |
|
|
16fa5572f2 | |
|
|
39c297ad04 | |
|
|
6081603bd8 | |
|
|
2861abcb83 | |
|
|
daf54bf877 | |
|
|
6c98b8faf9 | |
|
|
1dc0003661 | |
|
|
c4a3a2e1f8 | |
|
|
0e828bba0d | |
|
|
b0ab8a0b2f | |
|
|
9caef15329 | |
|
|
572fda5910 | |
|
|
67d709ab39 | |
|
|
076f3918fa | |
|
|
9411b7f8c2 | |
|
|
75c375d146 | |
|
|
5037816f41 | |
|
|
aafee317c8 | |
|
|
839d5f59a5 | |
|
|
d4180c0f57 | |
|
|
8da3f55092 | |
|
|
7ff7e5f61f | |
|
|
44fd27f027 | |
|
|
3539f5e4e3 | |
|
|
7bb61ec59c | |
|
|
89852a70f0 | |
|
|
d427b4dcd8 | |
|
|
ad4253dbb0 | |
|
|
e6e3087144 | |
|
|
ad0cfd6bd9 | |
|
|
0cf4cf8e26 | |
|
|
ba2c9ec119 | |
|
|
91f281d53c | |
|
|
05158e0a2e | |
|
|
384567f184 | |
|
|
937c0772ec | |
|
|
ebc6253dfe | |
|
|
969a5f0e03 | |
|
|
c2bb78aecd | |
|
|
e139731d9e | |
|
|
96717ab155 | |
|
|
0520f299ee | |
|
|
bbe2dddecf | |
|
|
14753ffc29 | |
|
|
3127ac0cdc | |
|
|
108ef78a51 | |
|
|
f2c988287c | |
|
|
7bab822a49 | |
|
|
1df8b55789 | |
|
|
68cd1053d2 | |
|
|
398149f17e | |
|
|
4c569581b8 | |
|
|
3d876f895e | |
|
|
0863e172eb | |
|
|
437a2c424a | |
|
|
2cb7863112 | |
|
|
249ea17178 | |
|
|
697eabc33b | |
|
|
930924bb34 | |
|
|
7839a335ed | |
|
|
91816b108b | |
|
|
31dabf8c95 | |
|
|
7628ba32c2 | |
|
|
4577e9a17b | |
|
|
f302c68012 | |
|
|
fe2dfc1088 | |
|
|
93f7652594 | |
|
|
0d152d91a5 | |
|
|
351be412e6 | |
|
|
bd4a2d6bd1 | |
|
|
bf76adad4e | |
|
|
02e10db478 | |
|
|
ad08637d21 | |
|
|
245bbaa366 | |
|
|
56606de2f6 | |
|
|
de6089e321 | |
|
|
37f5e4475c | |
|
|
1f850dcde4 | |
|
|
7f9e6b8586 | |
|
|
b0de60f6cb | |
|
|
f12b9765b3 | |
|
|
8c20994b12 | |
|
|
897af4532e | |
|
|
9669585216 | |
|
|
e84ea900d4 | |
|
|
450fce589c | |
|
|
9b0bedc045 | |
|
|
6059d63504 | |
|
|
e11a390afc | |
|
|
ff75ddbcfd | |
|
|
ea297041dd | |
|
|
c68cc0097c | |
|
|
c2a0a55005 | |
|
|
b52555c5ec | |
|
|
cb9ed02bc6 | |
|
|
d4b8f9f841 | |
|
|
7f454276a1 | |
|
|
f80830f666 | |
|
|
2fbf899140 | |
|
|
72912301ee | |
|
|
6531f3970f | |
|
|
b44654230f | |
|
|
1b9fb0c95c | |
|
|
242bff6208 | |
|
|
a1c536ba98 | |
|
|
2454568203 | |
|
|
48dce3545e | |
|
|
dde56752af | |
|
|
d9ce5da85f | |
|
|
dae949b8ce | |
|
|
54628265b3 | |
|
|
9d67e3fcad | |
|
|
bbccbfa451 | |
|
|
1f8bf73a0f | |
|
|
620646e8e0 | |
|
|
1adc5643aa | |
|
|
316355a72e | |
|
|
e73321a860 | |
|
|
ab22a5e03c | |
|
|
ace0c7f300 | |
|
|
2a8e3444d4 | |
|
|
4c9ef35a9a | |
|
|
540831e6a9 | |
|
|
a220db2345 | |
|
|
619b6645a5 | |
|
|
40c6c2c1bd | |
|
|
89bb8cb709 | |
|
|
e64864aa40 | |
|
|
c78ba4827d | |
|
|
a3d1ba5d30 | |
|
|
000e19d419 | |
|
|
b7f2ffa285 | |
|
|
12c94cf0b6 | |
|
|
3f84cd41c1 | |
|
|
9669237830 | |
|
|
8cecf2f3aa | |
|
|
ab16071797 | |
|
|
4a4c8de636 | |
|
|
942f3d9dbd | |
|
|
a383e4dd17 | |
|
|
b5ae94b36d | |
|
|
efbbb576ee | |
|
|
5c5c0ab160 | |
|
|
7f2d1ae789 | |
|
|
a91c8e9747 | |
|
|
f8a154aeda | |
|
|
a8087e06b7 | |
|
|
317a900077 | |
|
|
096d68ea77 | |
|
|
23ae923cff | |
|
|
c0f0efdb95 | |
|
|
b3c2d40588 | |
|
|
7612e679fb | |
|
|
5b8b47df5e | |
|
|
0b3635b3c3 | |
|
|
33f8bf766d | |
|
|
0b3d794738 | |
|
|
613501fc00 | |
|
|
e95452c1c8 | |
|
|
3ad925406b | |
|
|
6e4af36dc9 | |
|
|
d1151accde | |
|
|
2c9ba9c56c | |
|
|
85144e1c40 | |
|
|
426038b3fe | |
|
|
2c7b170637 | |
|
|
14d2332483 | |
|
|
ad84da7d3c | |
|
|
9ecca28fc2 | |
|
|
4ea2e7360d | |
|
|
edc2559489 | |
|
|
7bd81fecbf | |
|
|
6684d0a928 | |
|
|
30fa54a83f | |
|
|
ca7fc93953 | |
|
|
db50f7b0da | |
|
|
5cfd5f8242 | |
|
|
87b69e6b5f | |
|
|
002c482db3 | |
|
|
75d944eaf7 | |
|
|
92cc3fd7cb | |
|
|
f1d0e1bee0 | |
|
|
d7d06e2185 | |
|
|
55a493fd8e | |
|
|
f03861162f | |
|
|
cbf8f9bfa8 | |
|
|
3aa732d1ce | |
|
|
9ef1fc34ac | |
|
|
62ab160572 | |
|
|
87666aa353 | |
|
|
a2778f9b98 | |
|
|
af82b7e18e | |
|
|
a6cb5161ff | |
|
|
d4d211c50b | |
|
|
31cd078455 | |
|
|
499e091761 | |
|
|
4f39707beb | |
|
|
7fab19c46f | |
|
|
bd30b77d54 | |
|
|
cd78ff2797 | |
|
|
8692309c5b | |
|
|
aa5437c637 | |
|
|
8573f2f04e | |
|
|
6eee95967b | |
|
|
ef7ac3c088 | |
|
|
4b7172766c | |
|
|
dc64c27abd | |
|
|
1da81ffc02 | |
|
|
ae94b2dac3 | |
|
|
f8f01ecd13 | |
|
|
db95055a56 | |
|
|
32a755ac3f | |
|
|
784f336646 | |
|
|
732d4eb945 | |
|
|
e753437afc | |
|
|
ab0c39b7cd | |
|
|
db3b9b22ae | |
|
|
91e542de38 | |
|
|
bec072cd04 | |
|
|
bf0ded1171 | |
|
|
c4113e5eae | |
|
|
731269ebad | |
|
|
9797b595ea | |
|
|
3ea717043c | |
|
|
42b9cebb9d | |
|
|
9e2ca6bd98 | |
|
|
0422ed3f4f | |
|
|
7f5844ca94 | |
|
|
cc97bb80a2 | |
|
|
d4d5525392 | |
|
|
6c653f85cd | |
|
|
47067dbe73 | |
|
|
f6213aaaa4 | |
|
|
3a3abdd01c | |
|
|
bbf5df5601 | |
|
|
0f2260d116 | |
|
|
572aa7b76a | |
|
|
580d1bb431 | |
|
|
029a5578aa | |
|
|
501c3c086d | |
|
|
08d3ef69b0 | |
|
|
4ebfa61179 | |
|
|
8a7a899c64 | |
|
|
f7ad4309f8 | |
|
|
d56230518a | |
|
|
2a77acf1c0 | |
|
|
d5d0c15c8d | |
|
|
b3498197a8 | |
|
|
d10c57b598 | |
|
|
7050da1a68 | |
|
|
bb3c845c81 | |
|
|
ca6c0506e6 | |
|
|
79cc6c1b0d | |
|
|
bd69a255fd | |
|
|
8f6effd8eb | |
|
|
d503206e14 | |
|
|
b3383680f9 | |
|
|
567224ca60 | |
|
|
541420b8df | |
|
|
6cb1da2794 | |
|
|
ec7cb6d3ed | |
|
|
a30d922225 | |
|
|
a1b2c21767 | |
|
|
1e31b4b143 | |
|
|
baa9f62d6b | |
|
|
3b4a229918 | |
|
|
d362231ae8 | |
|
|
1ad01ff98b | |
|
|
5deb6d6553 | |
|
|
444b2036d1 | |
|
|
35e505bd74 | |
|
|
2fe8032d8b | |
|
|
69eb94dcd8 | |
|
|
0ad3172f2e | |
|
|
2ac7655f62 | |
|
|
6aab24fb88 | |
|
|
b94123fee3 | |
|
|
0caff12306 | |
|
|
6ad1e8e2e2 | |
|
|
9e979efd7b | |
|
|
f594838784 | |
|
|
24dcb24e24 | |
|
|
ddb2b4ff1d | |
|
|
2662fb05c2 | |
|
|
7e4d37b1db | |
|
|
d1715c75f6 | |
|
|
c82d2e6117 | |
|
|
ffb632c3d0 | |
|
|
11f890f3a4 | |
|
|
87b6cd5247 | |
|
|
61dc2af93d | |
|
|
413ccd32ce | |
|
|
e3b87326d2 | |
|
|
5a51b75411 | |
|
|
a2b76d38a9 | |
|
|
8c7c83cf7f | |
|
|
fbadc7cca0 | |
|
|
f03d33e5fe | |
|
|
c809c072bc | |
|
|
5c1522fce4 | |
|
|
a3eedc5438 | |
|
|
4f8cdb05c9 | |
|
|
008bbbe4d7 | |
|
|
d35a7b9120 | |
|
|
4d71b47e05 | |
|
|
40ea8a267f | |
|
|
2dcccf22a3 | |
|
|
e073e26f52 | |
|
|
ed42fd0fba | |
|
|
9959117d92 | |
|
|
3cab1bae2e | |
|
|
5a8d7c4152 | |
|
|
4507c2f706 | |
|
|
ad78dbe547 | |
|
|
e924a7c17d | |
|
|
6c463282d0 | |
|
|
bb19c7110a | |
|
|
5fce4eaa01 | |
|
|
01bd749d4a | |
|
|
3693eb47a8 | |
|
|
e39ac2d6d1 | |
|
|
d97967d565 | |
|
|
8650273a85 | |
|
|
ccf7f323c2 | |
|
|
a930a17e39 | |
|
|
0a456283e6 | |
|
|
d6345bc015 | |
|
|
086250c666 | |
|
|
54a160e230 | |
|
|
2d71241a3b | |
|
|
8a254e3e83 | |
|
|
95f5fcfcdb | |
|
|
2cd59657e1 | |
|
|
1f1ad5aaf6 | |
|
|
e887c01615 | |
|
|
df6004b42e | |
|
|
05c7c17af0 | |
|
|
a6c0a66a36 | |
|
|
a137813509 | |
|
|
0179edc3a8 | |
|
|
4ff67f168c | |
|
|
cb3329390a | |
|
|
93c5088591 | |
|
|
1ac2113afc | |
|
|
139972dfb8 | |
|
|
379ab6ce73 | |
|
|
d3592eb7cd | |
|
|
b6410e16d0 | |
|
|
d7f4ef5a07 | |
|
|
7a4bd4ed2b | |
|
|
a6931ce31b | |
|
|
32e005e1e6 | |
|
|
b5b00aeb87 | |
|
|
b16fe21506 | |
|
|
931fb9e67b | |
|
|
7576daa6cc | |
|
|
75893f0331 | |
|
|
355c0d8673 | |
|
|
815b1fb54b | |
|
|
29fa8e25ab | |
|
|
13324d1575 | |
|
|
46f56f0eca | |
|
|
2fe0d1f59e | |
|
|
b01253e6b3 | |
|
|
6712ee9504 | |
|
|
5fa69300ad | |
|
|
7e599e05a0 | |
|
|
a36a85558f | |
|
|
48c2b395bc | |
|
|
dff3084b93 | |
|
|
8fbdf82797 | |
|
|
73c8b20d3a | |
|
|
69b9f062e4 | |
|
|
1575e581cd | |
|
|
4cff9eb6ee | |
|
|
0fd0bc59d0 | |
|
|
c64837db67 | |
|
|
eb80b89c0e | |
|
|
9a14e32736 | |
|
|
4c1b0362c8 | |
|
|
5d11d6c0fb | |
|
|
7bbe7d681d | |
|
|
95c129c7d1 | |
|
|
716326f4bc | |
|
|
d1e9b6e3bc | |
|
|
ae344d20aa | |
|
|
537077279a | |
|
|
a91fdd38bc | |
|
|
9af9c927fe | |
|
|
f226ef1f79 | |
|
|
e8b6049584 | |
|
|
6db9654c7d | |
|
|
c6c0ee954f | |
|
|
4911bdcd41 | |
|
|
23b2446736 | |
|
|
6727987960 | |
|
|
629f509c6f | |
|
|
23b3c3bc9b | |
|
|
1cf2e419e1 | |
|
|
697b46bb2e | |
|
|
83fd149c59 | |
|
|
87d53b5018 | |
|
|
f0a9462041 | |
|
|
6a729e40a5 | |
|
|
fdec88605f | |
|
|
6194a4c106 | |
|
|
f7f5d851bb | |
|
|
0623fee57a | |
|
|
beb0e452ce | |
|
|
7cc7d4d9cf | |
|
|
a8a17b0f98 | |
|
|
538ea4194c | |
|
|
03eef4a75e | |
|
|
6b2e18e551 | |
|
|
0307a3df14 | |
|
|
ae69123857 | |
|
|
037663e355 | |
|
|
d3ef2dcc28 | |
|
|
aade8810ba | |
|
|
35eef70083 | |
|
|
6092e6d076 | |
|
|
04a4c1f1ce | |
|
|
9cbcca588a | |
|
|
33beaf96c9 | |
|
|
34e89d3f50 | |
|
|
2735819b4f | |
|
|
980c9cfdd4 | |
|
|
cdd2a2fc85 | |
|
|
c0058781c9 | |
|
|
b830361c52 | |
|
|
ac46b7359e | |
|
|
b73e190905 | |
|
|
4996c0ff6a | |
|
|
f9402614f6 | |
|
|
73e4e6047f | |
|
|
7a9a53a058 | |
|
|
c0fda2e211 | |
|
|
e569ef2aad | |
|
|
1795f2ad36 | |
|
|
d8ace3508b | |
|
|
3931fea9a5 | |
|
|
4529f9819c | |
|
|
bbd811e1be | |
|
|
b41a805cff | |
|
|
78c2fab10a | |
|
|
c1808255e7 | |
|
|
36f5f129a4 | |
|
|
95bcc92f11 | |
|
|
6551e8e854 | |
|
|
47d4f1e213 | |
|
|
da47234738 | |
|
|
1d32d5e6aa | |
|
|
bff6b5661c | |
|
|
ed57fa5c52 | |
|
|
ec1ae9504e | |
|
|
8952539b0f | |
|
|
39a806695e | |
|
|
06eb5c6e82 | |
|
|
9eabc9fcdc | |
|
|
e49a076dee | |
|
|
c376ccd72b | |
|
|
37dc606a59 | |
|
|
1a11c91061 | |
|
|
e481fea2b9 | |
|
|
3a2d02923e | |
|
|
89e20ab7b6 | |
|
|
40f2cdfcd9 | |
|
|
d2b12ef0c8 | |
|
|
40c7f53fc9 | |
|
|
3b7cc3d477 | |
|
|
22d2e2ed01 | |
|
|
19c2aa8199 | |
|
|
c82399f3b9 | |
|
|
5826ba610a | |
|
|
6943aae2d0 | |
|
|
f793a48b82 | |
|
|
f8f5f687e6 | |
|
|
b3c9dff603 | |
|
|
27b5efde55 | |
|
|
01902a6792 | |
|
|
3066d243cd | |
|
|
aa834af535 | |
|
|
86553fba20 | |
|
|
7256345713 | |
|
|
3ef87ff727 | |
|
|
75cddb1f2e | |
|
|
b66471f0ba | |
|
|
aad2d5d306 | |
|
|
4c87a3b71e | |
|
|
0c68fd1ed8 | |
|
|
6ed8bf0118 | |
|
|
b8fc536521 | |
|
|
dd18282102 | |
|
|
769f708b16 | |
|
|
a72a6b8282 | |
|
|
edaca47635 | |
|
|
df1252345b | |
|
|
7f5ec337d0 | |
|
|
c8446edaab | |
|
|
5fe0151907 | |
|
|
f24ed31469 | |
|
|
068dc2aee8 | |
|
|
da40a4ad17 | |
|
|
77b70409c6 | |
|
|
a5b42e32fe | |
|
|
7599cb1dfc | |
|
|
5412c3e365 | |
|
|
4697640fbc | |
|
|
4e588db3de | |
|
|
a406f0e636 | |
|
|
4ddd785bf0 | |
|
|
fed6788fe4 | |
|
|
09b05a6026 | |
|
|
885c4d815f | |
|
|
6607c9e61c | |
|
|
0b5148be76 | |
|
|
e8595bf525 | |
|
|
a8571d4a99 | |
|
|
0bc0d9ea87 | |
|
|
d96d54bbaa | |
|
|
9578720cd1 | |
|
|
1d09953acb | |
|
|
b04e477b99 | |
|
|
138eea8849 | |
|
|
3fef2a2fad | |
|
|
a4b39f5315 | |
|
|
d75b5929cd | |
|
|
2a579d8db6 | |
|
|
a8cb6b9c31 | |
|
|
874a0fbb06 | |
|
|
decee31d93 | |
|
|
f053e97b23 | |
|
|
0a4d16b434 | |
|
|
4803a7ecfb | |
|
|
ab9d5a443a | |
|
|
6ae8e496a3 | |
|
|
2f647b955c | |
|
|
de4f5c0c22 | |
|
|
4efa1b36b2 | |
|
|
18abb9d999 | |
|
|
1e88ed8163 | |
|
|
8dbbd05060 | |
|
|
02dc2b4739 | |
|
|
c0811c8989 | |
|
|
32438b5443 | |
|
|
3c88e7a12f | |
|
|
42a3370e65 | |
|
|
c4b722f74b | |
|
|
02530f3759 | |
|
|
8d606affff | |
|
|
6c9fdc5d26 | |
|
|
f10b8971e1 | |
|
|
654080b1d0 | |
|
|
7e7a612437 | |
|
|
5702ca9259 | |
|
|
76345e67b0 | |
|
|
e57adde491 | |
|
|
102b128c0d | |
|
|
95b822ac38 | |
|
|
c48150216e | |
|
|
a5f0837bb5 | |
|
|
09c05be3f2 | |
|
|
cb84133da8 | |
|
|
80ee44a259 | |
|
|
1355506ff2 | |
|
|
b3f775a196 | |
|
|
83e15427a8 | |
|
|
d686979718 | |
|
|
db3269eec6 | |
|
|
cbfd6408ad | |
|
|
750b8768be | |
|
|
32631fc9ad | |
|
|
aef51bede0 | |
|
|
61788530fd | |
|
|
8b3a039edc | |
|
|
f489d2a39a | |
|
|
0c960c4122 | |
|
|
2c46a386cc | |
|
|
e085caf148 | |
|
|
8e3c440ada | |
|
|
7ff7772e14 | |
|
|
0dcea0f995 | |
|
|
d7f9fb6882 | |
|
|
bce4c1e188 | |
|
|
3e6440f1dd | |
|
|
d2b81ac9a0 | |
|
|
ae5ed74601 | |
|
|
d6e8813fef | |
|
|
b4a525a737 | |
|
|
dcd3e94802 | |
|
|
90bbf5184f | |
|
|
3db3f4eb52 | |
|
|
6a70f93442 | |
|
|
7a03c21dfc | |
|
|
82d64d8aca | |
|
|
9837f68d42 | |
|
|
31d5bb6ebc | |
|
|
e3f2e572ae | |
|
|
27612f871a | |
|
|
1d80f3f00d | |
|
|
4cff6d9a33 | |
|
|
2084878935 | |
|
|
fe0605224e | |
|
|
52d25ef0ef | |
|
|
9843c53a6f | |
|
|
8cb9aeb29e | |
|
|
3316fce5f5 | |
|
|
b071019afd | |
|
|
76b494db73 | |
|
|
7d1101d910 | |
|
|
bdab0d8c6d | |
|
|
7d22a7d6ae | |
|
|
03efce4f24 | |
|
|
b299e9eaf1 | |
|
|
cffdc3d169 | |
|
|
3641d130d0 | |
|
|
e2aebce0a7 | |
|
|
67eb391a5a | |
|
|
2af4766874 | |
|
|
eb6547ee61 | |
|
|
a77ba7f5be | |
|
|
294695be48 | |
|
|
c85d8682b8 | |
|
|
239604ca91 | |
|
|
de956578f8 | |
|
|
c80d2ffb82 | |
|
|
f2a7485a56 | |
|
|
e434554864 | |
|
|
a1879c3d29 | |
|
|
3163e9300f | |
|
|
e8bbe27e42 | |
|
|
f3df96ef30 | |
|
|
8c92ed33f4 | |
|
|
1a2e327e22 | |
|
|
5110cefc47 | |
|
|
6af5789fc4 | |
|
|
bd084c0a5d | |
|
|
609e7749ee | |
|
|
f6bf9baf1e | |
|
|
e57db724d3 | |
|
|
142ded8db5 | |
|
|
b601b19d91 | |
|
|
6d42c45c12 | |
|
|
607fbcdde7 | |
|
|
159ec582f6 | |
|
|
e761381d0e | |
|
|
4fde2363fa | |
|
|
743bbd204b | |
|
|
0ffa58b592 | |
|
|
12aee0894d | |
|
|
7f6cac9e09 | |
|
|
fd0a6e09f4 | |
|
|
c5fad74d95 | |
|
|
680624db65 | |
|
|
c15be8c7b5 | |
|
|
69a085f8cf | |
|
|
91d0c39f11 | |
|
|
36df29a301 | |
|
|
05b2a55608 | |
|
|
db60303b75 | |
|
|
4c464fac2e | |
|
|
a5b759c756 | |
|
|
a66ed6aa1f | |
|
|
7a6a6fc515 | |
|
|
b44a71592d | |
|
|
3434635509 | |
|
|
e638421ae2 | |
|
|
53fcdcfdbc | |
|
|
4a88ab4c60 | |
|
|
d3fa2061f9 | |
|
|
1f1d3b1786 | |
|
|
e773ed819f | |
|
|
35968fafaa | |
|
|
061f2f8c91 | |
|
|
8f421f554d | |
|
|
8352b4f9e8 | |
|
|
c7a05a14b1 | |
|
|
35dd3895a6 | |
|
|
00e53e3af7 | |
|
|
a5887f7f54 | |
|
|
d5ceb4cd17 | |
|
|
f300f7eaca | |
|
|
50a852828a | |
|
|
811ffa1dce | |
|
|
a3015ce8c5 | |
|
|
3488d10d2b | |
|
|
696d7dd647 | |
|
|
97b837bc81 | |
|
|
33030a5235 | |
|
|
429087e811 | |
|
|
7f231a4a10 | |
|
|
eb185eb33a | |
|
|
ae4224754e | |
|
|
88462e9983 | |
|
|
a27bc6abe9 | |
|
|
b8b3cde805 | |
|
|
b9ed424125 | |
|
|
563a6c6f2f | |
|
|
e68c497936 | |
|
|
938b0e063f | |
|
|
78f4794926 | |
|
|
7d66d96525 | |
|
|
0e0664bf36 | |
|
|
c1c2336a82 | |
|
|
ef755075ec | |
|
|
0bd2c15c56 | |
|
|
791e81efe4 | |
|
|
cbf79d4682 | |
|
|
4a2fae35e6 | |
|
|
0b8146fa5f | |
|
|
f87bee3f20 | |
|
|
08e11220a5 | |
|
|
e980386c1f | |
|
|
1aed3ecc46 | |
|
|
818777d48a | |
|
|
d7f9213cf1 | |
|
|
fb29feebe0 | |
|
|
cc8bdb9cf1 | |
|
|
5b52484cf8 | |
|
|
e58757ada5 | |
|
|
4b19c716ff | |
|
|
100493ec11 | |
|
|
14fc2c5de3 | |
|
|
e09c9b57b9 | |
|
|
fa978b6b4c | |
|
|
fcc503c8d7 | |
|
|
c1616fbca3 | |
|
|
59e5545d70 | |
|
|
d286c15e34 | |
|
|
f97e72c23e | |
|
|
5a5e727811 | |
|
|
089948ae02 | |
|
|
4fa7aad302 | |
|
|
90d80ed622 | |
|
|
a5eb2faf5b | |
|
|
b949574af5 | |
|
|
d827dbd472 | |
|
|
91f9efa107 | |
|
|
8aa2ef71b3 | |
|
|
fa42e387a3 | |
|
|
dbb8e2b407 | |
|
|
d819ff2047 | |
|
|
b8710c17d8 | |
|
|
8ee41f4e1d | |
|
|
894fdc3924 | |
|
|
7325306a56 | |
|
|
5cf09bb3b1 | |
|
|
fea3503f7b | |
|
|
905590aff5 | |
|
|
7942816fad | |
|
|
fdedfcc0d8 | |
|
|
68486ea679 | |
|
|
6ecff85a3e | |
|
|
c5b45e0502 | |
|
|
7f9a9fd3a7 | |
|
|
257c391912 | |
|
|
bcf12a41db | |
|
|
015a15f2e0 | |
|
|
417636d93c | |
|
|
cb03009ac4 | |
|
|
34c410998f | |
|
|
e17add2d47 | |
|
|
f642c54848 | |
|
|
f047a3d360 | |
|
|
2772ccb0ab | |
|
|
1810fe74d6 | |
|
|
bd065347b3 | |
|
|
73b8aa3789 | |
|
|
2790f818c7 | |
|
|
010674506e | |
|
|
3c8569152f | |
|
|
3403b74e31 | |
|
|
00b3b4c76d | |
|
|
deeb947ba1 | |
|
|
a0efcf0456 | |
|
|
5a8a22a01c | |
|
|
aca88673d6 | |
|
|
3b96003ca1 | |
|
|
670cf1dcb3 | |
|
|
734ac597ea | |
|
|
06d81c581e | |
|
|
11df874b5d | |
|
|
5b058d2a8a | |
|
|
9659a920e9 | |
|
|
ca6b26c53a | |
|
|
a835d15d0a | |
|
|
ee1c22f932 | |
|
|
7b7171c233 | |
|
|
d482335ca2 | |
|
|
431e42209c | |
|
|
fc843205d5 | |
|
|
bf0f413a22 | |
|
|
b08e798e27 | |
|
|
10d3287b4d | |
|
|
6149b68a84 | |
|
|
8e88caecab | |
|
|
59434d03c9 | |
|
|
14badde79e | |
|
|
9751227b97 | |
|
|
7eb4a5ba5e | |
|
|
08a4ad245d | |
|
|
e59d71e316 | |
|
|
08b1656e02 | |
|
|
4b4984fce0 | |
|
|
9cb3bbcb07 | |
|
|
059446caa7 | |
|
|
db4aa26547 | |
|
|
16720b7b26 | |
|
|
ffb5488988 | |
|
|
be472cbbf7 | |
|
|
0b36cb947b | |
|
|
985dabd51c | |
|
|
c6a7652d9e | |
|
|
6254a598fc | |
|
|
27a8b249ae | |
|
|
602dc9203b | |
|
|
d746d5d4b2 | |
|
|
1e2544cc57 | |
|
|
bfd70f95dd | |
|
|
2965980a53 | |
|
|
802c1bb305 | |
|
|
4e3847455b | |
|
|
de023c538f | |
|
|
4cc87ef6fc | |
|
|
95ee8edef1 | |
|
|
feb9198470 | |
|
|
f41b8080e2 | |
|
|
238557ea78 | |
|
|
3d1fabb9bd | |
|
|
21c1c5ca11 | |
|
|
1b8d8598b1 | |
|
|
f6882b47aa | |
|
|
5f9bf8d8d3 | |
|
|
5056f08b88 | |
|
|
34e82bc618 | |
|
|
7fee31c645 | |
|
|
2a221eae28 | |
|
|
ea1e38c314 | |
|
|
58d6fccfdc | |
|
|
6ab2b6099c | |
|
|
0369816675 | |
|
|
30c60d19d4 | |
|
|
2b690b7f09 | |
|
|
377b4e6029 | |
|
|
7d69b3aed2 | |
|
|
0db9672c7b | |
|
|
70b12eaae2 | |
|
|
6705ed7cdc | |
|
|
4467b8403f | |
|
|
8dddff0e2b | |
|
|
76c8e8c298 | |
|
|
5b24a33768 | |
|
|
e2ea56f586 | |
|
|
f65eb173b6 | |
|
|
5acd1bcc47 | |
|
|
fc39a114c1 | |
|
|
ea5bfb52e0 | |
|
|
e17286e324 | |
|
|
bafd7741a2 | |
|
|
f4aee793a8 | |
|
|
e5e31005ac | |
|
|
5a5ae36b01 | |
|
|
b87e6029d6 | |
|
|
4415551681 | |
|
|
a1ee7fd5e7 | |
|
|
5f8a42789d | |
|
|
d58119db4b | |
|
|
9c70364080 | |
|
|
4e3c0c087b | |
|
|
5d29fe2f2a | |
|
|
cb1926911c | |
|
|
c18377d4c1 | |
|
|
d9012947de | |
|
|
55a0c6993d | |
|
|
9843232542 | |
|
|
37c6349f10 | |
|
|
1bb1f7cd50 | |
|
|
f03dc7be00 | |
|
|
788dffa9fa | |
|
|
b9b1d5a8c7 | |
|
|
068a7f48ed | |
|
|
b46b168842 | |
|
|
247c09376b | |
|
|
73043957d3 | |
|
|
5ea194bb22 | |
|
|
fc80149d82 | |
|
|
ce11dc76d9 | |
|
|
ceb31be88b | |
|
|
89b80ef310 | |
|
|
b546335bd1 | |
|
|
f0d1140bba | |
|
|
c4f3ebfef8 | |
|
|
dbc05223fd | |
|
|
ac86979872 | |
|
|
ff535a79f4 | |
|
|
e33004f319 | |
|
|
03c3ff6264 | |
|
|
e7bbeb7919 | |
|
|
728c04d633 | |
|
|
23609e81f5 | |
|
|
d60515dad6 | |
|
|
29ea6bf2b1 | |
|
|
d628ad5452 | |
|
|
f7f6d6282c | |
|
|
b23cb7fdf6 | |
|
|
3f4f8a4051 | |
|
|
208a3baab6 | |
|
|
7bdea3d171 | |
|
|
6b50224fd7 | |
|
|
4f52d8c4ef | |
|
|
7e81847d01 | |
|
|
3ca573869c | |
|
|
ebea2a1467 | |
|
|
42c22079b4 | |
|
|
2913c82ac8 | |
|
|
c377430a43 | |
|
|
5f68d284e1 | |
|
|
8b10e111f0 | |
|
|
cdaaa15444 | |
|
|
8436adb45f | |
|
|
bcb34f5191 | |
|
|
2a9d53c2c4 | |
|
|
008d82eee0 | |
|
|
f2f7baabea | |
|
|
d936085154 | |
|
|
8358ccc2f7 | |
|
|
8688ae3ecb | |
|
|
0809f9eda8 | |
|
|
dd9fe6d218 | |
|
|
87734a614f | |
|
|
94594542f7 | |
|
|
d3b65df599 | |
|
|
7e32a84d2c | |
|
|
d796a76400 | |
|
|
835d9c1b90 | |
|
|
6fefefa9ae | |
|
|
08eea15172 | |
|
|
a589b79e6c | |
|
|
01ab0a0373 | |
|
|
57f4fb4383 | |
|
|
6a8d641a07 | |
|
|
597b769311 | |
|
|
0d0cd486c9 | |
|
|
a584ef9caf | |
|
|
1701f49e45 | |
|
|
b1463a23a0 | |
|
|
cbba946ff7 | |
|
|
3d69968290 | |
|
|
92684b1183 | |
|
|
932a5d80b4 | |
|
|
64adcf122e | |
|
|
8494e1075d | |
|
|
6027ba7146 | |
|
|
b029d58aa3 | |
|
|
73b988bae5 | |
|
|
5fe7629fe8 | |
|
|
0bb8cb9553 | |
|
|
ad219e3e07 | |
|
|
c95bed6f58 | |
|
|
d83e20c99c | |
|
|
b9e9fcfd80 | |
|
|
90f0e86597 | |
|
|
37845e7dea | |
|
|
84a8befd4b | |
|
|
c8537541c3 | |
|
|
176081fcc7 | |
|
|
cc629102e5 | |
|
|
9c69fc237d | |
|
|
f4fba7db60 | |
|
|
09dda2c211 | |
|
|
300d631caf | |
|
|
fb564c79e5 | |
|
|
235cd0388c | |
|
|
614ced34e4 | |
|
|
ca3997f822 | |
|
|
d1c443f934 | |
|
|
d239a43fa5 | |
|
|
09a6d60620 | |
|
|
11f527b207 | |
|
|
4120c4e9c8 | |
|
|
35fc3cbf23 | |
|
|
0a7b2e225f | |
|
|
23dbb5ab2c | |
|
|
05bc534d87 | |
|
|
af12f97152 | |
|
|
1605a3d9a5 | |
|
|
52bede553d | |
|
|
202f86eba7 | |
|
|
a823a70326 | |
|
|
0aacd39882 | |
|
|
b705253664 | |
|
|
7a020f52aa | |
|
|
c7104902e3 | |
|
|
42a6d37c43 | |
|
|
5287df2fe7 | |
|
|
8a48d41657 | |
|
|
ddb815aab6 | |
|
|
e70d1e473e | |
|
|
7fbe737e92 | |
|
|
c9bf99d07f | |
|
|
22bd52fd65 | |
|
|
dfca3494ad | |
|
|
8b6e67f1d6 | |
|
|
12dd913be4 | |
|
|
dbcb33f608 | |
|
|
ae6ae1cfe2 | |
|
|
ac63e52851 | |
|
|
d0c5ea05b6 | |
|
|
4624e421b2 | |
|
|
0c3f199116 | |
|
|
34043b50e1 | |
|
|
9527177a91 | |
|
|
3cf3f0cc36 | |
|
|
d8fcf0a31e | |
|
|
55c3851872 | |
|
|
171d9319c5 | |
|
|
174e4a46db | |
|
|
0bc2060b31 | |
|
|
a784a0295e | |
|
|
2f26c9bc71 | |
|
|
de102702eb | |
|
|
1258a74064 | |
|
|
1a1e012387 | |
|
|
32317fdbf4 | |
|
|
5ffd7cb14c | |
|
|
9e1bc19eda | |
|
|
b4828485ad | |
|
|
91d9dccb90 | |
|
|
9900f37782 | |
|
|
078a0f2dfe | |
|
|
04545690c4 | |
|
|
5705993cec | |
|
|
347bda5dd5 | |
|
|
5f3bddec08 | |
|
|
eab6259b13 | |
|
|
6d61beb592 | |
|
|
f4a33d36bb | |
|
|
532ede302b | |
|
|
d589e0555a | |
|
|
862d218756 | |
|
|
34f09aadb2 | |
|
|
30dfcbb035 | |
|
|
9232c8fec1 | |
|
|
939dc51a6f | |
|
|
f93b8d2dfb | |
|
|
cb3304eb7d | |
|
|
6d878babb4 | |
|
|
90682e06eb | |
|
|
7d4a36d9bc | |
|
|
fe4057d8aa | |
|
|
bd3ffadabf | |
|
|
b7376d5868 | |
|
|
387f5a2097 | |
|
|
9de920ef2b | |
|
|
639c852534 | |
|
|
3c8cfa1282 | |
|
|
a67936d835 | |
|
|
9bdf030fe0 | |
|
|
7a2b9943b5 | |
|
|
c0955fedff | |
|
|
21bb060839 | |
|
|
b0f7bff2de | |
|
|
643e6a2463 | |
|
|
ec395be524 | |
|
|
7dcec80080 | |
|
|
bedaca5b70 |
|
|
@ -3,12 +3,92 @@
|
|||
*.o
|
||||
*.pyc
|
||||
*~
|
||||
*.log
|
||||
*.la
|
||||
*.so
|
||||
*.iml
|
||||
META-INF/
|
||||
.DS_Store
|
||||
|
||||
# Binary Files #
|
||||
*.jar
|
||||
!dbMaker-*.jar
|
||||
|
||||
# ignore all xdb except the one in ./data/
|
||||
*.xdb
|
||||
!/data/*.xdb
|
||||
|
||||
# Package Files #
|
||||
.settings/
|
||||
.classpath
|
||||
.project
|
||||
|
||||
# vim swp file #
|
||||
*.swp
|
||||
.idea
|
||||
.vscode
|
||||
|
||||
# binding
|
||||
/binding/java/classes/
|
||||
/binding/java/doc/
|
||||
/binding/java/target/
|
||||
/binding/java/*.jar
|
||||
|
||||
# python
|
||||
/**/__pycache__
|
||||
|
||||
# clang
|
||||
/binding/c/xdb_searcher
|
||||
/binding/c/test_util
|
||||
/binding/c/cmake-build-debug
|
||||
|
||||
# lua/luc_c
|
||||
/binding/lua_c/cmake-build-debug
|
||||
|
||||
# golang
|
||||
/binding/golang/searcher
|
||||
/binding/golang/xdb_searcher
|
||||
/binding/golang/golang
|
||||
|
||||
# rust
|
||||
Cargo.lock
|
||||
target
|
||||
|
||||
|
||||
# VS ignore cases
|
||||
/**/*.sln
|
||||
/binding/c#/**/.vs/
|
||||
/binding/c#/**/packages
|
||||
/binding/c#/**/bin
|
||||
/binding/c#/**/obj
|
||||
|
||||
# Nodejs
|
||||
/binding/nodejs/tests/unitTests/__snapshots__
|
||||
/binding/nodejs/coverage
|
||||
/binding/nodejs/node_modules
|
||||
/binding/nodejs/.nyc_output
|
||||
/binging/nodejs/package-lock.json
|
||||
|
||||
# Javascript
|
||||
/binding/javascript/tests/unitTests/__snapshots__
|
||||
/binding/javascript/coverage
|
||||
/binding/javascript/node_modules
|
||||
/binding/javascript/.nyc_output
|
||||
/binding/javascript/package-lock.json
|
||||
|
||||
# maker
|
||||
## golang
|
||||
/maker/golang/dbmaker
|
||||
/maker/golang/xdb_maker
|
||||
/maker/golang/golang
|
||||
|
||||
#erlang
|
||||
/binding/erlang/_build
|
||||
/binding/erlang/doc
|
||||
|
||||
#vscode
|
||||
.vscode
|
||||
build
|
||||
|
||||
# git worktrees
|
||||
.worktrees/
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
### 1.2
|
||||
|
||||
1. 新增分列式升级算法,大大的增加了数据的准确率,基本避免之前各大网友反馈的些许ip定位错误!
|
||||
2. 数据升级是2016/06/30版!
|
||||
3. 优化数据文件生成算法,ip2region.db文件由原来的3.5M降为1.5M,42亿个IP地址,皆大欢喜啊!
|
||||
4. C/PHP/JAVA客户端增加纯内存搜索模式(python, php扩展,nodejs会在后续版本加上!)
|
||||
说明:因为数据文件只有1.5M,对于PHP,java这类IO优化类语言提升速度不大,C有一个数量级的提升!
|
||||
13
LICENSE.md
13
LICENSE.md
|
|
@ -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
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
|
@ -201,9 +209,9 @@
|
|||
limitations under the License.
|
||||
|
||||
==========================================================================
|
||||
The following license applies to the JQuery JavaScript library
|
||||
The following license applies to the ip2region library
|
||||
--------------------------------------------------------------------------
|
||||
Copyright (c) 2010 John Resig, http://jquery.com/
|
||||
Copyright (c) 2015 Lionsoul<chenxin619315@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
|
|
@ -223,4 +231,3 @@ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
|
|
|||
191
README.md
191
README.md
|
|
@ -1,66 +1,125 @@
|
|||
ip2region - 最自由的ip地址查询库,ip到地区的映射库,提供Binary,B树和纯内存三种查询算法,妈妈再也不用担心我的ip地址定位。
|
||||
|
||||
**1. 99.9%准确率,定时更新:**
|
||||
|
||||
数据聚合了一些知名ip到地名查询提供商的数据,这些是他们官方的的准确率,经测试着实比纯真啥的准确多了。<br />
|
||||
每次聚合一下数据需要1-2天,会不定时更新。
|
||||
|
||||
**2. 标准化的数据格式:**
|
||||
|
||||
每条ip数据段都固定了格式:_城市Id|国家|区域|省份|城市|ISP_
|
||||
|
||||
只有中国的数据精确到了城市,其他国家只能定位到国家,后前的选项全部是0,已经包含了全部你能查到的大大小小的国家。
|
||||
(请忽略前面的城市Id,个人项目需求)
|
||||
|
||||
**3. 体积小:**
|
||||
|
||||
生成的数据库文件ip2region.db只有1.5M(1.2版本前是3.5M)
|
||||
|
||||
**4. 多查询客户端的支持,0.0x毫秒级别的查询**
|
||||
|
||||
已经集成的客户端有:java, php, c, python,nodejs,php扩展(支持linux, php5, php7版本已支持)。
|
||||
|
||||
提供了两种查询算法,响应时间如下:
|
||||
客户端/binary算法/b-tree算法/Memory算法:
|
||||
java/0.x毫秒/0.x毫秒/0.1x毫秒 (使用RandomAccessFile)
|
||||
php/0.x毫秒/0.1x毫秒/0.1x毫秒
|
||||
c/0.0x毫秒/0.0x毫秒/0.00x毫秒(b-tree算法基本稳定在0.02x毫秒级别)
|
||||
python/0.x毫秒/0.1x毫秒/未知
|
||||
|
||||
任何客户端b-tree都比binary算法快,当然Memory算法固然是最快的!
|
||||
|
||||
**5. 测试程序:**
|
||||
java:
|
||||
|
||||
cd binding/java
|
||||
ant all
|
||||
java -jar ip2region-{version}.jar ./data/ip2region.db
|
||||
|
||||
php:
|
||||
|
||||
php binding/php/testSearcher ./data/ip2region.db
|
||||
|
||||
c:
|
||||
|
||||
cd binding/c/
|
||||
gcc -g -O2 testSearcher.c ip2region.c
|
||||
./a.out ../../data/ip2region.db
|
||||
|
||||
python:
|
||||
|
||||
python binding/python/testSearcher ./data/ip2region.db
|
||||
|
||||
均会看到如下界面:
|
||||
|
||||
initializing B-tree ...
|
||||
+----------------------------------+
|
||||
| ip2region test script |
|
||||
| Author: chenxin619315@gmail.com |
|
||||
| Type 'quit' to exit program |
|
||||
+----------------------------------+
|
||||
p2region>> 101.105.35.57
|
||||
2163|中国|华南|广东省|深圳市|鹏博士 in 0.02295 millseconds
|
||||
|
||||
输入ip地址开始测试,第一次会稍微有点慢,在运行命令后面接入binary,memory来尝试其他算法,建议使用b-tree算法,速度和并发需求的可以使用memory算法。
|
||||
|
||||
具体集成请参考不同客户端的测试源码。
|
||||
: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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
all: xdb_searcher test_util
|
||||
|
||||
xdb_searcher: xdb_api.h xdb_util.c xdb_searcher.c main.c
|
||||
gcc -std=c99 -Wall -O2 -I./ xdb_util.c xdb_searcher.c main.c -o xdb_searcher
|
||||
|
||||
test_util: xdb_api.h xdb_util.c test_util.c
|
||||
gcc -std=c99 -Wall -O2 -I./ xdb_util.c test_util.c -o test_util
|
||||
|
||||
xdb_searcher.o: xdb_searcher.c
|
||||
gcc -std=c99 -Wall -c xdb_searcher.c
|
||||
|
||||
xdb_util.o: xdb_util.c
|
||||
gcc -std=c99 -Wall -c xdb_util.c
|
||||
|
||||
xdb_searcher_lib: xdb_util.o xdb_searcher.o
|
||||
mkdir -p build/lib
|
||||
mkdir -p build/include
|
||||
ar -rc build/lib/libxdb.a `find . -name "*.o"`
|
||||
cp xdb_api.h build/include
|
||||
|
||||
clean:
|
||||
find ./ -name \*.o | xargs rm -f
|
||||
find ./ -name test_util | xargs rm -f
|
||||
find ./ -name xdb_searcher | xargs rm -f
|
||||
rm -rf build
|
||||
|
||||
.PHONY: all clean xdb_searcher test_util
|
||||
|
|
@ -0,0 +1,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(®ion, 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, ®ion);
|
||||
cost_time = (int) (xdb_now() - s_time);
|
||||
if (err != 0) {
|
||||
printf("failed search(%s) with errno=%d\n", ip_string, err);
|
||||
} else {
|
||||
printf("{region: %s, took: %d μs}", region.value, cost_time);
|
||||
}
|
||||
|
||||
// Clean up memory resources for region info; must be called after every search
|
||||
xdb_region_buffer_free(®ion);
|
||||
|
||||
// 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(®ion, 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, ®ion);
|
||||
cost_time = (int) (xdb_now() - s_time);
|
||||
if (err != 0) {
|
||||
printf("failed search(%s) with errno=%d\n", ip_string, err);
|
||||
} else {
|
||||
printf("{region: %s, took: %d μs}", region.value, cost_time);
|
||||
}
|
||||
|
||||
// Clean up memory resources for region info; must be called after every search
|
||||
xdb_region_buffer_free(®ion);
|
||||
|
||||
|
||||
// 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(®ion, 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, ®ion);
|
||||
cost_time = (int) (xdb_now() - s_time);
|
||||
if (err != 0) {
|
||||
printf("failed search(%s) with errno=%d\n", ip_string, err);
|
||||
} else {
|
||||
printf("{region: %s, took: %d μs}", region.value, cost_time);
|
||||
}
|
||||
|
||||
// Clean up memory resources for region info; must be called after every search
|
||||
xdb_region_buffer_free(®ion);
|
||||
|
||||
|
||||
// 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(®ion, 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(®ion, 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(®ion);
|
||||
```
|
||||
|
||||
# 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.
|
||||
|
|
@ -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(®ion, region_buffer, sizeof(region_buffer));
|
||||
if (err != 0) {
|
||||
printf("failed to init the region buffer with errcode=%d\n", err);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 在服务启动的时候初始化 winsock,不需要重复调用,只需要在 windows 系统下调用
|
||||
err = xdb_init_winsock();
|
||||
if (err != 0) {
|
||||
printf("failed to init the winsock with errno=%d\n", err);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 1、从 db_path 初始化 xdb 查询对象.
|
||||
// @Note: 使用顶部描述的 db_path 和 version 来创建 searcher
|
||||
err = xdb_new_with_file_only(version, &searcher, db_path);
|
||||
if (err != 0) {
|
||||
printf("failed to create xdb searcher from `%s` with errno=%d\n", db_path, err);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 2、调用 search API 查询,IPv4 和 IPv6 都支持.
|
||||
const char *ip_string = "1.2.3.4";
|
||||
// ip_string = "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, ®ion);
|
||||
cost_time = (int) (xdb_now() - s_time);
|
||||
if (err != 0) {
|
||||
printf("failed search(%s) with errno=%d\n", ip_string, err);
|
||||
} else {
|
||||
printf("{region: %s, took: %d μs}", region.value, cost_time);
|
||||
}
|
||||
|
||||
// 清理 region 信息的内存资源,每次 search 之后都得调用
|
||||
xdb_region_buffer_free(®ion);
|
||||
|
||||
// 备注:并发使用,每一个线程需要单独定义并且初始化一个 searcher 查询对象。
|
||||
|
||||
// 3、关闭 xdb 查询器
|
||||
xdb_close(&searcher);
|
||||
xdb_clean_winsock(); // windows 下调用
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
### 缓存 `VectorIndex` 索引
|
||||
|
||||
我们可以提前从 xdb 文件中加载出来 VectorIndex 数据,然后全局缓存,每次创建 Searcher 对象的时候使用全局的 VectorIndex 缓存可以减少一次固定的 IO 操作,从而加速查询,减少 IO 压力。
|
||||
```c
|
||||
#include <stdio.h>
|
||||
#include "xdb_api.h"
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
xdb_vector_index_t *v_index;
|
||||
xdb_searcher_t searcher;
|
||||
xdb_region_buffer_t region;
|
||||
|
||||
// 使用 NULL 初始化 region_buffer,让其自动管理内存的分配
|
||||
int err = xdb_region_buffer_init(®ion, NULL, 0);
|
||||
if (err != 0) {
|
||||
printf("failed to init the region buffer with errcode=%d\n", err);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 在服务启动的时候初始化 winsock,不需要重复调用,只需要在 windows 系统下调用
|
||||
err = xdb_init_winsock();
|
||||
if (err != 0) {
|
||||
printf("failed to init the winsock with errno=%d\n", err);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 1、从顶部描述的 db_path 加载 VectorIndex 索引。
|
||||
// 得到 v_index 做成全局缓存,便于后续反复使用。
|
||||
// 注意:v_index 不需要每次都加载,建议在服务启动的时候加载一次,然后做成全局资源。
|
||||
v_index = xdb_load_vector_index_from_file(db_path);
|
||||
if (v_index == NULL) {
|
||||
printf("failed to load vector index from `%s`\n", db_path);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 2、使用全局的 VectorIndex 变量创建带 VectorIndex 缓存的 xdb 查询对象.
|
||||
// @Note: 使用顶部描述的 db_path 和 version 来创建 searcher
|
||||
err = xdb_new_with_vector_index(version, &searcher, db_path, v_index);
|
||||
if (err != 0) {
|
||||
printf("failed to create vector index cached searcher with errcode=%d\n", err);
|
||||
return 2;
|
||||
}
|
||||
|
||||
|
||||
// 3、调用 search API 查询,IPv4 和 IPv6 都支持
|
||||
const char *ip_string = "1.2.3.4";
|
||||
// ip_string = "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, ®ion);
|
||||
cost_time = (int) (xdb_now() - s_time);
|
||||
if (err != 0) {
|
||||
printf("failed search(%s) with errno=%d\n", ip_string, err);
|
||||
} else {
|
||||
printf("{region: %s, took: %d μs}", region.value, cost_time);
|
||||
}
|
||||
|
||||
// 清理 region 信息的内存资源,每次 search 之后都得调用
|
||||
xdb_region_buffer_free(®ion);
|
||||
|
||||
|
||||
// 备注:并发使用,每一个线程需要单独定义并且初始化一个 searcher 查询对象。
|
||||
|
||||
// 4、关闭 xdb 查询器,如果是要关闭服务,也需要释放 v_index 的内存。
|
||||
xdb_close(&searcher);
|
||||
xdb_close_vector_index(v_index);
|
||||
xdb_clean_winsock();
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
### 缓存整个 `xdb` 文件
|
||||
|
||||
我们也可以预先加载整个 xdb 文件到内存,然后基于这个数据创建查询对象来实现完全基于内存的查询,类似之前的 memory search。
|
||||
```c
|
||||
#include <stdio.h>
|
||||
#include "xdb_api.h"
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
xdb_content_t *c_buffer;
|
||||
xdb_searcher_t searcher;
|
||||
xdb_region_buffer_t region;
|
||||
|
||||
// 使用 NULL 初始化 region_buffer,让其自动管理内存的分配
|
||||
int err = xdb_region_buffer_init(®ion, NULL, 0);
|
||||
if (err != 0) {
|
||||
printf("failed to init the region buffer with errcode=%d\n", err);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// 在服务启动的时候初始化 winsock,不需要重复调用,只需要在 windows 系统下调用
|
||||
err = xdb_init_winsock();
|
||||
if (err != 0) {
|
||||
printf("failed to init the winsock with errno=%d\n", err);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 1、从 顶部描述的 db_path 加载整个 xdb 的数据。
|
||||
c_buffer = xdb_load_content_from_file(db_path);
|
||||
if (v_index == NULL) {
|
||||
printf("failed to load xdb content from `%s`\n", db_path);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 2、使用全局的 c_buffer 变量创建一个完全基于内存的 xdb 查询对象.
|
||||
// @Note: 使用顶部描述的 version 来创建 searcher.
|
||||
err = xdb_new_with_buffer(version, &searcher, c_buffer);
|
||||
if (err != 0) {
|
||||
printf("failed to create content cached searcher with errcode=%d\n", err);
|
||||
return 2;
|
||||
}
|
||||
|
||||
// 3、调用 search API 查询,IPv4 和 IPv6 都支持
|
||||
const char *ip_string = "1.2.3.4";
|
||||
// ip_string = "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, ®ion);
|
||||
cost_time = (int) (xdb_now() - s_time);
|
||||
if (err != 0) {
|
||||
printf("failed search(%s) with errno=%d\n", ip_string, err);
|
||||
} else {
|
||||
printf("{region: %s, took: %d μs}", region.value, cost_time);
|
||||
}
|
||||
|
||||
// 清理 region 信息的内存资源,每次 search 之后都得调用
|
||||
xdb_region_buffer_free(®ion);
|
||||
|
||||
|
||||
// 备注:并发使用,使用这种方式创建的 xdb 查询对象可以安全用于并发。
|
||||
// 建议在服务启动的时候创建好,然后一直安全并发使用,直到服务关闭。
|
||||
|
||||
// 4、关闭 xdb 查询器,关闭服务的时候需要释放 c_buffer 的内存。
|
||||
xdb_close(&searcher);
|
||||
xdb_close_content(c_buffer);
|
||||
xdb_clean_winsock();
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
### 关于定位信息的存储
|
||||
在旧版本的实现中,search相关的函数都是依靠指定一个 `region_buffer` 内存来用于存储地域信息,这种方式还是有很大的局限性。
|
||||
新的实现提供了一个 `xdb_region_buffer_t` 对象来管理这些内存的分配,你依然可以指定一个固定的 `region_buffer` 来创建 region 的内存管理,这个情况适合当你的地域信息的最大长度是可知的,这种方式可以减少运行过程中内存的碎片堆积。如果地域信息的长度不确定或者你的程序不适合提前分配一块内存来管理,你可以通过指定 NULL 的方式来初始化 `xdb_region_buffer_t`,这样对象会自动管理内存的分配,也适合任意长度的地域信息的存储,不过这种方式在长期的运行过程中肯定会增加内存碎片的堆积。
|
||||
```c
|
||||
// 1, 通过指定一块内存来创建 region_buffer
|
||||
char buffer[512];
|
||||
xdb_region_buffer_t region;
|
||||
int err = xdb_region_buffer_init(®ion, buffer, sizeof(buffer));
|
||||
if (err != 0) {
|
||||
// 初始化失败
|
||||
printf("failed to init region buffer width errcode=%d", err);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2,通过指定 NULL 来创建 region_buffer,让其自动按需分配内存
|
||||
xdb_region_buffer_t region;
|
||||
int err = xdb_region_buffer_init(®ion, NULL, 0);
|
||||
if (err != 0) {
|
||||
// 初始化失败
|
||||
printf("failed to init region buffer width errcode=%d", err);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// 备注:在每次调用 search 完成 IP 定位信息的查询后,你需要手动调用函数来释放内存 .
|
||||
// search 函数使用未经清理的 region 信息会报错。
|
||||
xdb_region_buffer_free(®ion);
|
||||
```
|
||||
|
||||
|
||||
# 测试程序编译
|
||||
|
||||
通过如下方式编译得到 xdb_searcher 可执行程序:
|
||||
```bash
|
||||
# cd 到 c binding 根目录
|
||||
➜ c git:(master) ✗ make
|
||||
gcc -std=c99 -Wall -O2 -I./ xdb_util.c xdb_searcher.c main.c -o xdb_searcher
|
||||
gcc -std=c99 -Wall -O2 -I./ xdb_util.c test_util.c -o test_util
|
||||
```
|
||||
|
||||
|
||||
# 查询测试
|
||||
|
||||
通过 `xdb_searcher search` 命令来测试对 xdb 的查询:
|
||||
```bash
|
||||
➜ c git:(fr_c_ipv6) ✗ ./xdb_searcher search
|
||||
./xdb_searcher search [command options]
|
||||
options:
|
||||
--db string ip2region binary xdb file path
|
||||
--cache-policy string cache policy: file/vectorIndex/content
|
||||
```
|
||||
|
||||
例如:使用默认的 data/ip2region_v4.xdb 进行 IPv4 查询测试:
|
||||
```bash
|
||||
➜ c git:(fr_c_ipv6) ✗ ./xdb_searcher search --db=../../data/ip2region_v4.xdb
|
||||
ip2region xdb searcher test program
|
||||
source xdb: ../../data/ip2region_v4.xdb (IPv4, vectorIndex)
|
||||
type 'quit' to exit
|
||||
ip2region>> 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 文件相同的源文件。
|
||||
|
|
@ -1,430 +0,0 @@
|
|||
/**
|
||||
* default ip2region implementation
|
||||
*
|
||||
* @see #ip2region.h
|
||||
* @author chenxin<chenxin619315@gmail.com>
|
||||
* @date 2015-10-30
|
||||
*/
|
||||
|
||||
#include "ip2region.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/**
|
||||
* create a new ip2region object
|
||||
*
|
||||
* @param dbFile path
|
||||
*/
|
||||
IP2R_API uint_t ip2region_create(ip2region_t ip2rObj, char *dbFile)
|
||||
{
|
||||
memset(ip2rObj, 0x00, sizeof(ip2region_entry));
|
||||
ip2rObj->headerLen = 0;
|
||||
ip2rObj->HeaderSip = (uint_t *) IP2R_MALLOC(TOTAL_HEADER_LENGTH);
|
||||
if ( ip2rObj->HeaderSip == NULL ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
ip2rObj->HeaderPtr = (uint_t *) IP2R_MALLOC(TOTAL_HEADER_LENGTH);
|
||||
if ( ip2rObj->HeaderPtr == NULL ) {
|
||||
IP2R_FREE(ip2rObj->HeaderSip);
|
||||
return 0;
|
||||
}
|
||||
|
||||
//open the db file
|
||||
ip2rObj->dbHandler = fopen(dbFile, "rb");
|
||||
if ( ip2rObj->dbHandler == NULL ) {
|
||||
IP2R_FREE(ip2rObj->HeaderSip);
|
||||
IP2R_FREE(ip2rObj->HeaderPtr);
|
||||
//fprintf(stderr, "Fail to open the db file %s\n", ip2rObj>dbFile);
|
||||
//exit(-1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
ip2rObj->firstIndexPtr = 0;
|
||||
ip2rObj->lastIndexPtr = 0;
|
||||
ip2rObj->totalBlocks = 0;
|
||||
ip2rObj->dbBinStr = NULL;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* destroy the specifield ip2region object
|
||||
*
|
||||
* @param ip2region_t
|
||||
*/
|
||||
IP2R_API uint_t ip2region_destroy(ip2region_t ip2rObj)
|
||||
{
|
||||
IP2R_FREE(ip2rObj->HeaderSip);
|
||||
ip2rObj->HeaderSip = NULL;
|
||||
IP2R_FREE(ip2rObj->HeaderPtr);
|
||||
ip2rObj->HeaderPtr = NULL;
|
||||
|
||||
//close the db file resource
|
||||
if ( ip2rObj->dbHandler != NULL ) {
|
||||
fclose(ip2rObj->dbHandler);
|
||||
ip2rObj->dbHandler = NULL;
|
||||
}
|
||||
|
||||
//free the db binary string
|
||||
if ( ip2rObj->dbBinStr != NULL ) {
|
||||
IP2R_FREE(ip2rObj->dbBinStr);
|
||||
ip2rObj->dbBinStr = NULL;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* get the region associated with the specified ip address with the memory binary search algorithm
|
||||
*
|
||||
* @param ip2rObj
|
||||
* @param ip
|
||||
* @param datablock
|
||||
*/
|
||||
IP2R_API uint_t ip2region_memory_search(ip2region_t ip2rObj, uint_t ip, datablock_t datablock)
|
||||
{
|
||||
int l, h, m, p;
|
||||
uint_t sip, eip, dptr;
|
||||
int dataLen, dataptr;
|
||||
long filesize;
|
||||
char *buffer;
|
||||
|
||||
if ( ip2rObj->dbBinStr == NULL ) {
|
||||
//get the size of the file
|
||||
fseek(ip2rObj->dbHandler, 0, SEEK_END);
|
||||
filesize = ftell(ip2rObj->dbHandler);
|
||||
fseek(ip2rObj->dbHandler, 0, SEEK_SET);
|
||||
|
||||
//alloc the buffer size
|
||||
ip2rObj->dbBinStr = IP2R_MALLOC(filesize);
|
||||
if ( ip2rObj->dbBinStr == NULL ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
//now read the whole file
|
||||
if ( fread(ip2rObj->dbBinStr, filesize, 1, ip2rObj->dbHandler) != 1 ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
buffer = ip2rObj->dbBinStr;
|
||||
ip2rObj->firstIndexPtr = getUnsignedInt(buffer, 0);
|
||||
ip2rObj->lastIndexPtr = getUnsignedInt(buffer, 4);
|
||||
ip2rObj->totalBlocks = (ip2rObj->lastIndexPtr-ip2rObj->firstIndexPtr)/INDEX_BLOCK_LENGTH + 1;
|
||||
}
|
||||
|
||||
l = 0; h = ip2rObj->totalBlocks; dptr = 0;
|
||||
while ( l <= h ) {
|
||||
m = (l + h) >> 1;
|
||||
p = ip2rObj->firstIndexPtr + m * INDEX_BLOCK_LENGTH;
|
||||
|
||||
buffer = ip2rObj->dbBinStr + p;
|
||||
sip = getUnsignedInt(buffer, 0);
|
||||
if ( ip < sip ) {
|
||||
h = m - 1;
|
||||
} else {
|
||||
eip = getUnsignedInt(buffer, 4);
|
||||
if ( ip > eip ) {
|
||||
l = m + 1;
|
||||
} else {
|
||||
dptr = getUnsignedInt(buffer, 8);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( dptr == 0 ) return 0;
|
||||
|
||||
//get the data
|
||||
dataLen = ((dptr >> 24) & 0xFF);
|
||||
dataptr = (dptr & 0x00FFFFFF);
|
||||
buffer = ip2rObj->dbBinStr + dataptr;
|
||||
|
||||
//fill the data to the datablock
|
||||
datablock->city_id = getUnsignedInt(buffer, 0);
|
||||
dataLen -= 4; //reduce the length of the city_id
|
||||
memcpy(datablock->region, buffer + 4, dataLen);
|
||||
datablock->region[dataLen] = '\0';
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
IP2R_API uint_t ip2region_memory_search_string(ip2region_t ip2rObj, char *ip, datablock_t datablock)
|
||||
{
|
||||
return ip2region_memory_search(ip2rObj, ip2long(ip), datablock);
|
||||
}
|
||||
|
||||
/**
|
||||
* get the region associated with the specifield ip address with binary search algorithm
|
||||
*
|
||||
* @param ip2rObj
|
||||
* @param ip
|
||||
* @param datablock
|
||||
* @return uint_t
|
||||
*/
|
||||
IP2R_API uint_t ip2region_binary_search(ip2region_t ip2rObj, uint_t ip, datablock_t datablock)
|
||||
{
|
||||
int l, h, m, p;
|
||||
uint_t sip, eip, dptr;
|
||||
char buffer[256];
|
||||
int dataLen, dataptr;
|
||||
|
||||
if ( ip2rObj->totalBlocks == 0 ) {
|
||||
fseek(ip2rObj->dbHandler, 0, 0);
|
||||
if ( fread(buffer, 8, 1, ip2rObj->dbHandler) != 1 ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
ip2rObj->firstIndexPtr = getUnsignedInt(buffer, 0);
|
||||
ip2rObj->lastIndexPtr = getUnsignedInt(buffer, 4);
|
||||
ip2rObj->totalBlocks = (ip2rObj->lastIndexPtr-ip2rObj->firstIndexPtr)/INDEX_BLOCK_LENGTH + 1;
|
||||
}
|
||||
|
||||
//binary search the index blocks to define the data block
|
||||
l = 0; h = ip2rObj->totalBlocks; dptr = 0;
|
||||
while ( l <= h ) {
|
||||
m = (l + h) >> 1;
|
||||
p = ip2rObj->firstIndexPtr + m * INDEX_BLOCK_LENGTH;
|
||||
|
||||
fseek(ip2rObj->dbHandler, p, 0);
|
||||
if ( fread(buffer, INDEX_BLOCK_LENGTH, 1, ip2rObj->dbHandler) != 1 ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
sip = getUnsignedInt(buffer, 0);
|
||||
if ( ip < sip ) {
|
||||
h = m - 1;
|
||||
} else {
|
||||
eip = getUnsignedInt(buffer, 4);
|
||||
if ( ip > eip ) {
|
||||
l = m + 1;
|
||||
} else {
|
||||
dptr = getUnsignedInt(buffer, 8);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( dptr == 0 ) return 0;
|
||||
|
||||
//get the data
|
||||
dataLen = ((dptr >> 24) & 0xFF);
|
||||
dataptr = (dptr & 0x00FFFFFF);
|
||||
|
||||
//memset(data, 0x00, sizeof(data));
|
||||
fseek(ip2rObj->dbHandler, dataptr, 0);
|
||||
if ( fread(buffer, dataLen, 1, ip2rObj->dbHandler) != 1 ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
//fill the data to the datablock
|
||||
datablock->city_id = getUnsignedInt(buffer, 0);
|
||||
dataLen -= 4; //reduce the length of the city_id
|
||||
memcpy(datablock->region, buffer + 4, dataLen);
|
||||
datablock->region[dataLen] = '\0';
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
IP2R_API uint_t ip2region_binary_search_string(ip2region_t ip2rObj, char *ip, datablock_t datablock)
|
||||
{
|
||||
return ip2region_binary_search(ip2rObj, ip2long(ip), datablock);
|
||||
}
|
||||
|
||||
/**
|
||||
* get the region associated with the specifield ip address with b-tree algorithm
|
||||
*
|
||||
* @param ip2rObj
|
||||
* @param ip
|
||||
* @param datablock
|
||||
* @return uint_t
|
||||
*/
|
||||
IP2R_API uint_t ip2region_btree_search(ip2region_t ip2rObj, uint_t ip, datablock_t datablock)
|
||||
{
|
||||
int i, idx;
|
||||
int l, m, h, p, sptr, eptr, indexBlockLen, dataLen, dataptr;
|
||||
uint_t sip, eip, idxptr, dptr;
|
||||
char buffer[TOTAL_HEADER_LENGTH];
|
||||
|
||||
if ( ip2rObj->headerLen == 0 ) {
|
||||
idx = 0;
|
||||
fseek(ip2rObj->dbHandler, 8, 0); //pass the super block
|
||||
if ( fread(buffer, TOTAL_HEADER_LENGTH, 1, ip2rObj->dbHandler) != 1 ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
for ( i = 0; i < TOTAL_HEADER_LENGTH; i += 8 ) {
|
||||
sip = getUnsignedInt(buffer, i);
|
||||
idxptr = getUnsignedInt(buffer, i + 4);
|
||||
if ( idxptr == 0 ) break;
|
||||
|
||||
ip2rObj->HeaderSip[idx] = sip;
|
||||
ip2rObj->HeaderPtr[idx] = idxptr;
|
||||
idx++;
|
||||
}
|
||||
|
||||
ip2rObj->headerLen = idx;
|
||||
}
|
||||
|
||||
//search the header block to define the index block
|
||||
l = 0; h = ip2rObj->headerLen; sptr = 0; eptr = 0;
|
||||
while ( l <= h ) {
|
||||
m = ((l + h) >> 1);
|
||||
|
||||
//perfetc matched, just return it
|
||||
if ( ip == ip2rObj->HeaderSip[m] ) {
|
||||
if ( m > 0 ) {
|
||||
sptr = ip2rObj->HeaderPtr[m-1];
|
||||
eptr = ip2rObj->HeaderPtr[m ];
|
||||
} else {
|
||||
sptr = ip2rObj->HeaderPtr[m ];
|
||||
eptr = ip2rObj->HeaderPtr[m+1];
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
//less then the middle value
|
||||
if ( ip < ip2rObj->HeaderSip[m] ) {
|
||||
if ( m == 0 ) {
|
||||
sptr = ip2rObj->HeaderPtr[m ];
|
||||
eptr = ip2rObj->HeaderPtr[m+1];
|
||||
break;
|
||||
} else if ( ip > ip2rObj->HeaderSip[m-1] ) {
|
||||
sptr = ip2rObj->HeaderPtr[m-1];
|
||||
eptr = ip2rObj->HeaderPtr[m ];
|
||||
break;
|
||||
}
|
||||
h = m - 1;
|
||||
} else {
|
||||
if ( m == ip2rObj->headerLen - 1 ) {
|
||||
sptr = ip2rObj->HeaderPtr[m-1];
|
||||
eptr = ip2rObj->HeaderPtr[m ];
|
||||
break;
|
||||
} else if ( ip <= ip2rObj->HeaderSip[m+1] ) {
|
||||
sptr = ip2rObj->HeaderPtr[m ];
|
||||
eptr = ip2rObj->HeaderPtr[m+1];
|
||||
break;
|
||||
}
|
||||
l = m + 1;
|
||||
}
|
||||
}
|
||||
|
||||
//not matched just stop it
|
||||
if ( sptr == 0 ) return 0;
|
||||
|
||||
indexBlockLen = eptr - sptr;
|
||||
fseek(ip2rObj->dbHandler, sptr, 0);
|
||||
if ( fread(buffer, indexBlockLen + INDEX_BLOCK_LENGTH, 1, ip2rObj->dbHandler) != 1 ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
dptr = 0; l = 0; h = indexBlockLen / INDEX_BLOCK_LENGTH;
|
||||
while ( l <= h ) {
|
||||
m = ((l + h) >> 1);
|
||||
p = m * INDEX_BLOCK_LENGTH;
|
||||
sip = getUnsignedInt(buffer, p);
|
||||
if ( ip < sip ) {
|
||||
h = m - 1;
|
||||
} else {
|
||||
eip = getUnsignedInt(buffer, p + 4);
|
||||
if ( ip > eip ) {
|
||||
l = m + 1;
|
||||
} else {
|
||||
dptr = getUnsignedInt(buffer, p + 8);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( dptr == 0 ) return 0;
|
||||
|
||||
dataLen = ((dptr >> 24) & 0xFF);
|
||||
dataptr = (dptr & 0x00FFFFFF);
|
||||
|
||||
fseek(ip2rObj->dbHandler, dataptr, 0);
|
||||
if ( fread(buffer, dataLen, 1, ip2rObj->dbHandler) != 1 ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
datablock->city_id = getUnsignedInt(buffer, 0);
|
||||
dataLen -= 4;
|
||||
memcpy(datablock->region, buffer + 4, dataLen);
|
||||
datablock->region[dataLen] = '\0';
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
IP2R_API uint_t ip2region_btree_search_string(ip2region_t ip2rObj, char *ip, datablock_t datablock)
|
||||
{
|
||||
return ip2region_btree_search(ip2rObj, ip2long(ip), datablock);
|
||||
}
|
||||
|
||||
/**
|
||||
* get a unsinged long(4bytes) from a specifield buffer start from the specifield offset
|
||||
*
|
||||
* @param buffer
|
||||
* @param offset
|
||||
* @return uint_t
|
||||
*/
|
||||
IP2R_API uint_t getUnsignedInt(char *buffer, int offset)
|
||||
{
|
||||
return (
|
||||
((buffer[offset ]) & 0x000000FF) |
|
||||
((buffer[offset+1] << 8) & 0x0000FF00) |
|
||||
((buffer[offset+2] << 16) & 0x00FF0000) |
|
||||
((buffer[offset+3] << 24) & 0xFF000000)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* string ip to long
|
||||
*
|
||||
* @param ip
|
||||
* @return uint_t
|
||||
*/
|
||||
IP2R_API uint_t ip2long(char *ip)
|
||||
{
|
||||
int i = 0, p = 24;
|
||||
char buffer[4], *cs = ip;
|
||||
uint_t ipval = 0;
|
||||
|
||||
while ( *cs != '\0' ) {
|
||||
if ( *cs == '.' ) {
|
||||
//single part length limit
|
||||
if ( i > 3 ) {
|
||||
ipval = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if ( p < 0 ) break;
|
||||
buffer[i] = '\0';
|
||||
ipval |= (atoi(buffer) << p);
|
||||
p -= 8;
|
||||
i = 0;
|
||||
} else {
|
||||
buffer[i++] = *cs;
|
||||
}
|
||||
|
||||
cs++;
|
||||
}
|
||||
|
||||
//append the rest parts
|
||||
if ( i > 3 ) return 0;
|
||||
buffer[i] = '\0';
|
||||
ipval |= atoi(buffer);
|
||||
|
||||
return ipval;
|
||||
}
|
||||
|
||||
/**
|
||||
* long to string ip
|
||||
*
|
||||
* @param ip
|
||||
* @param buffer
|
||||
* @return uint_t(1 for success and 0 for failed)
|
||||
*/
|
||||
IP2R_API uint_t long2ip(uint_t ip, char *buffer)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -1,150 +0,0 @@
|
|||
/**
|
||||
* ip2region header file
|
||||
*
|
||||
* @author chenxin<chenxin619315@gmail.com>
|
||||
*/
|
||||
|
||||
#ifndef _IP2REGION_H
|
||||
#define _IP2REGION_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
//yat, just take it as this way, 99 percent and they works find
|
||||
#if ( defined(_WIN32) || defined(_WINDOWS_) || defined(__WINDOWS_) )
|
||||
# define OS_WINDOW
|
||||
#else
|
||||
# define OS_LINUX
|
||||
#endif
|
||||
|
||||
#ifdef OS_WINDOW
|
||||
# define IP2R_API extern __declspec(dllexport)
|
||||
#else
|
||||
/*platform shared library statement :: unix*/
|
||||
# define IP2R_API extern
|
||||
#endif
|
||||
|
||||
#define print(str) printf("%s", str)
|
||||
#define println(str) printf("%s\n", str)
|
||||
|
||||
/*
|
||||
* memory allocation macro definition.
|
||||
* cause we should use emalloc,ecalloc .ege. in php.
|
||||
* so you could make it better apdat the php environment.
|
||||
*/
|
||||
#define IP2R_CALLOC(_bytes, _blocks) calloc(_bytes, _blocks)
|
||||
#define IP2R_MALLOC(_bytes) malloc(_bytes)
|
||||
#define IP2R_FREE(_ptr) free(_ptr)
|
||||
|
||||
typedef unsigned short ushort_t;
|
||||
typedef unsigned char uchar_t;
|
||||
typedef unsigned int uint_t;
|
||||
typedef unsigned long ulong_t;
|
||||
|
||||
#define INDEX_BLOCK_LENGTH 12
|
||||
#define TOTAL_HEADER_LENGTH 4096
|
||||
|
||||
/*
|
||||
* ip2region properties struct
|
||||
*/
|
||||
typedef struct {
|
||||
uint_t *HeaderSip; //header start ip blocks
|
||||
uint_t *HeaderPtr; //header ptr blocks
|
||||
uint_t headerLen; //header block number
|
||||
char *dbFile; //path of db file
|
||||
FILE *dbHandler; //file handler
|
||||
char *dbBinStr; //db binary string for memory search mode
|
||||
|
||||
uint_t firstIndexPtr; //first index ptr
|
||||
uint_t lastIndexPtr; //last index ptr
|
||||
uint_t totalBlocks; //total index blocks number
|
||||
} ip2region_entry;
|
||||
typedef ip2region_entry * ip2region_t;
|
||||
|
||||
/*
|
||||
* data block
|
||||
*/
|
||||
typedef struct {
|
||||
uint_t city_id;
|
||||
char region[256];
|
||||
} datablock_entry;
|
||||
typedef datablock_entry * datablock_t;
|
||||
|
||||
|
||||
/**
|
||||
* create a new ip2region object
|
||||
*
|
||||
* @param ip2rObj
|
||||
* @param dbFile path
|
||||
*/
|
||||
IP2R_API uint_t ip2region_create(ip2region_t, char *);
|
||||
|
||||
/**
|
||||
* destroy the specified ip2region object
|
||||
*
|
||||
* @param ip2region_t
|
||||
*/
|
||||
IP2R_API uint_t ip2region_destroy(ip2region_t);
|
||||
|
||||
/**
|
||||
* get the region associated with the specified ip address with the memory binary search algorithm
|
||||
*
|
||||
* @param ip2region_t
|
||||
* @param uint_t
|
||||
* @param datablock_t
|
||||
* @date 2016/06/30
|
||||
*/
|
||||
IP2R_API uint_t ip2region_memory_search(ip2region_t, uint_t, datablock_t);
|
||||
IP2R_API uint_t ip2region_memory_search_string(ip2region_t, char *, datablock_t);
|
||||
|
||||
|
||||
/**
|
||||
* get the region associated with the specified ip address with binary search algorithm
|
||||
*
|
||||
* @param ip2rObj
|
||||
* @param ip
|
||||
* @param datablock
|
||||
* @return uint_t
|
||||
*/
|
||||
IP2R_API uint_t ip2region_binary_search(ip2region_t, uint_t, datablock_t);
|
||||
IP2R_API uint_t ip2region_binary_search_string(ip2region_t, char *, datablock_t);
|
||||
|
||||
/**
|
||||
* get the region associated with the specified ip address with b-tree algorithm
|
||||
*
|
||||
* @param ip2rObj
|
||||
* @param ip
|
||||
* @param datablock
|
||||
* @return uint_t
|
||||
*/
|
||||
IP2R_API uint_t ip2region_btree_search(ip2region_t, uint_t, datablock_t);
|
||||
IP2R_API uint_t ip2region_btree_search_string(ip2region_t, char *, datablock_t);
|
||||
|
||||
/**
|
||||
* get a unsinged long(4bytes) from a specified buffer start from the specified offset
|
||||
*
|
||||
* @param buffer
|
||||
* @param offset
|
||||
* @return uint_t
|
||||
*/
|
||||
IP2R_API uint_t getUnsignedInt(char *, int);
|
||||
|
||||
/**
|
||||
* string ip to long
|
||||
*
|
||||
* @param ip
|
||||
* @return uint_t
|
||||
*/
|
||||
IP2R_API uint_t ip2long(char *);
|
||||
|
||||
/**
|
||||
* long to string ip
|
||||
*
|
||||
* @param ip
|
||||
* @param buffer
|
||||
* @return uint_t(1 for success and 0 for failed)
|
||||
*/
|
||||
IP2R_API uint_t long2ip(uint_t, char *);
|
||||
|
||||
#endif /*end ifndef*/
|
||||
|
|
@ -0,0 +1,428 @@
|
|||
// Copyright 2022 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// ---
|
||||
// @Author Lion <chenxin619315@gmail.com>
|
||||
// @Date 2022/06/28
|
||||
|
||||
#include "stdio.h"
|
||||
#include "xdb_api.h"
|
||||
|
||||
struct searcher_test_entry {
|
||||
xdb_searcher_t searcher;
|
||||
xdb_vector_index_t *v_index;
|
||||
xdb_content_t *c_buffer;
|
||||
|
||||
// xdb region buffer
|
||||
// char region_buffer[256];
|
||||
xdb_region_buffer_t region;
|
||||
};
|
||||
typedef struct searcher_test_entry searcher_test_t;
|
||||
|
||||
int init_searcher_test(searcher_test_t *test, char *db_path, char *cache_policy) {
|
||||
int err, errcode = 0;
|
||||
FILE *handle = fopen(db_path, "rb");
|
||||
if (handle == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// auto detect the version from the xdb header
|
||||
xdb_header_t *header = xdb_load_header(handle);
|
||||
if (header == NULL) {
|
||||
printf("failed to load header from `%s`\n", db_path);
|
||||
errcode = 1;
|
||||
goto defer;
|
||||
}
|
||||
|
||||
// verify the current xdb
|
||||
err = xdb_verify_from_header(handle, header);
|
||||
if (err != 0) {
|
||||
printf("failed to verify xdb file `%s` with errno=%d\n", db_path, err);
|
||||
errcode = 2;
|
||||
goto defer;
|
||||
}
|
||||
|
||||
xdb_version_t *version = xdb_version_from_header(header);
|
||||
if (version == NULL) {
|
||||
printf("failed to load version from header\n");
|
||||
errcode = 3;
|
||||
goto defer;
|
||||
}
|
||||
|
||||
test->v_index = NULL;
|
||||
test->c_buffer = NULL;
|
||||
|
||||
if (strcmp(cache_policy, "file") == 0) {
|
||||
err = xdb_new_with_file_only(version, &test->searcher, db_path);
|
||||
if (err != 0) {
|
||||
printf("failed to create searcher with errcode=%d\n", err);
|
||||
errcode = 4;
|
||||
goto defer;
|
||||
}
|
||||
} else if (strcmp(cache_policy, "vectorIndex") == 0) {
|
||||
test->v_index = xdb_load_vector_index_from_file(db_path);
|
||||
if (test->v_index == NULL) {
|
||||
printf("failed to load vector index from `%s`\n", db_path);
|
||||
errcode = 4;
|
||||
goto defer;
|
||||
}
|
||||
|
||||
err = xdb_new_with_vector_index(version, &test->searcher, db_path, test->v_index);
|
||||
if (err != 0) {
|
||||
printf("failed to create vector index cached searcher with errcode=%d\n", err);
|
||||
errcode = 5;
|
||||
goto defer;
|
||||
}
|
||||
} else if (strcmp(cache_policy, "content") == 0) {
|
||||
test->c_buffer = xdb_load_content_from_file(db_path);
|
||||
if (test->c_buffer == NULL) {
|
||||
printf("failed to load xdb content from `%s`\n", db_path);
|
||||
errcode = 4;
|
||||
goto defer;
|
||||
}
|
||||
|
||||
err = xdb_new_with_buffer(version, &test->searcher, test->c_buffer);
|
||||
if (err != 0) {
|
||||
printf("failed to create content cached searcher with errcode=%d\n", err);
|
||||
errcode = 5;
|
||||
goto defer;
|
||||
}
|
||||
} else {
|
||||
printf("invalid cache policy `%s`, options: file/vectorIndex/content\n", cache_policy);
|
||||
errcode = 6;
|
||||
goto defer;
|
||||
}
|
||||
|
||||
// init the region buffer
|
||||
// err = xdb_region_buffer_init(&test->region, test->region_buffer, sizeof(test->region_buffer));
|
||||
err = xdb_region_buffer_init(&test->region, NULL, 0);
|
||||
if (err != 0) {
|
||||
printf("failed to init the region buffer with err=%d\n", err);
|
||||
errcode = 7;
|
||||
goto defer;
|
||||
}
|
||||
|
||||
defer:
|
||||
if (header != NULL) {
|
||||
xdb_free_header(header);
|
||||
}
|
||||
|
||||
if (handle != NULL) {
|
||||
fclose(handle);
|
||||
}
|
||||
|
||||
return errcode;
|
||||
}
|
||||
|
||||
void destroy_searcher_test(searcher_test_t *test) {
|
||||
xdb_close(&test->searcher);
|
||||
|
||||
// check and free the vector index
|
||||
if (test->v_index != NULL) {
|
||||
xdb_free_vector_index(test->v_index);
|
||||
test->v_index = NULL;
|
||||
}
|
||||
|
||||
// check and free the content buffer
|
||||
if (test->c_buffer != NULL) {
|
||||
xdb_free_content(test->c_buffer);
|
||||
test->c_buffer = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
//read a line from a command line.
|
||||
static char *get_line(FILE *fp, char *__dst) {
|
||||
register int c;
|
||||
register char *cs;
|
||||
|
||||
cs = __dst;
|
||||
while ( ( c = getc( fp ) ) != EOF ) {
|
||||
if ( c == '\n' ) break;
|
||||
*cs++ = c;
|
||||
}
|
||||
*cs = '\0';
|
||||
|
||||
return ( c == EOF && cs == __dst ) ? NULL : __dst;
|
||||
}
|
||||
|
||||
void print_help(char *argv[]) {
|
||||
printf("ip2region xdb searcher\n");
|
||||
printf("%s [command] [command options]\n", argv[0]);
|
||||
printf("Command: \n");
|
||||
printf(" search search input test\n");
|
||||
printf(" bench search bench test\n");
|
||||
}
|
||||
|
||||
void test_search(int argc, char *argv[]) {
|
||||
int i, n, err;
|
||||
|
||||
// for args parse
|
||||
char *r, key[33] = {'\0'}, val[256] = {'\0'};
|
||||
char db_file[256] = {'\0'}, cache_policy[16] = {"vectorIndex"};
|
||||
|
||||
// for search
|
||||
long s_time, c_time;
|
||||
char line[512] = {'\0'};
|
||||
|
||||
// ip parse
|
||||
xdb_version_t *version;
|
||||
bytes_ip_t ip_bytes[16] = {'\0'};
|
||||
|
||||
searcher_test_t test;
|
||||
|
||||
for (i = 2; i < argc; i++) {
|
||||
r = argv[i];
|
||||
if (strlen(r) < 5) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (r[0] != '-' || r[1] != '-') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strchr(r, '=') == NULL) {
|
||||
printf("missing = for args pair '%s'\n", r);
|
||||
return;
|
||||
}
|
||||
|
||||
n = sscanf(r+2, "%32[^=]=%255[^\n]", key, val);
|
||||
if (n != 2) {
|
||||
printf("invalid option flag `%s`\n", r);
|
||||
return;
|
||||
}
|
||||
|
||||
// printf("key=%s, val=%s\n", key, val);
|
||||
if (strcmp(key, "db") == 0) {
|
||||
snprintf(db_file, sizeof(db_file), "%s", val);
|
||||
} else if (strcmp(key, "cache-policy") == 0) {
|
||||
memcpy(cache_policy, val, sizeof(cache_policy) - 1);
|
||||
// snprintf(cache_policy, sizeof(cache_policy), "%s", val);
|
||||
} else {
|
||||
printf("undefined option `%s`\n", r);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (strlen(db_file) < 1) {
|
||||
printf("%s search [command options]\n", argv[0]);
|
||||
printf("options:\n");
|
||||
printf(" --db string ip2region binary xdb file path\n");
|
||||
printf(" --cache-policy string cache policy: file/vectorIndex/content\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// init the win sock
|
||||
err = xdb_init_winsock();
|
||||
if (err != 0) {
|
||||
printf("failed to init the winsock with errno=%d\n", err);
|
||||
return;
|
||||
}
|
||||
|
||||
// printf("db_file=%s, cache_policy=%s\n", db_file, cache_policy);
|
||||
err = init_searcher_test(&test, db_file, cache_policy);
|
||||
if (err != 0) {
|
||||
// init program will print the error reasons;
|
||||
return;
|
||||
}
|
||||
|
||||
printf("ip2region xdb searcher test program\n"
|
||||
"source xdb: %s (%s, %s)\n"
|
||||
"type 'quit' to exit\n", db_file, xdb_get_version(&test.searcher)->name, cache_policy);
|
||||
while ( 1 ) {
|
||||
printf("ip2region>> ");
|
||||
get_line(stdin, line);
|
||||
if ( strlen(line) < 2 ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strcmp(line, "quit") == 0 ) {
|
||||
break;
|
||||
}
|
||||
|
||||
version = xdb_parse_ip(line, ip_bytes, sizeof(ip_bytes));
|
||||
if (version == NULL) {
|
||||
printf("invalid ip address `%s`\n", line);
|
||||
continue;
|
||||
}
|
||||
|
||||
s_time = xdb_now();
|
||||
err = xdb_search(&test.searcher, ip_bytes, version->bytes, &test.region);
|
||||
if (err != 0) {
|
||||
printf("{err: %d, io_count: %d}\n", err, xdb_get_io_count(&test.searcher));
|
||||
} else {
|
||||
c_time = xdb_now() - s_time;
|
||||
printf("{region: %s, io_count: %d, took: %ld μs}\n", test.region.value, xdb_get_io_count(&test.searcher), c_time);
|
||||
}
|
||||
|
||||
// free the region
|
||||
xdb_region_buffer_free(&test.region);
|
||||
}
|
||||
|
||||
destroy_searcher_test(&test);
|
||||
xdb_clean_winsock();
|
||||
printf("searcher test program exited, thanks for trying\n");
|
||||
}
|
||||
|
||||
void test_bench(int argc, char *argv[]) {
|
||||
int i, n, err;
|
||||
char *r, key[33] = {'\0'}, val[256] = {'\0'};
|
||||
char db_file[256] = {'\0'}, src_file[256] = {'\0'}, cache_policy[16] = {"vectorIndex"};
|
||||
|
||||
FILE *handle;
|
||||
char line[1024] = {'\0'}, sip_str[INET6_ADDRSTRLEN+1] = {'\0'}, eip_str[INET6_ADDRSTRLEN+1] = {'\0'};
|
||||
char src_region[512] = {'\0'};
|
||||
int count = 0, took;
|
||||
long s_time, t_time, c_time = 0;
|
||||
|
||||
// ip parse
|
||||
xdb_version_t *s_version, *e_version;
|
||||
bytes_ip_t sip_bytes[16] = {'\0'}, eip_bytes[16] = {'\0'};
|
||||
string_ip_t ip_string[INET6_ADDRSTRLEN] = {'\0'};
|
||||
bytes_ip_t *ip_list[2];
|
||||
|
||||
searcher_test_t test;
|
||||
for (i = 2; i < argc; i++) {
|
||||
r = argv[i];
|
||||
if (strlen(r) < 5) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (r[0] != '-' || r[1] != '-') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strchr(r, '=') == NULL) {
|
||||
printf("missing = for args pair '%s'\n", r);
|
||||
return;
|
||||
}
|
||||
|
||||
n = sscanf(r+2, "%32[^=]=%255[^\n]", key, val);
|
||||
if (n != 2) {
|
||||
printf("invalid option flag `%s`\n", r);
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp(key, "db") == 0) {
|
||||
snprintf(db_file, sizeof(db_file), "%s", val);
|
||||
} else if (strcmp(key, "src") == 0) {
|
||||
snprintf(src_file, sizeof(src_file), "%s", val);
|
||||
} else if (strcmp(key, "cache-policy") == 0) {
|
||||
memcpy(cache_policy, val, sizeof(cache_policy) - 1);
|
||||
} else {
|
||||
printf("undefined option `%s`\n", r);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (strlen(db_file) < 1 || strlen(src_file) < 1) {
|
||||
printf("%s bench [command options]\n", argv[0]);
|
||||
printf("options:\n");
|
||||
printf(" --db string ip2region binary xdb file path\n");
|
||||
printf(" --src string source ip text file path\n");
|
||||
printf(" --cache-policy string cache policy: file/vectorIndex/content\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// init the win sock
|
||||
err = xdb_init_winsock();
|
||||
if (err != 0) {
|
||||
printf("failed to init the winsock with errno=%d\n", err);
|
||||
return;
|
||||
}
|
||||
|
||||
// printf("db_file=%s, src_file=%s, cache_policy=%s\n", db_file, src_file, cache_policy);
|
||||
s_time = xdb_now();
|
||||
err = init_searcher_test(&test, db_file, cache_policy);
|
||||
if (err != 0) {
|
||||
// the init function will print the details;
|
||||
return;
|
||||
}
|
||||
|
||||
// open the source file
|
||||
handle = fopen(src_file, "r");
|
||||
if (handle == NULL) {
|
||||
printf("failed to open source text file `%s`\n", src_file);
|
||||
return;
|
||||
}
|
||||
|
||||
while(fgets(line, sizeof(line), handle) != NULL) {
|
||||
n = sscanf(line, "%46[^|]|%46[^|]|%511[^\n]", sip_str, eip_str, src_region);
|
||||
if (n != 3) {
|
||||
printf("invalid ip segment line `%s`\n", line);
|
||||
return;
|
||||
}
|
||||
|
||||
s_version = xdb_parse_ip(sip_str, sip_bytes, sizeof(sip_bytes));
|
||||
if (s_version == NULL) {
|
||||
printf("invalid start ip `%s`\n", sip_str);
|
||||
return;
|
||||
}
|
||||
|
||||
e_version = xdb_parse_ip(eip_str, eip_bytes, sizeof(eip_bytes));
|
||||
if (e_version == NULL) {
|
||||
printf("invalid end ip `%s`\n", sip_str);
|
||||
return;
|
||||
}
|
||||
|
||||
if (s_version->id != e_version->id) {
|
||||
printf("start ip and end ip version not match for line `%s`\n", line);
|
||||
return;
|
||||
}
|
||||
|
||||
if (xdb_ip_sub_compare(sip_bytes, s_version->bytes, (string_ip_t *) eip_bytes, 0) > 0) {
|
||||
printf("start ip(%s) should not be greater than end ip(%s)\n", sip_str, eip_str);
|
||||
return;
|
||||
}
|
||||
|
||||
ip_list[0] = sip_bytes;
|
||||
ip_list[1] = eip_bytes;
|
||||
for (i = 0; i < 2; i++) {
|
||||
t_time = xdb_now();
|
||||
err = xdb_search(&test.searcher, ip_list[i], s_version->bytes, &test.region);
|
||||
c_time += xdb_now() - t_time;
|
||||
if (err != 0) {
|
||||
xdb_ip_to_string(ip_list[i], s_version->bytes, ip_string, sizeof(ip_string));
|
||||
printf("failed to search ip `%s` with errno=%d\n", ip_string, err);
|
||||
return;
|
||||
}
|
||||
|
||||
// check the region info
|
||||
if (strcmp(test.region.value, src_region) != 0) {
|
||||
xdb_ip_to_string(ip_list[i], s_version->bytes, ip_string, sizeof(ip_string));
|
||||
printf("failed to search(%s) with (%s != %s)\n", ip_string, test.region.value, src_region);
|
||||
return;
|
||||
}
|
||||
|
||||
// free the region buffer
|
||||
xdb_region_buffer_free(&test.region);
|
||||
|
||||
count++;
|
||||
}
|
||||
};
|
||||
|
||||
took = xdb_now() - s_time;
|
||||
destroy_searcher_test(&test);
|
||||
xdb_clean_winsock();
|
||||
fclose(handle);
|
||||
printf("Bench finished, {cache_policy: %s, total: %d, took: %.3fs, cost: %d μs/op}\n",
|
||||
cache_policy, count, took/1e6, count == 0 ? 0 : (int)(c_time/count));
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
if (argc < 2) {
|
||||
print_help(argv);
|
||||
return 0;
|
||||
}
|
||||
|
||||
char *opt = argv[1];
|
||||
if (strcmp(opt, "search") == 0) {
|
||||
test_search(argc, argv);
|
||||
} else if (strcmp(opt, "bench") == 0) {
|
||||
test_bench(argc, argv);
|
||||
} else {
|
||||
print_help(argv);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -1,102 +0,0 @@
|
|||
/**
|
||||
* test ip2region searcher program
|
||||
*
|
||||
* @author chenxin<chenxin619315@gmail.com>
|
||||
* @date 2015-10-30
|
||||
*/
|
||||
|
||||
#include "ip2region.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
#define __PRINT_ABOUT__ \
|
||||
println("+-------------------------------------+"); \
|
||||
println("| ip2region test program |"); \
|
||||
println("| Author: chenxin619315@gmail.com. |"); \
|
||||
println("| Type 'quit' to exit the program. |"); \
|
||||
println("+-------------------------------------+");
|
||||
|
||||
//read a line from a command line.
|
||||
static char *getLine( FILE *fp, char *__dst )
|
||||
{
|
||||
register int c;
|
||||
register char *cs;
|
||||
|
||||
cs = __dst;
|
||||
while ( ( c = getc( fp ) ) != EOF ) {
|
||||
if ( c == '\n' ) break;
|
||||
*cs++ = c;
|
||||
}
|
||||
*cs = '\0';
|
||||
|
||||
return ( c == EOF && cs == __dst ) ? NULL : __dst;
|
||||
}
|
||||
|
||||
static double getTime()
|
||||
{
|
||||
struct timeval tv;
|
||||
struct timezone tz;
|
||||
gettimeofday(&tv, &tz);
|
||||
|
||||
return (tv.tv_sec * 1000 + ((double)tv.tv_usec)/1000);
|
||||
}
|
||||
|
||||
int main( int argc, char **argv )
|
||||
{
|
||||
ip2region_entry ip2rEntry;
|
||||
datablock_entry datablock;
|
||||
char *dbFile = NULL, *algorithm = NULL;
|
||||
char line[256];
|
||||
uint_t (*func_ptr)(ip2region_t, char *, datablock_t);
|
||||
double s_time, c_time;
|
||||
memset(&datablock, 0x00, sizeof(datablock_entry));
|
||||
|
||||
if ( argc < 2 ) {
|
||||
printf("Usage: a.out [ip2region db file path] [algorithm]\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
dbFile = argv[1];
|
||||
algorithm = "B-tree";
|
||||
func_ptr = ip2region_btree_search_string;
|
||||
if ( argc >= 3 ) {
|
||||
if ( strcmp(argv[2], "binary") == 0 ) {
|
||||
algorithm = "Binary";
|
||||
func_ptr = ip2region_binary_search_string;
|
||||
} else if ( strcmp(argv[2], "memory") == 0 ) {
|
||||
algorithm = "Memory";
|
||||
func_ptr = ip2region_memory_search_string;
|
||||
}
|
||||
}
|
||||
|
||||
//create a new ip2rObj
|
||||
printf("+--initializing %s ... \n", algorithm);
|
||||
if ( ip2region_create(&ip2rEntry, dbFile) == 0 ) {
|
||||
println("Error: Fail to create the ip2region object\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
__PRINT_ABOUT__;
|
||||
|
||||
while ( 1 ) {
|
||||
print("ip2region>> ");
|
||||
getLine( stdin, line );
|
||||
if ( strlen(line) < 2 ) continue;
|
||||
if ( strcasecmp( line, "quit" ) == 0 ) {
|
||||
println("+--Bye!");
|
||||
break;
|
||||
}
|
||||
|
||||
s_time = getTime();
|
||||
func_ptr(&ip2rEntry, line, &datablock);
|
||||
c_time = getTime() - s_time;
|
||||
printf("%d|%s in %.5f millseconds\n", datablock.city_id, datablock.region, c_time);
|
||||
}
|
||||
|
||||
//destory the ip2rObj
|
||||
ip2region_destroy(&ip2rEntry);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,213 @@
|
|||
// Copyright 2022 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// ---
|
||||
// @Author Lion <chenxin619315@gmail.com>
|
||||
// @Date 2022/06/27
|
||||
|
||||
#include "stdio.h"
|
||||
#include "xdb_api.h"
|
||||
|
||||
typedef void (* test_func_ptr) ();
|
||||
struct test_func_entry {
|
||||
char *name;
|
||||
test_func_ptr func;
|
||||
};
|
||||
typedef struct test_func_entry test_func_t;
|
||||
|
||||
void test_load_header() {
|
||||
xdb_header_t *header = xdb_load_header_from_file("../../data/ip2region_v4.xdb");
|
||||
if (header == NULL) {
|
||||
printf("failed to load header");
|
||||
} else {
|
||||
printf("header loaded: {\n"
|
||||
" version: %d, \n"
|
||||
" index_policy: %d, \n"
|
||||
" created_at: %u, \n"
|
||||
" start_index_ptr: %d, \n"
|
||||
" end_index_ptr: %d\n"
|
||||
" ip_version: %d\n"
|
||||
" runtime_ptr_bytes: %d\n"
|
||||
" length: %d\n"
|
||||
"}\n",
|
||||
header->version, header->index_policy, header->created_at,
|
||||
header->start_index_ptr, header->end_index_ptr,
|
||||
header->ip_version, header->runtime_ptr_bytes, header->length
|
||||
);
|
||||
}
|
||||
|
||||
xdb_free_header(header);
|
||||
}
|
||||
|
||||
void test_load_vector_index() {
|
||||
xdb_vector_index_t *v_index = xdb_load_vector_index_from_file("../../data/ip2region_v4.xdb");
|
||||
if (v_index == NULL) {
|
||||
printf("failed to load vector index from file\n");
|
||||
} else {
|
||||
printf("vector index loaded from file, length=%d\n", v_index->length);
|
||||
}
|
||||
|
||||
xdb_free_vector_index(v_index);
|
||||
}
|
||||
|
||||
void test_load_content() {
|
||||
xdb_content_t *content = xdb_load_content_from_file("../../data/ip2region_v4.xdb");
|
||||
if (content == NULL) {
|
||||
printf("failed to load content from file\n");
|
||||
} else {
|
||||
printf("content loaded from file, length=%d\n", content->length);
|
||||
}
|
||||
|
||||
xdb_free_content(content);
|
||||
}
|
||||
|
||||
void test_parse_ip() {
|
||||
const char *ip_list[] = {
|
||||
"1.0.0.0", "58.251.30.115", "192.168.1.100",
|
||||
"::", "2c0f:fff0::", "2fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", "240e:982:e617:ffff:ffff:ffff:ffff:ffff",
|
||||
"219.xx.xx.11", "::xx:ffff",
|
||||
NULL
|
||||
};
|
||||
|
||||
int errcode;
|
||||
xdb_version_t *version;
|
||||
bytes_ip_t ip_bytes[16] = {'\0'};
|
||||
string_ip_t ip_string[INET6_ADDRSTRLEN] = {'\0'};
|
||||
|
||||
// init the sock env (for windows)
|
||||
if ((errcode = xdb_init_winsock()) != 0) {
|
||||
printf("failed to init the winsock");
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0;; i++) {
|
||||
if (ip_list[i] == NULL) {
|
||||
break;
|
||||
}
|
||||
|
||||
version = xdb_parse_ip(ip_list[i], ip_bytes, sizeof(ip_bytes));
|
||||
if (version == NULL) {
|
||||
printf("failed to parse ip `%s`\n", ip_list[i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
xdb_ip_to_string(ip_bytes, version->bytes, ip_string, sizeof(ip_string));
|
||||
printf("ip: %s (version=v%d), toString: %s\n", ip_list[i], version->id, ip_string);
|
||||
}
|
||||
|
||||
// clean up the winsock
|
||||
xdb_clean_winsock();
|
||||
}
|
||||
|
||||
struct ip_pair {
|
||||
const char *sip;
|
||||
const char *eip;
|
||||
};
|
||||
void test_ip_compare() {
|
||||
struct ip_pair ip_pair_list[] = {
|
||||
{"1.0.0.0", "1.0.0.1"},
|
||||
{"192.168.1.101", "192.168.1.90"},
|
||||
{"219.133.111.87", "114.114.114.114"},
|
||||
{"1.0.4.0", "1.0.1.0"},
|
||||
{"1.0.4.0", "1.0.3.255"},
|
||||
{"2000::", "2000:ffff:ffff:ffff:ffff:ffff:ffff:ffff"},
|
||||
{"2001:4:112::", "2001:4:112:ffff:ffff:ffff:ffff:ffff"},
|
||||
{"ffff::", "2001:4:ffff:ffff:ffff:ffff:ffff:ffff"},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
struct ip_pair *pair_ptr = NULL;
|
||||
bytes_ip_t sip_bytes[16] = {'\0'};
|
||||
bytes_ip_t eip_bytes[16] = {'\0'};
|
||||
xdb_version_t *s_version, *e_version;
|
||||
int errcode;
|
||||
|
||||
// init the sock env (for windows)
|
||||
if ((errcode = xdb_init_winsock()) != 0) {
|
||||
printf("failed to init the winsock");
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; ;i++) {
|
||||
pair_ptr = &ip_pair_list[i];
|
||||
if (pair_ptr->sip == NULL) {
|
||||
break;
|
||||
}
|
||||
|
||||
s_version = xdb_parse_ip(pair_ptr->sip, sip_bytes, sizeof(sip_bytes));
|
||||
if (s_version == NULL) {
|
||||
printf("failed to parse sip `%s`", pair_ptr->sip);
|
||||
continue;
|
||||
}
|
||||
|
||||
e_version = xdb_parse_ip(pair_ptr->eip, eip_bytes, sizeof(eip_bytes));
|
||||
if (e_version == NULL) {
|
||||
printf("failed to parse eip `%s`", pair_ptr->eip);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (s_version->id != e_version->id) {
|
||||
printf("sip and eip version not match `%s` != `%s`\n", s_version->name, e_version->name);
|
||||
continue;
|
||||
}
|
||||
|
||||
printf(
|
||||
"ip_sub_compare(%s, %s): %d\n",
|
||||
pair_ptr->sip, pair_ptr->eip,
|
||||
xdb_ip_sub_compare(sip_bytes, s_version->bytes, (string_ip_t *) eip_bytes, 0)
|
||||
);
|
||||
}
|
||||
|
||||
// clean up the winsock
|
||||
xdb_clean_winsock();
|
||||
}
|
||||
|
||||
// please register your function heare
|
||||
static test_func_t _test_function_list[] = {
|
||||
// xdb buffer
|
||||
{"test_load_header", test_load_header},
|
||||
{"test_load_vector_index", test_load_vector_index},
|
||||
{"test_load_content", test_load_content},
|
||||
|
||||
// ip utils
|
||||
{"test_parse_ip", test_parse_ip},
|
||||
{"test_ip_compare", test_ip_compare},
|
||||
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
// valgrind --tool=memcheck --leak-check=full ./a.out
|
||||
int main(int argc, char *argv[]) {
|
||||
int i;
|
||||
char *name;
|
||||
|
||||
// check and call the function
|
||||
if (argc < 2) {
|
||||
printf("please specified the function name to call\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
name = argv[1];
|
||||
test_func_ptr func = NULL;
|
||||
for (i = 0; ; i++) {
|
||||
if (_test_function_list[i].name == NULL) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (strcmp(name, _test_function_list[i].name) == 0) {
|
||||
func = _test_function_list[i].func;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (func == NULL) {
|
||||
printf("can't find test function `%s`\n", name);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// call the function
|
||||
func();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,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
|
||||
|
|
@ -0,0 +1,280 @@
|
|||
// Copyright 2022 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// ---
|
||||
// @Author Lion <chenxin619315@gmail.com>
|
||||
// @Date 2022/06/27
|
||||
|
||||
#include "xdb_api.h"
|
||||
|
||||
// --- region buffer
|
||||
XDB_PUBLIC(int) xdb_region_buffer_init(xdb_region_buffer_t *region, char *buffer, size_t length) {
|
||||
if (buffer == NULL) {
|
||||
region->type = xdb_region_buffer_auto;
|
||||
region->length = 0;
|
||||
} else if (length <= 0) {
|
||||
return 1;
|
||||
} else {
|
||||
region->type = xdb_region_buffer_wrapper;
|
||||
region->length = length;
|
||||
memset(buffer, 0x00, length); // zero-fill the buffer
|
||||
}
|
||||
|
||||
region->value = buffer;
|
||||
return 0;
|
||||
}
|
||||
|
||||
XDB_PUBLIC(int) xdb_region_buffer_alloc(xdb_region_buffer_t *region, int length) {
|
||||
if (length <= 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// no allocation supports for the buffer wapper
|
||||
if (region->type == xdb_region_buffer_wrapper) {
|
||||
if (length >= region->length) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
region->value[length] = '\0';
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ensure that the value were freed
|
||||
// by calling #xdb_region_buffer_free
|
||||
if (region->value != NULL) {
|
||||
return 3;
|
||||
}
|
||||
|
||||
char *ptr = (char *) xdb_malloc(length + 1);
|
||||
if (ptr == NULL) {
|
||||
return 4;
|
||||
}
|
||||
|
||||
ptr[length] = '\0'; // NULL-end
|
||||
region->value = ptr;
|
||||
region->length = length;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// fixed internal empty string ptr
|
||||
static char * _empty_region_string = "\0";
|
||||
|
||||
XDB_PUBLIC(int) xdb_region_buffer_empty(xdb_region_buffer_t *region) {
|
||||
// no allocation supports for the buffer wapper
|
||||
if (region->type == xdb_region_buffer_wrapper) {
|
||||
region->value[0] = '\0';
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ensure that the value were freed
|
||||
// by calling #xdb_region_buffer_free
|
||||
if (region->value != NULL) {
|
||||
return 3;
|
||||
}
|
||||
|
||||
region->value = _empty_region_string;
|
||||
region->length = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
XDB_PUBLIC(void) xdb_region_buffer_free(xdb_region_buffer_t *region) {
|
||||
if (region->type == xdb_region_buffer_auto) {
|
||||
// empty string interception
|
||||
if (region->length == 0
|
||||
|| region->value == _empty_region_string) {
|
||||
// do nothing for empty string
|
||||
} else {
|
||||
xdb_free(region->value);
|
||||
}
|
||||
|
||||
// reset the value
|
||||
region->value = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// --- END region buffer
|
||||
|
||||
// internal function prototype define
|
||||
XDB_PRIVATE(int) read(xdb_searcher_t *, long offset, char *, size_t length);
|
||||
|
||||
XDB_PRIVATE(int) xdb_new_base(xdb_version_t *version, xdb_searcher_t *xdb, const char *db_path, const xdb_vector_index_t *v_index, const xdb_content_t *c_buffer) {
|
||||
memset(xdb, 0x00, sizeof(xdb_searcher_t));
|
||||
|
||||
// set the version
|
||||
xdb->version = version;
|
||||
|
||||
// check the content buffer first
|
||||
if (c_buffer != NULL) {
|
||||
xdb->v_index = NULL;
|
||||
xdb->content = c_buffer;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// open the xdb binary file
|
||||
FILE *handle = fopen(db_path, "rb");
|
||||
if (handle == NULL) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
xdb->handle = handle;
|
||||
xdb->v_index = v_index;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// xdb searcher new api define
|
||||
XDB_PUBLIC(int) xdb_new_with_file_only(xdb_version_t *version, xdb_searcher_t *xdb, const char *db_path) {
|
||||
return xdb_new_base(version, xdb, db_path, NULL, NULL);
|
||||
}
|
||||
|
||||
XDB_PUBLIC(int) xdb_new_with_vector_index(xdb_version_t *version, xdb_searcher_t *xdb, const char *db_path, const xdb_vector_index_t *v_index) {
|
||||
return xdb_new_base(version, xdb, db_path, v_index, NULL);
|
||||
}
|
||||
|
||||
XDB_PUBLIC(int) xdb_new_with_buffer(xdb_version_t *version, xdb_searcher_t *xdb, const xdb_content_t *c_buffer) {
|
||||
return xdb_new_base(version, xdb, NULL, NULL, c_buffer);
|
||||
}
|
||||
|
||||
XDB_PUBLIC(void) xdb_close(void *ptr) {
|
||||
xdb_searcher_t *xdb = (xdb_searcher_t *) ptr;
|
||||
if (xdb->handle != NULL) {
|
||||
fclose(xdb->handle);
|
||||
xdb->handle = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// --- xdb searcher search api define
|
||||
|
||||
XDB_PUBLIC(int) xdb_search_by_string(xdb_searcher_t *xdb, const string_ip_t *ip_string, xdb_region_buffer_t *region) {
|
||||
bytes_ip_t ip_bytes[16] = {'\0'};
|
||||
xdb_version_t *version = xdb_parse_ip(ip_string, ip_bytes, sizeof(ip_bytes));
|
||||
if (version == NULL) {
|
||||
return 10;
|
||||
} else {
|
||||
return xdb_search(xdb, ip_bytes, version->bytes, region);
|
||||
}
|
||||
}
|
||||
|
||||
XDB_PUBLIC(int) xdb_search(xdb_searcher_t *xdb, const bytes_ip_t *ip_bytes, int ip_len, xdb_region_buffer_t *region) {
|
||||
int il0, il1, idx, err, bytes, d_bytes;
|
||||
register int seg_index_size, l, h, m, p;
|
||||
unsigned int s_ptr, e_ptr, data_ptr, data_len;
|
||||
char vector_buffer[xdb_vector_index_size];
|
||||
char segment_buffer[xdb_v6_index_size];
|
||||
|
||||
// ip version check
|
||||
if (ip_len != xdb->version->bytes) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// some resets
|
||||
err = 0;
|
||||
data_len = 0;
|
||||
bytes = xdb->version->bytes;
|
||||
d_bytes = xdb->version->bytes << 1;
|
||||
xdb->io_count = 0;
|
||||
|
||||
// locate the segment index block based on the vector index
|
||||
il0 = (int) (ip_bytes[0]);
|
||||
il1 = (int) (ip_bytes[1]);
|
||||
idx = il0 * xdb_vector_index_cols * xdb_vector_index_size + il1 * xdb_vector_index_size;
|
||||
if (xdb->v_index != NULL) {
|
||||
s_ptr = xdb_le_get_uint32(xdb->v_index->buffer, idx);
|
||||
e_ptr = xdb_le_get_uint32(xdb->v_index->buffer, idx + 4);
|
||||
} else if (xdb->content != NULL) {
|
||||
s_ptr = xdb_le_get_uint32(xdb->content->buffer, xdb_header_info_length + idx);
|
||||
e_ptr = xdb_le_get_uint32(xdb->content->buffer, xdb_header_info_length + idx + 4);
|
||||
} else {
|
||||
err = read(xdb, xdb_header_info_length + idx, vector_buffer, sizeof(vector_buffer));
|
||||
if (err != 0) {
|
||||
return 10 + err;
|
||||
}
|
||||
|
||||
s_ptr = xdb_le_get_uint32(vector_buffer, 0);
|
||||
e_ptr = xdb_le_get_uint32(vector_buffer, 4);
|
||||
}
|
||||
|
||||
// printf("s_ptr=%u, e_ptr=%u\n", s_ptr, e_ptr);
|
||||
// @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
|
||||
// segment_buffer = xdb_malloc(seg_index_size);
|
||||
seg_index_size = xdb->version->segment_index_size;
|
||||
data_len = 0, data_ptr = 0;
|
||||
l = 0, h = ((int) (e_ptr - s_ptr)) / seg_index_size;
|
||||
while (l <= h) {
|
||||
m = (l + h) >> 1;
|
||||
p = s_ptr + m * seg_index_size;
|
||||
|
||||
// read the segment index item
|
||||
err = read(xdb, p, segment_buffer, seg_index_size);
|
||||
if (err != 0) {
|
||||
return 20 + err;
|
||||
}
|
||||
|
||||
// decode the data fields as needed
|
||||
if (xdb->version->ip_compare(ip_bytes, bytes, segment_buffer, 0) < 0) {
|
||||
h = m - 1;
|
||||
} else if (xdb->version->ip_compare(ip_bytes, bytes, segment_buffer, bytes) > 0) {
|
||||
l = m + 1;
|
||||
} else {
|
||||
data_len = xdb_le_get_uint16(segment_buffer, d_bytes);
|
||||
data_ptr = xdb_le_get_uint32(segment_buffer, d_bytes + 2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// printf("data_len=%u, data_ptr=%u\n", data_len, data_ptr);
|
||||
if (data_len == 0) {
|
||||
// return 100;
|
||||
xdb_region_buffer_empty(region);
|
||||
return err;
|
||||
}
|
||||
|
||||
// buffer alloc checking
|
||||
err = xdb_region_buffer_alloc(region, data_len);
|
||||
if (err != 0) {
|
||||
return 100 + err;
|
||||
}
|
||||
|
||||
err = read(xdb, data_ptr, region->value, data_len);
|
||||
if (err != 0) {
|
||||
return 30 + err;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
XDB_PRIVATE(int) read(xdb_searcher_t *xdb, long offset, char *buffer, size_t length) {
|
||||
// check the xdb content cache first
|
||||
if (xdb->content != NULL) {
|
||||
memcpy(buffer, xdb->content->buffer + offset, length);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// seek to the offset
|
||||
if (fseek(xdb->handle, offset, SEEK_SET) == -1) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
xdb->io_count++;
|
||||
if (fread(buffer, 1, length, xdb->handle) != length) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
XDB_PUBLIC(xdb_version_t *) xdb_get_version(xdb_searcher_t *xdb) {
|
||||
return xdb->version;
|
||||
}
|
||||
|
||||
XDB_PUBLIC(int) xdb_get_io_count(xdb_searcher_t *xdb) {
|
||||
return xdb->io_count;
|
||||
}
|
||||
|
|
@ -0,0 +1,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);
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
# build cache
|
||||
.cache/
|
||||
|
||||
# dependency lock
|
||||
cjpm.lock
|
||||
|
|
@ -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
|
||||
|
|
@ -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]
|
||||
|
|
@ -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 = ".." }
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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") }
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
package ip2region.tests
|
||||
|
|
@ -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") }
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
bin/
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
|
||||
all: bin header search bench make edit
|
||||
|
||||
FILES=$(wildcard src/*.cc)
|
||||
|
||||
bin:
|
||||
mkdir -p bin
|
||||
|
||||
header: $(FILES) test/header.cc
|
||||
g++ -std=c++11 -O2 $^ -o bin/$@
|
||||
|
||||
search: $(FILES) test/search.cc
|
||||
g++ -std=c++11 -O2 $^ -o bin/$@
|
||||
|
||||
bench: $(FILES) test/bench.cc
|
||||
g++ -std=c++11 -O2 $^ -o bin/$@
|
||||
|
||||
make: $(FILES) test/make.cc
|
||||
g++ -std=c++11 -O2 $^ -o bin/$@
|
||||
|
||||
edit: $(FILES)
|
||||
g++ -std=c++11 -O2 $^ test/edit_v4.cc -o bin/edit_v4
|
||||
g++ -std=c++11 -O2 $^ test/edit_v6.cc -o bin/edit_v6
|
||||
|
||||
clean:
|
||||
rm -rf bin
|
||||
|
||||
|
|
@ -0,0 +1,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
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
|
||||
#include "base.h"
|
||||
|
||||
namespace xdb {
|
||||
|
||||
int ip_version; // ip 版本
|
||||
int ip_size; // ip 占的字节数
|
||||
int content_size;
|
||||
|
||||
void init_xdb(int version) {
|
||||
ip_version = version;
|
||||
ip_size = version == ipv4 ? 4 : 16;
|
||||
content_size = ip_size * 2 + 2 + 4;
|
||||
}
|
||||
|
||||
void log_exit(const string &msg) {
|
||||
std::cout << msg << std::endl;
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
void read_bin(int index, char *buf, size_t len, FILE *db) {
|
||||
fseek(db, index, SEEK_SET);
|
||||
if (fread(buf, 1, len, db) != len)
|
||||
log_exit(__func__);
|
||||
}
|
||||
|
||||
unsigned to_uint(const char *buf) {
|
||||
return ((buf[0]) & 0x000000FF) | ((buf[1] << 8) & 0x0000FF00) |
|
||||
((buf[2] << 16) & 0x00FF0000) | ((buf[3] << 24) & 0xFF000000);
|
||||
}
|
||||
|
||||
unsigned to_ushort(const char *buf) {
|
||||
return ((buf[0]) & 0x000000FF) | ((buf[1] << 8) & 0x0000FF00);
|
||||
}
|
||||
|
||||
unsigned to_int(const char *buf, int n) {
|
||||
return n == 2 ? to_ushort(buf) : to_uint(buf);
|
||||
}
|
||||
|
||||
void write_uint(unsigned data, char buf[]) {
|
||||
buf[0] = (data >> 0) & 0xFF;
|
||||
buf[1] = (data >> 8) & 0xFF;
|
||||
buf[2] = (data >> 16) & 0xFF;
|
||||
buf[3] = (data >> 24) & 0xFF;
|
||||
}
|
||||
|
||||
void write_uint(unsigned data, FILE *dst) {
|
||||
char buf[4];
|
||||
write_uint(data, buf);
|
||||
fwrite(buf, 1, sizeof(buf), dst);
|
||||
}
|
||||
|
||||
void write_ushort(unsigned data, char buf[]) {
|
||||
buf[0] = (data >> 0) & 0xFF;
|
||||
buf[1] = (data >> 8) & 0xFF;
|
||||
}
|
||||
|
||||
void write_ushort(unsigned data, FILE *dst) {
|
||||
char buf[2];
|
||||
write_ushort(data, buf);
|
||||
fwrite(buf, 1, sizeof(buf), dst);
|
||||
}
|
||||
|
||||
void write_string(const char *buf, unsigned len, FILE *dst) {
|
||||
fwrite(buf, 1, len, dst);
|
||||
}
|
||||
|
||||
unsigned long long get_time() {
|
||||
struct timeval tv1;
|
||||
gettimeofday(&tv1, NULL);
|
||||
return (unsigned long long)tv1.tv_sec * 1000 * 1000 + tv1.tv_usec;
|
||||
}
|
||||
|
||||
} // namespace xdb
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
#ifndef BASE_H
|
||||
#define BASE_H
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace xdb {
|
||||
|
||||
using std::string;
|
||||
|
||||
constexpr int ipv4 = 4;
|
||||
constexpr int ipv6 = 6;
|
||||
|
||||
constexpr int policy_file = 0;
|
||||
constexpr int policy_vector = 1;
|
||||
constexpr int policy_content = 2;
|
||||
|
||||
constexpr int length_header = 256;
|
||||
constexpr int length_vector = 256 * 256 * 8;
|
||||
|
||||
extern int ip_version; // ip 版本
|
||||
extern int ip_size; // ip 占的字节数
|
||||
extern int content_size;
|
||||
|
||||
void init_xdb(int version);
|
||||
|
||||
void log_exit(const string &msg);
|
||||
|
||||
void read_bin(int index, char *buf, size_t len, FILE *db);
|
||||
|
||||
unsigned to_uint(const char *buf);
|
||||
unsigned to_ushort(const char *buf);
|
||||
unsigned to_int(const char *buf, int n);
|
||||
|
||||
void write_uint(unsigned data, char buf[]);
|
||||
void write_uint(unsigned data, FILE *dst);
|
||||
|
||||
void write_ushort(unsigned data, char buf[]);
|
||||
void write_ushort(unsigned data, FILE *dst);
|
||||
|
||||
void write_string(const char *buf, unsigned len, FILE *dst);
|
||||
|
||||
unsigned long long get_time();
|
||||
|
||||
} // namespace xdb
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
|
||||
#include "bench.h"
|
||||
|
||||
namespace xdb {
|
||||
|
||||
bench_t::bench_t(const std::string &file_name, int version, int policy)
|
||||
: search(file_name, version, policy) {
|
||||
}
|
||||
|
||||
void bench_t::test_one(const ip_t &ip, const string region) {
|
||||
if (search.search(ip.to_string()) != region)
|
||||
xdb::log_exit("failed: " + ip.to_string() + " " + region);
|
||||
sum_io_count += search.get_io_count();
|
||||
sum_cost_time += search.get_cost_time();
|
||||
sum_count++;
|
||||
}
|
||||
|
||||
void bench_t::test_line(char *buf) {
|
||||
size_t buf_len = strlen(buf);
|
||||
if (buf_len == 0)
|
||||
return;
|
||||
buf[buf_len - 1] = '\0'; // 去掉换行符
|
||||
|
||||
node_t node(buf);
|
||||
|
||||
// 只测五个
|
||||
for (int i = 0; i < 5 && node.ip1 < node.ip2; ++i) {
|
||||
test_one(node.ip1, node.region);
|
||||
node.ip1 = node.ip1 + 1;
|
||||
}
|
||||
test_one(node.ip2, node.region);
|
||||
}
|
||||
|
||||
void bench_t::test_file(const std::string &file_name) {
|
||||
FILE *f = fopen(file_name.data(), "r");
|
||||
if (f == NULL)
|
||||
xdb::log_exit("can't open " + file_name);
|
||||
char buf[1024];
|
||||
while (fgets(buf, sizeof(buf), f) != NULL)
|
||||
test_line(buf);
|
||||
}
|
||||
|
||||
void bench_t::test(const string &file_name) {
|
||||
sum_io_count = 0;
|
||||
sum_cost_time = 0;
|
||||
sum_count = 0;
|
||||
|
||||
unsigned long long tv1 = xdb::get_time();
|
||||
test_file(file_name);
|
||||
unsigned long long tv2 = xdb::get_time();
|
||||
|
||||
double took = (tv2 - tv1) * 1.0 / 1000 / 1000;
|
||||
double cost = sum_cost_time * 1.0 / sum_count;
|
||||
|
||||
printf(
|
||||
"total: %llu, took: %8.2fs, cost: %6.2fμs/op, io "
|
||||
"count: "
|
||||
"%llu\n",
|
||||
sum_count,
|
||||
took,
|
||||
cost,
|
||||
sum_io_count);
|
||||
}
|
||||
|
||||
} // namespace xdb
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
#ifndef BENCH_H
|
||||
#define BENCH_H
|
||||
|
||||
#include "search.h"
|
||||
|
||||
namespace xdb {
|
||||
|
||||
class bench_t {
|
||||
public:
|
||||
bench_t(const string &file_name, int version, int policy);
|
||||
|
||||
void test(const string &file_name);
|
||||
|
||||
private:
|
||||
void test_one(const ip_t &ip, const string region);
|
||||
void test_line(char *buf);
|
||||
void test_file(const std::string &file_name);
|
||||
|
||||
search_t search;
|
||||
|
||||
unsigned long long sum_io_count;
|
||||
unsigned long long sum_cost_time;
|
||||
unsigned long long sum_count;
|
||||
};
|
||||
|
||||
} // namespace xdb
|
||||
#endif
|
||||
|
|
@ -0,0 +1,170 @@
|
|||
|
||||
#include "edit.h"
|
||||
|
||||
namespace xdb {
|
||||
|
||||
void handle_ip_txt(const string& name, std::list<node_t>& regions) {
|
||||
FILE* f = fopen(name.data(), "r");
|
||||
if (f == NULL)
|
||||
log_exit("can't open " + name);
|
||||
|
||||
char buf[1024];
|
||||
while (fgets(buf, sizeof(buf), f) != NULL) {
|
||||
unsigned int buf_len = strlen(buf);
|
||||
// 去掉多余的空
|
||||
while (buf_len > 0 && isspace(buf[buf_len - 1]))
|
||||
--buf_len;
|
||||
if (buf_len == 0)
|
||||
continue;
|
||||
buf[buf_len] = '\0';
|
||||
regions.push_back(node_t(buf));
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
void edit_t::handle_new_file(const std::string& file_name) {
|
||||
handle_ip_txt(file_name, new_regions); // 输入
|
||||
new_regions.sort(); // 排序
|
||||
|
||||
// 检验及其去重
|
||||
auto it = new_regions.begin();
|
||||
|
||||
for (;;) {
|
||||
if (it == new_regions.end())
|
||||
break;
|
||||
auto next = it;
|
||||
++next;
|
||||
if (next == new_regions.end())
|
||||
break;
|
||||
if (it->ip1 > it->ip2)
|
||||
it = new_regions.erase(it); // 非法, 直接跳过
|
||||
else if (it->ip1 == next->ip1 || next->ip1 <= it->ip2) {
|
||||
// 数据重叠
|
||||
if (it->region != next->region)
|
||||
log_exit("数据有二义性: " + it->to_string() + ", " +
|
||||
next->to_string());
|
||||
it->ip2 = std::max(it->ip2, next->ip2);
|
||||
new_regions.erase(next);
|
||||
} else if (it->ip2 + 1 == next->ip1 && it->region == next->region) {
|
||||
// 数据连接
|
||||
it->ip2 = next->ip2;
|
||||
new_regions.erase(next);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void edit_t::handle_old_file(const std::string& file_name) {
|
||||
handle_ip_txt(file_name, old_regions);
|
||||
}
|
||||
|
||||
void edit_t::merge() {
|
||||
auto it1 = old_regions.begin();
|
||||
auto it2 = new_regions.begin();
|
||||
|
||||
for (;;) {
|
||||
if (it2 == new_regions.end())
|
||||
break;
|
||||
if (it2->ip1 > it2->ip2) {
|
||||
++it2;
|
||||
continue;
|
||||
}
|
||||
// it1->ip1 it1->ip2 it2->ip1 it2->ip2
|
||||
while (it1->ip2 < it2->ip1)
|
||||
++it1;
|
||||
if (it1->ip2 <= it2->ip2) {
|
||||
// it1->ip1 it2->ip1 it1->ip2 it2->ip2
|
||||
node_t node;
|
||||
node.ip1 = it2->ip1;
|
||||
node.ip2 = it1->ip2;
|
||||
node.region = it2->region;
|
||||
|
||||
it1->ip2 = node.ip1 - 1;
|
||||
it2->ip1 = node.ip2 + 1;
|
||||
|
||||
++it1;
|
||||
it1 = old_regions.insert(it1, node);
|
||||
++it1;
|
||||
} else {
|
||||
// it1->ip1 it2->ip1 it2->ip2 it1->ip2
|
||||
node_t node;
|
||||
node.ip1 = it2->ip2 + 1;
|
||||
node.ip2 = it1->ip2;
|
||||
node.region = it1->region;
|
||||
|
||||
it1->ip2 = it2->ip1 - 1;
|
||||
|
||||
++it1;
|
||||
it1 = old_regions.insert(it1, *it2);
|
||||
|
||||
++it1;
|
||||
it1 = old_regions.insert(it1, node);
|
||||
|
||||
++it2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void edit_t::write_old_file(const std::string& file_name) {
|
||||
FILE* f = fopen(file_name.data(), "w");
|
||||
if (f == NULL)
|
||||
log_exit("can't open " + file_name);
|
||||
|
||||
auto it = old_regions.begin();
|
||||
|
||||
// 删除非法的数据
|
||||
for (;;) {
|
||||
if (it == old_regions.end())
|
||||
break;
|
||||
if (it->ip1 > it->ip2)
|
||||
it = old_regions.erase(it);
|
||||
else
|
||||
++it;
|
||||
}
|
||||
|
||||
// 合并数据域相同的相邻数据
|
||||
it = old_regions.begin();
|
||||
for (;;) {
|
||||
if (it == old_regions.end())
|
||||
break;
|
||||
auto next = it;
|
||||
++next;
|
||||
if (next == old_regions.end())
|
||||
break;
|
||||
if (it->region == next->region) {
|
||||
it->ip2 = next->ip2;
|
||||
old_regions.erase(next);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& d : old_regions) {
|
||||
string res =
|
||||
d.ip1.to_string() + "|" + d.ip2.to_string() + "|" + d.region + "\n";
|
||||
fputs(res.data(), f);
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
edit_t::edit_t(const string& name_old, const string& name_new, int version) {
|
||||
unsigned long long tv1 = get_time();
|
||||
|
||||
init_xdb(version);
|
||||
|
||||
handle_new_file(name_new);
|
||||
handle_old_file(name_old);
|
||||
merge();
|
||||
write_old_file(name_old);
|
||||
|
||||
unsigned long long tv2 = get_time();
|
||||
|
||||
double took = (tv2 - tv1) * 1.0 / 1000 / 1000;
|
||||
|
||||
printf("took: %.2fs\n", took);
|
||||
}
|
||||
|
||||
} // namespace xdb
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
#ifndef EDIT_H
|
||||
#define EDIT_H
|
||||
|
||||
#include "ip.h"
|
||||
|
||||
namespace xdb {
|
||||
|
||||
class edit_t {
|
||||
public:
|
||||
edit_t(const string& old_name, const string& new_name, int version);
|
||||
|
||||
private:
|
||||
void handle_new_file(const string& file_name);
|
||||
void handle_old_file(const string& file_name);
|
||||
void merge();
|
||||
void write_old_file(const string& file_name);
|
||||
|
||||
std::list<node_t> old_regions;
|
||||
std::list<node_t> new_regions;
|
||||
};
|
||||
|
||||
} // namespace xdb
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
|
||||
#include "header.h"
|
||||
|
||||
namespace xdb {
|
||||
|
||||
header_t::header_t(FILE* db) {
|
||||
read_bin(0, header, sizeof(header), db);
|
||||
}
|
||||
|
||||
header_t::~header_t() {
|
||||
}
|
||||
|
||||
int header_t::version() {
|
||||
return to_int(header, 2); // 版本号(2)
|
||||
}
|
||||
|
||||
int header_t::index_policy() {
|
||||
return to_int(header + 2, 2); // 缓存策略(2)
|
||||
}
|
||||
|
||||
int header_t::create_at() {
|
||||
return to_int(header + 4, 4); // 文件生成时间(4)
|
||||
}
|
||||
|
||||
int header_t::index_start() {
|
||||
return to_int(header + 8, 4); // 索引起始地址(4)
|
||||
}
|
||||
|
||||
int header_t::index_end() {
|
||||
return to_int(header + 12, 4); // 索引结束地址(4)
|
||||
}
|
||||
|
||||
int header_t::ip_version() {
|
||||
return to_int(header + 16, 2); // IP 版本(2)
|
||||
}
|
||||
|
||||
int header_t::ptr() {
|
||||
return to_int(header + 18, 2); // 指针字节数(2)
|
||||
}
|
||||
|
||||
} // namespace xdb
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
#ifndef HEADER_H
|
||||
#define HEADER_H
|
||||
|
||||
#include "base.h"
|
||||
|
||||
namespace xdb {
|
||||
|
||||
class header_t {
|
||||
public:
|
||||
header_t(FILE* db);
|
||||
virtual ~header_t();
|
||||
|
||||
int version(); // 版本号
|
||||
int index_policy(); // 缓存策略
|
||||
int create_at(); // 文件生成时间
|
||||
int index_start(); // 索引起始地址
|
||||
int index_end(); // 索引结束地址
|
||||
int ip_version(); // IP 版本
|
||||
int ptr(); // 指针字节数
|
||||
|
||||
protected:
|
||||
char header[length_header];
|
||||
};
|
||||
|
||||
} // namespace xdb
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,158 @@
|
|||
|
||||
#include "ip.h"
|
||||
|
||||
namespace xdb {
|
||||
|
||||
ip_t::ip_t() {
|
||||
memset(p, '\0', sizeof(p));
|
||||
}
|
||||
|
||||
ip_t::ip_t(const ip_t& rhs, int val) {
|
||||
memcpy(p, rhs.p, ip_size);
|
||||
|
||||
if (val == 0 || val == 255)
|
||||
for (int i = 2; i < ip_size; ++i)
|
||||
p[i] = val;
|
||||
}
|
||||
|
||||
ip_t::ip_t(const char* p) {
|
||||
from_xdb(p);
|
||||
}
|
||||
|
||||
bool ip_t::from_str(const string& str) {
|
||||
int af_inet = ip_version == ipv4 ? AF_INET : AF_INET6;
|
||||
return inet_pton(af_inet, str.data(), p) == 1;
|
||||
}
|
||||
|
||||
void ip_t::from_xdb(const char str[16]) {
|
||||
for (int i = 0; i < ip_size; ++i)
|
||||
if (ip_version == ipv6)
|
||||
p[i] = str[i];
|
||||
else
|
||||
p[i] = str[ip_size - 1 - i];
|
||||
}
|
||||
|
||||
ip_t& ip_t::operator=(const ip_t& rhs) {
|
||||
memcpy(p, rhs.p, ip_size);
|
||||
return *this;
|
||||
}
|
||||
|
||||
int ip_t::compare(const ip_t& rhs) const {
|
||||
for (int i = 0; i < ip_size; ++i) {
|
||||
if ((unsigned char)p[i] > (unsigned char)rhs.p[i])
|
||||
return 1;
|
||||
if ((unsigned char)p[i] < (unsigned char)rhs.p[i])
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool ip_t::operator<(const ip_t& rhs) const {
|
||||
return compare(rhs) < 0;
|
||||
}
|
||||
|
||||
bool ip_t::operator<=(const ip_t& rhs) const {
|
||||
return compare(rhs) <= 0;
|
||||
}
|
||||
|
||||
bool ip_t::operator>(const ip_t& rhs) const {
|
||||
return compare(rhs) > 0;
|
||||
}
|
||||
|
||||
bool ip_t::operator>=(const ip_t& rhs) const {
|
||||
return compare(rhs) >= 0;
|
||||
}
|
||||
|
||||
bool ip_t::operator==(const ip_t& rhs) const {
|
||||
return compare(rhs) == 0;
|
||||
}
|
||||
|
||||
bool ip_t::operator!=(const ip_t& rhs) const {
|
||||
return compare(rhs) != 0;
|
||||
}
|
||||
|
||||
string ip_t::to_string() const {
|
||||
char buf[INET6_ADDRSTRLEN + 1];
|
||||
int af_inet = ip_version == ipv4 ? AF_INET : AF_INET6;
|
||||
inet_ntop(af_inet, p, buf, sizeof(buf));
|
||||
return string(buf);
|
||||
}
|
||||
|
||||
string ip_t::to_bit() const {
|
||||
string str;
|
||||
for (int i = 0; i < ip_size; ++i)
|
||||
if (ip_version == ipv6)
|
||||
str.push_back(p[i]);
|
||||
else
|
||||
str.push_back(p[ip_size - 1 - i]);
|
||||
return str;
|
||||
}
|
||||
|
||||
ip_t operator+(const ip_t& lhs, int v) {
|
||||
ip_t ip;
|
||||
|
||||
int i = ip_size;
|
||||
while (--i >= 0) {
|
||||
v += lhs.p[i];
|
||||
ip.p[i] = v % 256;
|
||||
v /= 256;
|
||||
}
|
||||
return ip;
|
||||
}
|
||||
|
||||
ip_t operator-(const ip_t& lhs, int v) {
|
||||
ip_t ip;
|
||||
|
||||
int i = ip_size;
|
||||
v = -v;
|
||||
while (--i >= 0) {
|
||||
v += lhs.p[i];
|
||||
if (v == -1)
|
||||
ip.p[i] = 255;
|
||||
else {
|
||||
ip.p[i] = v;
|
||||
v = 0;
|
||||
}
|
||||
}
|
||||
return ip;
|
||||
}
|
||||
|
||||
// node_t
|
||||
node_t::node_t() {
|
||||
}
|
||||
|
||||
node_t::node_t(char* buf) {
|
||||
char* pos1 = strchr(buf, '|');
|
||||
|
||||
if (pos1 == NULL)
|
||||
log_exit("invalid data: " + std::string(buf));
|
||||
char* pos2 = strchr(pos1 + 1, '|');
|
||||
if (pos2 == NULL)
|
||||
log_exit("invalid data: " + std::string(buf));
|
||||
*pos1 = '\0';
|
||||
*pos2 = '\0';
|
||||
|
||||
region = pos2 + 1;
|
||||
|
||||
if (!ip1.from_str(buf) || !ip2.from_str(pos1 + 1) || ip2 < ip1 ||
|
||||
region.empty()) {
|
||||
*pos1 = *pos2 = '|';
|
||||
log_exit(string("invalid data: ") + buf);
|
||||
}
|
||||
}
|
||||
|
||||
bool node_t::operator<(const node_t& rhs) const {
|
||||
if (ip1 < rhs.ip1)
|
||||
return true;
|
||||
return ip2 < rhs.ip2;
|
||||
}
|
||||
|
||||
string node_t::to_string() const {
|
||||
return ip1.to_string() + "|" + ip2.to_string() + "|" + region;
|
||||
}
|
||||
|
||||
string node_t::to_bit() const {
|
||||
return ip1.to_bit() + ip2.to_bit();
|
||||
}
|
||||
|
||||
} // namespace xdb
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
#ifndef IP_H
|
||||
#define IP_H
|
||||
|
||||
#include "base.h"
|
||||
|
||||
namespace xdb {
|
||||
|
||||
struct ip_t {
|
||||
unsigned char p[16];
|
||||
|
||||
ip_t();
|
||||
ip_t(const char* p);
|
||||
// val 为 0 或 255 时, 将 ip 的后几位置为 val
|
||||
ip_t(const ip_t& rhs, int val = -1);
|
||||
|
||||
bool from_str(const string& str);
|
||||
void from_xdb(const char str[16]);
|
||||
|
||||
ip_t& operator=(const ip_t& rhs);
|
||||
|
||||
int compare(const ip_t& rhs) const;
|
||||
bool operator<(const ip_t& rhs) const;
|
||||
bool operator<=(const ip_t& rhs) const;
|
||||
bool operator>(const ip_t& rhs) const;
|
||||
bool operator>=(const ip_t& rhs) const;
|
||||
bool operator==(const ip_t& rhs) const;
|
||||
bool operator!=(const ip_t& rhs) const;
|
||||
|
||||
string to_string() const;
|
||||
string to_bit() const;
|
||||
};
|
||||
|
||||
ip_t operator+(const ip_t& lhs, int v);
|
||||
ip_t operator-(const ip_t& lhs, int v);
|
||||
|
||||
struct node_t {
|
||||
ip_t ip1;
|
||||
ip_t ip2;
|
||||
string region;
|
||||
|
||||
node_t();
|
||||
node_t(char* buf);
|
||||
|
||||
bool operator<(const node_t& rhs) const;
|
||||
|
||||
string to_string() const;
|
||||
string to_bit() const;
|
||||
};
|
||||
|
||||
} // namespace xdb
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,167 @@
|
|||
|
||||
#include "make.h"
|
||||
|
||||
namespace xdb {
|
||||
|
||||
void make_t::vector_index_push_back(int row, int col, const node_t &node) {
|
||||
vector_index[row][col].push_back(
|
||||
std::make_pair<string, string>(node.to_bit(), string(node.region)));
|
||||
}
|
||||
|
||||
void make_t::vector_index_push_back(node_t &node) {
|
||||
ip_t ip1 = node.ip1;
|
||||
ip_t ip2 = node.ip2;
|
||||
|
||||
unsigned ip1_1 = ip1.p[0];
|
||||
unsigned ip1_2 = ip1.p[1];
|
||||
unsigned ip2_1 = ip2.p[0];
|
||||
unsigned ip2_2 = ip2.p[1];
|
||||
|
||||
if (ip1_1 == ip2_1 && ip1_2 == ip2_2) {
|
||||
vector_index_push_back(ip1_1, ip1_2, node);
|
||||
return;
|
||||
}
|
||||
|
||||
node.ip1 = ip1;
|
||||
node.ip2 = ip_t(ip1, 255);
|
||||
vector_index_push_back(ip1_1, ip1_2, node);
|
||||
|
||||
node.ip1 = ip_t(ip2, 0);
|
||||
node.ip2 = ip2;
|
||||
vector_index_push_back(ip2_1, ip2_2, node);
|
||||
|
||||
for (;;) {
|
||||
++ip1_2;
|
||||
if (ip1_2 == 256) {
|
||||
++ip1_1;
|
||||
ip1_2 = 0;
|
||||
}
|
||||
if (ip1_1 == ip2_1 && ip1_2 == ip2_2)
|
||||
break;
|
||||
ip1.p[0] = ip1_1;
|
||||
ip1.p[1] = ip1_2;
|
||||
node.ip1 = ip_t(ip1, 0);
|
||||
node.ip2 = ip_t(ip1, 255);
|
||||
vector_index_push_back(ip1_1, ip1_2, node);
|
||||
}
|
||||
}
|
||||
|
||||
void make_t::handle_input_help(char *buf) {
|
||||
// 去掉多余的空
|
||||
unsigned int buf_len = strlen(buf);
|
||||
while (buf_len > 0 && isspace(buf[buf_len - 1]))
|
||||
--buf_len;
|
||||
if (buf_len == 0)
|
||||
return;
|
||||
buf[buf_len] = '\0';
|
||||
|
||||
node_t node(buf);
|
||||
|
||||
if (node.ip1 < next_ip) {
|
||||
log_exit("ip 未排序: " + node.ip1.to_string() + ", " +
|
||||
next_ip.to_string());
|
||||
}
|
||||
|
||||
next_ip = node.ip2 + 1;
|
||||
|
||||
if (region.find(node.region) == region.end()) {
|
||||
region[node.region] = region_index;
|
||||
region_index += node.region.size();
|
||||
}
|
||||
|
||||
vector_index_push_back(node);
|
||||
}
|
||||
|
||||
void make_t::handle_input(const std::string &file_name) {
|
||||
FILE *src = fopen(file_name.data(), "r");
|
||||
if (src == NULL)
|
||||
log_exit("can't open " + file_name);
|
||||
|
||||
char buf[1024];
|
||||
while (fgets(buf, sizeof(buf), src) != NULL)
|
||||
handle_input_help(buf);
|
||||
fclose(src);
|
||||
}
|
||||
|
||||
void make_t::handle_header() {
|
||||
char buf[length_header];
|
||||
memset(buf, 0, length_header);
|
||||
write_ushort(3, buf); // 版本号
|
||||
write_ushort(1, buf + 2); // 缓存策略
|
||||
write_uint(time(NULL), buf + 4); // 时间
|
||||
// 索引
|
||||
unsigned int content_left = length_header + length_vector;
|
||||
for (auto &d : region)
|
||||
content_left += d.first.size();
|
||||
unsigned int content_right = content_left;
|
||||
|
||||
for (int i = 0; i < 256; ++i)
|
||||
for (int j = 0; j < 256; ++j)
|
||||
content_right += vector_index[i][j].size() * content_size;
|
||||
content_right -= content_size;
|
||||
write_uint(content_left, buf + 8);
|
||||
write_uint(content_right, buf + 12);
|
||||
write_ushort(ip_version, buf + 16); // IP
|
||||
write_ushort(4, buf + 18); // 指针数
|
||||
|
||||
write_string(buf, length_header, db);
|
||||
}
|
||||
|
||||
void make_t::handle_vector_index() {
|
||||
unsigned index = length_header + length_vector;
|
||||
for (auto &d : region)
|
||||
index += d.first.size();
|
||||
for (unsigned i = 0; i < 256; ++i)
|
||||
for (unsigned j = 0; j < 256; ++j)
|
||||
if (vector_index[i][j].size() == 0) {
|
||||
write_uint(0, db);
|
||||
write_uint(0, db);
|
||||
} else {
|
||||
write_uint(index, db);
|
||||
index += content_size * vector_index[i][j].size();
|
||||
write_uint(index, db);
|
||||
}
|
||||
}
|
||||
|
||||
void make_t::handle_region() {
|
||||
for (auto &d : region) {
|
||||
fseek(db, d.second, SEEK_SET);
|
||||
write_string(d.first.data(), d.first.size(), db);
|
||||
}
|
||||
}
|
||||
|
||||
void make_t::handle_content() {
|
||||
fseek(db, 0, SEEK_END);
|
||||
for (unsigned i = 0; i < 256; ++i)
|
||||
for (unsigned j = 0; j < 256; ++j)
|
||||
for (auto d : vector_index[i][j]) {
|
||||
write_string(d.first.data(), d.first.size(), db);
|
||||
write_ushort(d.second.size(), db);
|
||||
write_uint(region[d.second], db);
|
||||
}
|
||||
}
|
||||
|
||||
make_t::make_t(const string &src, const string &dst, int version)
|
||||
: region_index(length_vector + length_header) {
|
||||
unsigned long long tv1 = get_time();
|
||||
|
||||
init_xdb(version);
|
||||
|
||||
handle_input(src);
|
||||
|
||||
db = fopen(dst.data(), "w");
|
||||
if (db == NULL)
|
||||
log_exit("can't open " + dst);
|
||||
|
||||
handle_header();
|
||||
handle_vector_index();
|
||||
handle_region();
|
||||
handle_content();
|
||||
|
||||
fclose(db);
|
||||
|
||||
unsigned long long tv2 = get_time();
|
||||
printf("took: %.2fs\n", (tv2 - tv1) * 1.0 / 1000 / 1000);
|
||||
}
|
||||
|
||||
} // namespace xdb
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
#ifndef MAKE_H
|
||||
#define MAKE_H
|
||||
|
||||
#include "ip.h"
|
||||
|
||||
namespace xdb {
|
||||
|
||||
class make_t {
|
||||
public:
|
||||
make_t(const string &src, const string &dst, int version);
|
||||
|
||||
private:
|
||||
void vector_index_push_back(int row, int col, const node_t &node);
|
||||
void vector_index_push_back(node_t &node);
|
||||
void handle_input_help(char buf[]);
|
||||
void handle_input(const std::string &file_name);
|
||||
|
||||
void handle_header();
|
||||
void handle_vector_index();
|
||||
void handle_region();
|
||||
void handle_content();
|
||||
|
||||
FILE *db = NULL;
|
||||
|
||||
std::vector<std::pair<string, string>> vector_index[256][256];
|
||||
|
||||
std::unordered_map<string, unsigned> region;
|
||||
|
||||
unsigned region_index;
|
||||
ip_t next_ip;
|
||||
};
|
||||
|
||||
} // namespace xdb
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,137 @@
|
|||
|
||||
#include "search.h"
|
||||
|
||||
namespace xdb {
|
||||
|
||||
search_t::search_t(const string &file, int version, int p)
|
||||
: db(fopen(file.data(), "r")), header(db), policy(p) {
|
||||
init_xdb(version);
|
||||
|
||||
if (db == NULL)
|
||||
log_exit("can't open " + file);
|
||||
if (header.ip_version() != version)
|
||||
log_exit("ip 版本不匹配");
|
||||
|
||||
if (policy != policy_file) {
|
||||
read_bin(length_header, vector, length_vector, db);
|
||||
if (policy == policy_content) {
|
||||
fseek(db, 0, SEEK_END);
|
||||
int size = ftell(db) - length_vector - length_header;
|
||||
content = (char *)malloc(size);
|
||||
read_bin(length_vector + length_header, content, size, db);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
search_t::~search_t() {
|
||||
fclose(db);
|
||||
if (policy == policy_content)
|
||||
free(content);
|
||||
}
|
||||
|
||||
int search_t::get_io_count() {
|
||||
return io_count;
|
||||
}
|
||||
|
||||
int search_t::get_cost_time() {
|
||||
return cost_time;
|
||||
}
|
||||
|
||||
char const *search_t::get_content_index_help(int index) {
|
||||
if (policy != policy_file)
|
||||
return vector + index;
|
||||
|
||||
++io_count;
|
||||
static char v[8];
|
||||
read_bin(length_header + index, v, sizeof(v), db);
|
||||
return v;
|
||||
}
|
||||
|
||||
void search_t::get_content_index(const ip_t &ip, int &left, int &right) {
|
||||
int index = ((unsigned char)ip.p[0] * 256 + (unsigned char)ip.p[1]) * 8;
|
||||
|
||||
const char *p = get_content_index_help(index);
|
||||
left = to_uint(p);
|
||||
right = to_uint(p + 4);
|
||||
}
|
||||
|
||||
char const *search_t::get_content_help(int index) {
|
||||
if (policy == policy_content)
|
||||
return content + index - length_header - length_vector;
|
||||
++io_count;
|
||||
static char v[16 + 16 + 2 + 4];
|
||||
read_bin(index, v, content_size, db);
|
||||
return v;
|
||||
}
|
||||
|
||||
string search_t::get_region(int index, int len) {
|
||||
if (policy == policy_content)
|
||||
return string(content + index - length_header - length_vector, len);
|
||||
++io_count;
|
||||
char *p = (char *)malloc(sizeof(char) * len);
|
||||
read_bin(index, p, len, db);
|
||||
string res(p, len);
|
||||
free(p);
|
||||
return res;
|
||||
}
|
||||
|
||||
void search_t::get_content(int index,
|
||||
ip_t &ip_left,
|
||||
ip_t &ip_right,
|
||||
int ®ion_len,
|
||||
int ®ion_index) {
|
||||
const char *p = get_content_help(index);
|
||||
|
||||
ip_left.from_xdb(p);
|
||||
ip_right.from_xdb(p + ip_size);
|
||||
|
||||
region_len = to_ushort(p + ip_size * 2);
|
||||
region_index = to_uint(p + ip_size * 2 + 2);
|
||||
}
|
||||
|
||||
string search_t::search(const ip_t &ip) {
|
||||
io_count = 0;
|
||||
|
||||
int content_left, content_right;
|
||||
get_content_index(ip, content_left, content_right);
|
||||
|
||||
if (content_left == 0 || content_right == 0)
|
||||
return "";
|
||||
|
||||
ip_t ip_left, ip_right;
|
||||
int region_len;
|
||||
int region_index;
|
||||
|
||||
int left = 0;
|
||||
int right = (content_right - content_left) / content_size;
|
||||
|
||||
for (;;) {
|
||||
int mid = left + (right - left) / 2;
|
||||
int mid_index = content_left + mid * content_size;
|
||||
get_content(mid_index, ip_left, ip_right, region_len, region_index);
|
||||
|
||||
// ip ip_left ip_right
|
||||
if (ip < ip_left)
|
||||
right = mid - 1;
|
||||
// ip_left ip_right ip
|
||||
else if (ip_right < ip)
|
||||
left = mid + 1;
|
||||
else
|
||||
return get_region(region_index, region_len);
|
||||
}
|
||||
}
|
||||
|
||||
string search_t::search(const string &str) {
|
||||
unsigned long long t1 = get_time();
|
||||
|
||||
ip_t ip;
|
||||
if (ip.from_str(str) == false)
|
||||
return "invalid ipv" + std::to_string(ip_version) + ": " + str;
|
||||
string region = search(ip);
|
||||
|
||||
unsigned long long t2 = get_time();
|
||||
cost_time = t2 - t1;
|
||||
return region;
|
||||
}
|
||||
|
||||
} // namespace xdb
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
#ifndef SEARCH_H
|
||||
#define SEARCH_H
|
||||
|
||||
#include "header.h"
|
||||
#include "ip.h"
|
||||
|
||||
namespace xdb {
|
||||
|
||||
class search_t {
|
||||
protected:
|
||||
FILE *db;
|
||||
|
||||
header_t header;
|
||||
|
||||
int policy;
|
||||
|
||||
int io_count;
|
||||
int cost_time;
|
||||
|
||||
char vector[length_vector];
|
||||
char *content;
|
||||
|
||||
public:
|
||||
search_t(const string &file_name, int version, int policy);
|
||||
virtual ~search_t();
|
||||
|
||||
int get_io_count();
|
||||
int get_cost_time();
|
||||
|
||||
string search(const string &str);
|
||||
|
||||
protected:
|
||||
string search(const ip_t &ip);
|
||||
|
||||
void get_content_index(const ip_t &ip1, int &left, int &right);
|
||||
void get_content(int index,
|
||||
ip_t &left,
|
||||
ip_t &right,
|
||||
int ®ion_len,
|
||||
int ®ion_index);
|
||||
|
||||
char const *get_content_index_help(int index);
|
||||
char const *get_content_help(int index);
|
||||
string get_region(int index, int len);
|
||||
};
|
||||
|
||||
} // namespace xdb
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
|
||||
#include "../src/bench.h"
|
||||
|
||||
std::map<int, std::string> prompt;
|
||||
|
||||
void test_ipv4(int policy) {
|
||||
std::cout << "测试 IPv4, " << prompt[policy];
|
||||
xdb::bench_t("../../data/ip2region_v4.xdb", xdb::ipv4, policy)
|
||||
.test("../../data/ipv4_source.txt");
|
||||
}
|
||||
|
||||
void test_ipv6(int policy) {
|
||||
std::cout << "测试 IPv6, " << prompt[policy];
|
||||
xdb::bench_t("../../data/ip2region_v6.xdb", xdb::ipv6, policy)
|
||||
.test("../../data/ipv6_source.txt");
|
||||
}
|
||||
|
||||
int main() {
|
||||
prompt[xdb::policy_file] = " 不缓存, ";
|
||||
prompt[xdb::policy_vector] = "部分缓存, ";
|
||||
prompt[xdb::policy_content] = "全部缓存, ";
|
||||
|
||||
test_ipv4(xdb::policy_file);
|
||||
test_ipv4(xdb::policy_vector);
|
||||
test_ipv4(xdb::policy_content);
|
||||
|
||||
test_ipv6(xdb::policy_file);
|
||||
test_ipv6(xdb::policy_vector);
|
||||
test_ipv6(xdb::policy_content);
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
|
||||
#include "../src/edit.h"
|
||||
|
||||
int main() {
|
||||
std::string file_name_old = "../../data/ipv4_source.txt";
|
||||
std::string file_name_new = "./1.txt";
|
||||
xdb::edit_t xdb(file_name_old, file_name_new, xdb::ipv4);
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
|
||||
#include "../src/edit.h"
|
||||
|
||||
int main() {
|
||||
std::string file_name_old = "../../data/ipv6_source.txt";
|
||||
std::string file_name_new = "./1.txt";
|
||||
xdb::edit_t xdb(file_name_old, file_name_new, xdb::ipv6);
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
|
||||
#include "../src/header.h"
|
||||
|
||||
void test(const std::string& prompt, const std::string& file_name) {
|
||||
std::cout << prompt << std::endl;
|
||||
|
||||
xdb::header_t head(fopen(file_name.data(), "r"));
|
||||
|
||||
std::cout << "版本号: " << head.version() << std::endl;
|
||||
std::cout << "缓存策略: " << head.index_policy() << std::endl;
|
||||
|
||||
time_t rawtime = head.create_at();
|
||||
struct tm* info = localtime(&rawtime);
|
||||
char buf[80];
|
||||
strftime(buf, 80, "%Y-%m-%d %H:%M:%S", info);
|
||||
|
||||
std::cout << "文件生成时间: " << buf << std::endl;
|
||||
std::cout << "索引起始地址: " << head.index_start() << std::endl;
|
||||
std::cout << "索引结束地址: " << head.index_end() << std::endl;
|
||||
std::cout << "IP版本: " << head.ip_version() << std::endl;
|
||||
std::cout << "指针字节数: " << head.ptr() << std::endl;
|
||||
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
int main() {
|
||||
test("测试 IPv4", "../../data/ip2region_v4.xdb");
|
||||
test("测试 IPv6", "../../data/ip2region_v6.xdb");
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
|
||||
#include "../src/make.h"
|
||||
|
||||
void test(const std::string& prompt,
|
||||
const std::string& filename_xdb,
|
||||
const std::string& filename_src,
|
||||
int version
|
||||
|
||||
) {
|
||||
std::cout << prompt;
|
||||
xdb::make_t(filename_xdb, filename_src, version);
|
||||
}
|
||||
|
||||
int main() {
|
||||
test("生成 ipv4 的 xdb 文件, ",
|
||||
"../../data/ipv4_source.txt",
|
||||
"./ip2region_v4.xdb",
|
||||
xdb::ipv4);
|
||||
|
||||
test("生成 ipv6 的 xdb 文件, ",
|
||||
"../../data/ipv6_source.txt",
|
||||
"./ip2region_v6.xdb",
|
||||
xdb::ipv6);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,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;
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
root = true
|
||||
|
||||
# To learn more about .editorconfig see https://aka.ms/editorconfigdocs
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
spelling_exclusion_path = .\exclusion.dic
|
||||
|
||||
[*.csproj]
|
||||
charset = utf-8
|
||||
insert_final_newline = true
|
||||
|
||||
[*.{xml,config,csproj,nuspec,props,resx,targets,yml,tasks,json}]
|
||||
indent_size = 2
|
||||
|
||||
[*.sh]
|
||||
end_of_line = lf
|
||||
|
||||
[*.cs]
|
||||
csharp_style_namespace_declarations = file_scoped:silent
|
||||
|
||||
[*.cs]
|
||||
file_header_template = Copyright 2025 The Ip2Region Authors. All rights reserved.\nUse of this source code is governed by a Apache2.0-style\nlicense that can be found in the LICENSE file.\n@Author Alan <lzh.shap@gmail.com>\n@Date 2023/07/25\nUpdated by Argo Zhang <argo@live.ca> at 2025/11/21
|
||||
|
|
@ -0,0 +1,337 @@
|
|||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
##
|
||||
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
|
||||
|
||||
# User-specific files
|
||||
*.rsuser
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
[Aa][Rr][Mm]/
|
||||
[Aa][Rr][Mm]64/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
|
||||
# Visual Studio 2015/2017 cache/options directory
|
||||
.vs/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
# Visual Studio 2017 auto generated files
|
||||
Generated\ Files/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# NUNIT
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
# Benchmark Results
|
||||
BenchmarkDotNet.Artifacts/
|
||||
|
||||
# .NET Core
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
|
||||
# StyleCop
|
||||
StyleCopReport.xml
|
||||
|
||||
# Files built by Visual Studio
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_h.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.iobj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.ipdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*_wpftmp.csproj
|
||||
*.log
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# Visual Studio Trace Files
|
||||
*.e2e
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# JustCode is a .NET coding add-in
|
||||
.JustCode
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# AxoCover is a Code Coverage Tool
|
||||
.axoCover/*
|
||||
!.axoCover/settings.json
|
||||
|
||||
# Visual Studio code coverage results
|
||||
*.coverage
|
||||
*.coveragexml
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||
# in these scripts will be unencrypted
|
||||
PublishScripts/
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/[Pp]ackages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/[Pp]ackages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/[Pp]ackages/repositories.config
|
||||
# NuGet v3's project.json files produces more ignorable files
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Windows Store app package directories and files
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
*.appx
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!?*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.jfm
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
orleans.codegen.cs
|
||||
|
||||
# Including strong name files can present a security risk
|
||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||
#*.snk
|
||||
|
||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||
#bower_components/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
ServiceFabricBackup/
|
||||
*.rptproj.bak
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
*.ndf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
*.rptproj.rsuser
|
||||
*- Backup*.rdl
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
node_modules/
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||
*.vbw
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
|
||||
# Paket dependency manager
|
||||
.paket/paket.exe
|
||||
paket-files/
|
||||
|
||||
# FAKE - F# Make
|
||||
.fake/
|
||||
|
||||
# JetBrains Rider
|
||||
.idea/
|
||||
*.sln.iml
|
||||
|
||||
# CodeRush personal settings
|
||||
.cr/personal
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Cake - Uncomment if you are using it
|
||||
# tools/**
|
||||
# !tools/packages.config
|
||||
|
||||
# Tabs Studio
|
||||
*.tss
|
||||
|
||||
# Telerik's JustMock configuration file
|
||||
*.jmconfig
|
||||
|
||||
# BizTalk build output
|
||||
*.btp.cs
|
||||
*.btm.cs
|
||||
*.odx.cs
|
||||
*.xsd.cs
|
||||
|
||||
# OpenCover UI analysis results
|
||||
OpenCover/
|
||||
|
||||
# Azure Stream Analytics local run output
|
||||
ASALocalRun/
|
||||
|
||||
# MSBuild Binary and Structured Log
|
||||
*.binlog
|
||||
|
||||
# NVidia Nsight GPU debugger configuration file
|
||||
*.nvuser
|
||||
|
||||
# MFractors (Xamarin productivity tool) working folder
|
||||
.mfractor/
|
||||
|
||||
# Local History for Visual Studio
|
||||
.localhistory/
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [3.0.0] - 2025-11-22
|
||||
- 支持 .NET 10.0
|
||||
- 增加 IPv6 支持
|
||||
- 修复若干 bug
|
||||
|
||||
## [2.0.1] - 2023-07-30
|
||||
|
||||
### Added
|
||||
- Support netstandard2.0
|
||||
|
||||
## [2.0.0] - 2023-07-26
|
||||
|
||||
### Removed
|
||||
- Remove nuget include xdb file
|
||||
- Searcher cache policy default parameters
|
||||
- Searcher xdb file path default parameters
|
||||
|
||||
### Added
|
||||
- Dependent file query policies CachePolicy.VectorIndex, CachePolicy.File support thread-safe concurrent queries
|
||||
- Dramatically optimizes overall performance
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
// Copyright 2025 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
// @Author Alan <lzh.shap@gmail.com>
|
||||
// @Date 2023/07/25
|
||||
// Updated by Argo Zhang <argo@live.ca> at 2025/11/21
|
||||
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using IP2Region.Net.XDB;
|
||||
|
||||
namespace IP2Region.Net.BenchMark;
|
||||
|
||||
[MemoryDiagnoser]
|
||||
public class Benchmarks
|
||||
{
|
||||
private static readonly string XdbPathV4 = Path.Combine(AppContext.BaseDirectory, "IP2Region", "ip2region_v4.xdb");
|
||||
private static readonly string XdbPathV6 = Path.Combine(AppContext.BaseDirectory, "IP2Region", "ip2region_v6.xdb");
|
||||
private static readonly Searcher _contentV4Searcher = new(CachePolicy.Content, XdbPathV4);
|
||||
private static readonly Searcher _vectorV4Searcher = new(CachePolicy.VectorIndex, XdbPathV4);
|
||||
private static readonly Searcher _fileV4Searcher = new(CachePolicy.File, XdbPathV4);
|
||||
private static readonly Searcher _contentV6Searcher = new(CachePolicy.Content, XdbPathV6);
|
||||
private static readonly Searcher _vectorV6Searcher = new(CachePolicy.VectorIndex, XdbPathV6);
|
||||
private static readonly Searcher _fileV6Searcher = new(CachePolicy.File, XdbPathV6);
|
||||
|
||||
private readonly string _testIPv4Address = "114.114.114.114";
|
||||
private readonly string _testIPv6Address = "240e:3b7:3272:d8d0:db09:c067:8d59:539e";
|
||||
|
||||
public Benchmarks()
|
||||
{
|
||||
_contentV4Searcher.Search(_testIPv4Address);
|
||||
_vectorV4Searcher.Search(_testIPv4Address);
|
||||
_fileV4Searcher.Search(_testIPv4Address);
|
||||
|
||||
_contentV6Searcher.Search(_testIPv6Address);
|
||||
_vectorV6Searcher.Search(_testIPv6Address);
|
||||
_fileV6Searcher.Search(_testIPv6Address);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("IPv4")]
|
||||
public void ContentIPv4() => _contentV4Searcher.Search(_testIPv4Address);
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("IPv4")]
|
||||
public void VectorIPv4() => _vectorV4Searcher.Search(_testIPv4Address);
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("IPv4")]
|
||||
public void FileIPv4() => _fileV4Searcher.Search(_testIPv4Address);
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("IPv6")]
|
||||
public void ContentIPv6() => _contentV6Searcher.Search(_testIPv6Address);
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("IPv6")]
|
||||
public void VectorIPv6() => _vectorV6Searcher.Search(_testIPv6Address);
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("IPv6")]
|
||||
public void FileIPv6() => _fileV6Searcher.Search(_testIPv6Address);
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.15.6" />
|
||||
<PackageReference Include="BenchmarkDotNet.Annotations" Version="0.15.6" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\IP2Region.Net\IP2Region.Net.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="..\..\..\data\ip2region_v4.xdb">
|
||||
<Link>IP2Region/ip2region_v4.xdb</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="..\..\..\data\ip2region_v6.xdb">
|
||||
<Link>IP2Region/ip2region_v6.xdb</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
// Copyright 2025 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
// @Author Alan <lzh.shap@gmail.com>
|
||||
// @Date 2023/07/25
|
||||
// Updated by Argo Zhang <argo@live.ca> at 2025/11/21
|
||||
|
||||
using BenchmarkDotNet.Running;
|
||||
using IP2Region.Net.BenchMark;
|
||||
|
||||
BenchmarkRunner.Run<Benchmarks>();
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
|
||||
<PackageReference Include="xunit" Version="2.*" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.*">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\IP2Region.Net\IP2Region.Net.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="..\..\..\data\ipv4_source.txt">
|
||||
<Link>TestData/ipv4_source.txt</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="..\..\..\data\ip2region_v4.xdb">
|
||||
<Link>TestData/ip2region_v4.xdb</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="..\..\..\data\ipv6_source.txt">
|
||||
<Link>TestData/ipv6_source.txt</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="..\..\..\data\ip2region_v6.xdb">
|
||||
<Link>TestData/ip2region_v6.xdb</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,163 @@
|
|||
// Copyright 2025 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
// @Author Alan <lzh.shap@gmail.com>
|
||||
// @Date 2023/07/25
|
||||
// Updated by Argo Zhang <argo@live.ca> at 2025/11/21
|
||||
|
||||
using IP2Region.Net.Abstractions;
|
||||
using IP2Region.Net.XDB;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System.Net;
|
||||
using Xunit;
|
||||
|
||||
namespace IP2Region.Net.Test;
|
||||
|
||||
public class SearcherTest
|
||||
{
|
||||
private readonly string _xdbPathV4 = Path.Combine(AppContext.BaseDirectory, "TestData", "ip2region_v4.xdb");
|
||||
private readonly string _xdbPathV6 = Path.Combine(AppContext.BaseDirectory, "TestData", "ip2region_v6.xdb");
|
||||
|
||||
[Theory]
|
||||
[InlineData("58.251.27.201", "中国|广东省|深圳市|联通|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)
|
||||
{
|
||||
var _xdbPath = version == "v4" ? _xdbPathV4 : _xdbPathV6;
|
||||
var contentSearcher = new Searcher(CachePolicy.Content, _xdbPath);
|
||||
var region = contentSearcher.Search(ip);
|
||||
Assert.Equal(expected, region);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("58.251.27.201", "中国|广东省|深圳市|联通|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 TestSearchCacheVector(string ip, string expected, string version)
|
||||
{
|
||||
var _xdbPath = version == "v4" ? _xdbPathV4 : _xdbPathV6;
|
||||
var vectorSearcher = new Searcher(CachePolicy.VectorIndex, _xdbPath);
|
||||
var region = vectorSearcher.Search(ip);
|
||||
Assert.Equal(expected, region);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("58.251.0.0", "中国|广东省|深圳市|联通|CN", "v4")]
|
||||
[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 _xdbPath = version == "v4" ? _xdbPathV4 : _xdbPathV6;
|
||||
var fileSearcher = new Searcher(CachePolicy.File, _xdbPath);
|
||||
var region = fileSearcher.Search(ip);
|
||||
Assert.Equal(expected, region);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IoCount_File_Ok()
|
||||
{
|
||||
var searcher = new Searcher(CachePolicy.File, _xdbPathV4);
|
||||
searcher.Search("58.251.27.201");
|
||||
Assert.Equal(3, searcher.IoCount);
|
||||
|
||||
searcher.Search("58.251.27.201");
|
||||
Assert.Equal(3, searcher.IoCount);
|
||||
|
||||
searcher.Dispose();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IoCount_Vector_Ok()
|
||||
{
|
||||
var searcher = new Searcher(CachePolicy.VectorIndex, _xdbPathV4);
|
||||
searcher.Search("58.251.27.201");
|
||||
Assert.Equal(2, searcher.IoCount);
|
||||
|
||||
searcher.Search("58.251.27.201");
|
||||
Assert.Equal(2, searcher.IoCount);
|
||||
|
||||
searcher.Dispose();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IoCount_Content_Ok()
|
||||
{
|
||||
var searcher = new Searcher(CachePolicy.Content, _xdbPathV4);
|
||||
searcher.Search("58.251.27.201");
|
||||
Assert.Equal(0, searcher.IoCount);
|
||||
|
||||
searcher.Search("58.251.27.201");
|
||||
Assert.Equal(0, searcher.IoCount);
|
||||
|
||||
searcher.Dispose();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("58.251.255.255", "中国|广东省|深圳市|联通|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))
|
||||
{
|
||||
var ps = line.Trim().Split("|", 3);
|
||||
var sip = ps[0];
|
||||
var eip = ps[1];
|
||||
|
||||
var s1 = searcher.Search(sip);
|
||||
var s2 = searcher.Search(eip);
|
||||
Assert.Equal(s1, ps[2]);
|
||||
Assert.Equal(s2, ps[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
// Copyright 2025 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
// @Author Alan <lzh.shap@gmail.com>
|
||||
// @Date 2023/07/25
|
||||
// Updated by Argo Zhang <argo@live.ca> at 2025/11/21
|
||||
|
||||
using Xunit;
|
||||
|
||||
namespace IP2Region.Net.Test;
|
||||
|
||||
public class UtilTest
|
||||
{
|
||||
[Fact]
|
||||
public void IpAddressToUInt32_Ok()
|
||||
{
|
||||
var uintIp = XDB.Util.IpAddressToUInt32("114.114.114.114");
|
||||
Assert.Equal((uint)1920103026, uintIp);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetMidIp_Ok()
|
||||
{
|
||||
var uintIp = XDB.Util.GetMidIp(1, 10);
|
||||
Assert.Equal((uint)5, uintIp);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
// Copyright 2025 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
// @Author Alan <lzh.shap@gmail.com>
|
||||
// @Date 2023/07/25
|
||||
// Updated by Argo Zhang <argo@live.ca> at 2025/11/21
|
||||
|
||||
using Xunit;
|
||||
|
||||
namespace IP2Region.Net.Test;
|
||||
|
||||
public class XdbTest
|
||||
{
|
||||
[Fact]
|
||||
public async Task VersionIPV4_Ok()
|
||||
{
|
||||
var db = Path.Combine(AppContext.BaseDirectory, "TestData", $"ip2region_v4.xdb");
|
||||
|
||||
var version = await XDB.Util.GetVersionAsync(db);
|
||||
Assert.Equal(3, version.Ver);
|
||||
Assert.Equal(1, version.CachePolice);
|
||||
//Assert.Equal("2025-09-06 02:24:16", version.CreatedTime.ToString("yyyy-MM-dd HH:mm:ss"));
|
||||
//Assert.Equal((uint)955933, version.StartIndex);
|
||||
//Assert.Equal((uint)11042415, version.EndIndex);
|
||||
Assert.Equal(4, version.IPVer);
|
||||
Assert.Equal(4, version.BytesCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task VersionIPV6_Ok()
|
||||
{
|
||||
var db = Path.Combine(AppContext.BaseDirectory, "TestData", $"ip2region_v6.xdb");
|
||||
var version = await XDB.Util.GetVersionAsync(db);
|
||||
Assert.Equal(3, version.Ver);
|
||||
Assert.Equal(1, version.CachePolice);
|
||||
//Assert.Equal("2025-10-17 04:41:04", version.CreatedTime.ToString("yyyy-MM-dd HH:mm:ss"));
|
||||
//Assert.Equal((uint)3094259, version.StartIndex);
|
||||
//Assert.Equal((uint)36258303, version.EndIndex);
|
||||
Assert.Equal(6, version.IPVer);
|
||||
Assert.Equal(4, version.BytesCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetVersionAsync_Error()
|
||||
{
|
||||
await Assert.ThrowsAsync<ArgumentNullException>(async () => await XDB.Util.GetVersionAsync(null!));
|
||||
await Assert.ThrowsAsync<FileNotFoundException>(async () => await XDB.Util.GetVersionAsync(Path.Combine(AppContext.BaseDirectory, "test.xdb")));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<Solution>
|
||||
<Folder Name="/Solution Items/">
|
||||
<File Path=".editorconfig" />
|
||||
<File Path="README.md" />
|
||||
</Folder>
|
||||
<Project Path="IP2Region.Net.BenchMark/IP2Region.Net.BenchMark.csproj" />
|
||||
<Project Path="IP2Region.Net.Test/IP2Region.Net.Test.csproj" />
|
||||
<Project Path="IP2Region.Net/IP2Region.Net.csproj" />
|
||||
</Solution>
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
// Copyright 2025 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
// @Author Alan <lzh.shap@gmail.com>
|
||||
// @Date 2023/07/25
|
||||
// Updated by Argo Zhang <argo@live.ca> at 2025/11/21
|
||||
|
||||
using System.Net;
|
||||
|
||||
namespace IP2Region.Net.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// IP 转化为地理位置搜索器接口
|
||||
/// </summary>
|
||||
public interface ISearcher : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// 搜索方法
|
||||
/// </summary>
|
||||
/// <param name="ipStr">IP 地址字符串 如 192.168.0.1</param>
|
||||
/// <returns></returns>
|
||||
string? Search(string ipStr);
|
||||
|
||||
/// <summary>
|
||||
/// 搜索方法
|
||||
/// </summary>
|
||||
string? Search(IPAddress ipAddress);
|
||||
|
||||
/// <summary>
|
||||
/// 搜索方法 仅限 IPv4 使用
|
||||
/// </summary>
|
||||
/// <param name="ipAddress">IPv4 地址字节数组小端读取 uint 数值</param>
|
||||
[Obsolete("已弃用,请改用其他方法;Deprecated; please use Search(string) or Search(IPAddress) method.")]
|
||||
string? Search(uint ipAddress);
|
||||
|
||||
/// <summary>
|
||||
/// 获得 内部 IO 访问次数
|
||||
/// </summary>
|
||||
int IoCount { get; }
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
// Copyright 2025 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
// @Author Alan <lzh.shap@gmail.com>
|
||||
// @Date 2023/07/25
|
||||
// Updated by Argo Zhang <argo@live.ca> at 2025/11/21
|
||||
|
||||
using IP2Region.Net.Abstractions;
|
||||
using IP2Region.Net.XDB;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
|
||||
namespace Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
/// <summary>
|
||||
/// IP2Region 服务扩展类
|
||||
/// </summary>
|
||||
public static class IP2RegionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 添加 IP2RegionService 服务。
|
||||
/// </summary>
|
||||
/// <param name="services"><see cref="IServiceCollection"/> 集合</param>
|
||||
/// <param name="path">IP2Region 数据库文件的路径。</param>
|
||||
/// <param name="cachePolicy">缓存策略,默认为 <see cref="CachePolicy.Content"/>。</param>
|
||||
public static IServiceCollection AddIP2RegionService(this IServiceCollection services, string path, CachePolicy cachePolicy = CachePolicy.Content)
|
||||
{
|
||||
services.TryAddSingleton<ISearcher>(provider =>
|
||||
{
|
||||
return new Searcher(cachePolicy, path);
|
||||
});
|
||||
#if NET8_0_OR_GREATER
|
||||
services.TryAddKeyedSingleton("IP2Region.Net", (provider, _) =>
|
||||
{
|
||||
return provider.GetRequiredService<ISearcher>();
|
||||
});
|
||||
#endif
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<id>IP2Region.Net</id>
|
||||
<version>3.0.2</version>
|
||||
<title>IP2Region.Net</title>
|
||||
<authors>Alan Lee;Argo Zhang(argo@live.ca)</authors>
|
||||
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
<PackageProjectUrl>https://github.com/lionsoul2014/ip2region/tree/master/binding/csharp</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/lionsoul2014/ip2region/tree/master/binding/csharp</RepositoryUrl>
|
||||
<PackageReleaseNotes>Please refer to CHANGELOG.md for details</PackageReleaseNotes>
|
||||
<Description>.NET client library for ip2region</Description>
|
||||
<PackageTags>IP2Region GeoIP IPSearch</PackageTags>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<TargetFrameworks>netstandard2.0;netstandard2.1;net6.0;net7.0;net8.0;net9.0;net10.0</TargetFrameworks>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<UserSecretsId>c2f07fe1-a300-4de3-8200-1278ed8cb5b7</UserSecretsId>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="..\README.md" Pack="true" PackagePath="\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
|
||||
<PackageReference Include="System.Buffers" Version="4.5.1" />
|
||||
<PackageReference Include="System.Memory" Version="4.5.5" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.*" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.1'">
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net6.0'">
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.*" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.*" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.*" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net9.0'">
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.*" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net10.0'">
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.*" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="..\CHANGELOG.md">
|
||||
<Link>CHANGELOG.md</Link>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
// Copyright 2025 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
// @Author Alan <lzh.shap@gmail.com>
|
||||
// @Date 2023/07/25
|
||||
// Updated by Argo Zhang <argo@live.ca> at 2025/11/21
|
||||
|
||||
using IP2Region.Net.XDB;
|
||||
|
||||
namespace IP2Region.Net.Internal;
|
||||
|
||||
static class CacheStrategyFactory
|
||||
{
|
||||
public static ICacheStrategy CreateCacheStrategy(CachePolicy cachePolicy, string xdbPath) => cachePolicy switch
|
||||
{
|
||||
CachePolicy.Content => new ContentCacheStrategy(xdbPath),
|
||||
CachePolicy.VectorIndex => new VectorIndexCacheStrategy(xdbPath),
|
||||
_ => new FileCacheStrategy(xdbPath),
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
// 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 Wong <vcd.hai@outlook.com> at 2025/12/31
|
||||
|
||||
namespace IP2Region.Net.Internal;
|
||||
|
||||
class ContentCacheStrategy(string xdbPath) : ICacheStrategy
|
||||
{
|
||||
// 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;
|
||||
|
||||
private readonly ReadOnlyMemory<byte> _cacheData = File.ReadAllBytes(xdbPath);
|
||||
|
||||
public int IoCount => 0;
|
||||
|
||||
public void ResetIoCount()
|
||||
{
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
public ReadOnlyMemory<byte> GetVectorIndex(int offset)
|
||||
=> _cacheData.Slice(HeaderInfoLength + offset, VectorIndexSize);
|
||||
|
||||
public ReadOnlyMemory<byte> GetData(long offset, int length) => _cacheData.Slice((int)offset, length);
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
// Copyright 2025 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
// @Author Alan <lzh.shap@gmail.com>
|
||||
// @Date 2023/07/25
|
||||
// Updated by Argo Zhang <argo@live.ca> at 2025/11/21
|
||||
|
||||
using System.Buffers;
|
||||
|
||||
namespace IP2Region.Net.Internal;
|
||||
|
||||
class FileCacheStrategy(string xdbPath) : ICacheStrategy
|
||||
{
|
||||
protected const int HeaderInfoLength = 256;
|
||||
protected const int VectorIndexSize = 8;
|
||||
|
||||
protected const int BufferSize = 64 * 1024;
|
||||
|
||||
protected FileStream XdbFileStream = new(xdbPath, FileMode.Open, FileAccess.Read, FileShare.Read, BufferSize, FileOptions.RandomAccess);
|
||||
|
||||
public int IoCount { get; set; }
|
||||
|
||||
public void ResetIoCount()
|
||||
{
|
||||
IoCount = 0;
|
||||
}
|
||||
|
||||
public virtual ReadOnlyMemory<byte> GetVectorIndex(int offset) => GetData(HeaderInfoLength + offset, VectorIndexSize);
|
||||
|
||||
public virtual ReadOnlyMemory<byte> GetData(long offset, int length)
|
||||
{
|
||||
var buffer = ArrayPool<byte>.Shared.Rent(length);
|
||||
try
|
||||
{
|
||||
int totalBytesRead = 0;
|
||||
XdbFileStream.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
int bytesRead;
|
||||
while (totalBytesRead < length)
|
||||
{
|
||||
bytesRead = XdbFileStream.Read(buffer, totalBytesRead, length - totalBytesRead);
|
||||
if (bytesRead == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
totalBytesRead += bytesRead;
|
||||
IoCount++;
|
||||
}
|
||||
|
||||
var ret = new byte[totalBytesRead];
|
||||
if (totalBytesRead > 0)
|
||||
{
|
||||
Array.Copy(buffer, 0, ret, 0, totalBytesRead);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放文件句柄
|
||||
/// </summary>
|
||||
/// <param name="disposing"></param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
XdbFileStream.Close();
|
||||
XdbFileStream.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
// Copyright 2025 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
// @Author Alan <lzh.shap@gmail.com>
|
||||
// @Date 2023/07/25
|
||||
// Updated by Argo Zhang <argo@live.ca> at 2025/11/21
|
||||
|
||||
namespace IP2Region.Net.Internal;
|
||||
|
||||
internal interface ICacheStrategy : IDisposable
|
||||
{
|
||||
int IoCount { get; }
|
||||
|
||||
void ResetIoCount();
|
||||
|
||||
ReadOnlyMemory<byte> GetVectorIndex(int offset);
|
||||
|
||||
ReadOnlyMemory<byte> GetData(long offset, int length);
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
// Copyright 2025 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
// @Author Alan <lzh.shap@gmail.com>
|
||||
// @Date 2023/07/25
|
||||
// Updated by Argo Zhang <argo@live.ca> at 2025/11/21
|
||||
|
||||
namespace IP2Region.Net.Internal;
|
||||
|
||||
class VectorIndexCacheStrategy : FileCacheStrategy
|
||||
{
|
||||
private const int VectorIndexRows = 256;
|
||||
private const int VectorIndexCols = 256;
|
||||
|
||||
private readonly ReadOnlyMemory<byte> _vectorCache;
|
||||
|
||||
public VectorIndexCacheStrategy(string xdbPath) : base(xdbPath)
|
||||
{
|
||||
_vectorCache = GetData(HeaderInfoLength, VectorIndexRows * VectorIndexCols * VectorIndexSize);
|
||||
}
|
||||
|
||||
public override ReadOnlyMemory<byte> GetVectorIndex(int offset) => _vectorCache.Slice(offset, VectorIndexSize);
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
// Copyright 2025 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
// @Author Alan <lzh.shap@gmail.com>
|
||||
// @Date 2023/07/25
|
||||
// Updated by Argo Zhang <argo@live.ca> at 2025/11/21
|
||||
|
||||
namespace IP2Region.Net.XDB;
|
||||
|
||||
/// <summary>
|
||||
/// 缓存策略枚举
|
||||
/// </summary>
|
||||
public enum CachePolicy
|
||||
{
|
||||
/// <summary>
|
||||
/// no cache
|
||||
/// </summary>
|
||||
File,
|
||||
|
||||
/// <summary>
|
||||
/// cache vector index , reduce the number of IO operations
|
||||
/// </summary>
|
||||
VectorIndex,
|
||||
|
||||
/// <summary>
|
||||
/// default cache policy , cache whole xdb file
|
||||
/// </summary>
|
||||
Content
|
||||
}
|
||||
|
|
@ -0,0 +1,167 @@
|
|||
// Copyright 2025 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
// @Author Alan <lzh.shap@gmail.com>
|
||||
// @Date 2023/07/25
|
||||
// Updated by Argo Zhang <argo@live.ca> at 2025/11/21
|
||||
|
||||
using IP2Region.Net.Abstractions;
|
||||
using IP2Region.Net.Internal;
|
||||
using System.Buffers.Binary;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
|
||||
namespace IP2Region.Net.XDB;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="ISearcher"/> 实现类
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <inheritdoc/>
|
||||
/// </remarks>
|
||||
public class Searcher(CachePolicy cachePolicy, string xdbPath) : ISearcher
|
||||
{
|
||||
private readonly ICacheStrategy _cacheStrategy = CacheStrategyFactory.CreateCacheStrategy(cachePolicy, xdbPath);
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
public int IoCount => _cacheStrategy.IoCount;
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
public string? Search(string ipStr)
|
||||
{
|
||||
var ipAddress = IPAddress.Parse(ipStr);
|
||||
return SearchCore(ipAddress.GetAddressBytes());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
public string? Search(IPAddress ipAddress) => SearchCore(ipAddress.GetAddressBytes());
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
[Obsolete("已弃用,请改用其他方法;Deprecated; please use Search(string) or Search(IPAddress) method.")]
|
||||
[ExcludeFromCodeCoverage]
|
||||
public string? Search(uint ipAddress)
|
||||
{
|
||||
var bytes = BitConverter.GetBytes(ipAddress);
|
||||
Array.Reverse(bytes);
|
||||
return SearchCore(bytes);
|
||||
}
|
||||
|
||||
string? SearchCore(byte[] ipBytes)
|
||||
{
|
||||
// 重置 IO 计数器
|
||||
_cacheStrategy.ResetIoCount();
|
||||
|
||||
// 每个 vector 索引项的字节数
|
||||
var vectorIndexSize = 8;
|
||||
|
||||
// vector 索引的列数
|
||||
var vectorIndexCols = 256;
|
||||
|
||||
// 计算得到 vector 索引项的开始地址。
|
||||
var il0 = ipBytes[0];
|
||||
var il1 = ipBytes[1];
|
||||
var idx = il0 * vectorIndexCols * vectorIndexSize + il1 * vectorIndexSize;
|
||||
|
||||
var vector = _cacheStrategy.GetVectorIndex(idx);
|
||||
var sPtr = BinaryPrimitives.ReadUInt32LittleEndian(vector.Span);
|
||||
var ePtr = BinaryPrimitives.ReadUInt32LittleEndian(vector.Span.Slice(4));
|
||||
|
||||
// @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;
|
||||
long dataPtr = 0;
|
||||
|
||||
while (l <= h)
|
||||
{
|
||||
int m = (int)(l + h) >> 1;
|
||||
|
||||
var p = sPtr + m * indexSize;
|
||||
var buff = _cacheStrategy.GetData(p, indexSize);
|
||||
|
||||
var s = buff.Span.Slice(0, length);
|
||||
var e = buff.Span.Slice(length, length);
|
||||
if (ByteCompare(ipBytes, s) < 0)
|
||||
{
|
||||
h = m - 1;
|
||||
}
|
||||
else if (ByteCompare(ipBytes, e) > 0)
|
||||
{
|
||||
l = m + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
dataLen = BinaryPrimitives.ReadUInt16LittleEndian(buff.Span.Slice(length * 2, 2));
|
||||
dataPtr = BinaryPrimitives.ReadUInt32LittleEndian(buff.Span.Slice(length * 2 + 2, 4));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var regionBuff = _cacheStrategy.GetData(dataPtr, dataLen);
|
||||
return Encoding.UTF8.GetString(regionBuff.Span.ToArray());
|
||||
}
|
||||
|
||||
static int ByteCompare(byte[] ip1, ReadOnlySpan<byte> ip2) => ip1.Length == 4 ? IPv4Compare(ip1, ip2) : IPv6Compare(ip1, ip2);
|
||||
|
||||
static int IPv4Compare(byte[] ip1, ReadOnlySpan<byte> ip2)
|
||||
{
|
||||
var ret = 0;
|
||||
for (int i = 0; i < ip1.Length; i++)
|
||||
{
|
||||
var ip2Index = ip1.Length - 1 - i;
|
||||
if (ip1[i] < ip2[ip2Index])
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else if (ip1[i] > ip2[ip2Index])
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int IPv6Compare(byte[] ip1, ReadOnlySpan<byte> ip2)
|
||||
{
|
||||
var ret = 0;
|
||||
for (int i = 0; i < ip1.Length; i++)
|
||||
{
|
||||
if (ip1[i] < ip2[i])
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else if (ip1[i] > ip2[i])
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
_cacheStrategy.Dispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
// Copyright 2025 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
// @Author Alan <lzh.shap@gmail.com>
|
||||
// @Date 2023/07/25
|
||||
// Updated by Argo Zhang <argo@live.ca> at 2025/11/21
|
||||
|
||||
using System.Buffers;
|
||||
using System.Buffers.Binary;
|
||||
using System.Net;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace IP2Region.Net.XDB;
|
||||
|
||||
/// <summary>
|
||||
/// 工具类
|
||||
/// </summary>
|
||||
public static class Util
|
||||
{
|
||||
public static uint IpAddressToUInt32(string ipAddress)
|
||||
{
|
||||
var address = IPAddress.Parse(ipAddress);
|
||||
return IpAddressToUInt32(address);
|
||||
}
|
||||
|
||||
public static uint IpAddressToUInt32(IPAddress ipAddress)
|
||||
{
|
||||
byte[] bytes = ipAddress.GetAddressBytes();
|
||||
Array.Reverse(bytes);
|
||||
return MemoryMarshal.Read<uint>(bytes);
|
||||
}
|
||||
|
||||
public static uint GetMidIp(uint x, uint y)
|
||||
=> (x & y) + ((x ^ y) >> 1);
|
||||
|
||||
public static async Task<XdbVersion> GetVersionAsync(string dbPath, CancellationToken token = default)
|
||||
{
|
||||
if (string.IsNullOrEmpty(dbPath))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(dbPath));
|
||||
}
|
||||
|
||||
if (!File.Exists(dbPath))
|
||||
{
|
||||
throw new FileNotFoundException("xdb file not found.", dbPath);
|
||||
}
|
||||
|
||||
using var reader = File.OpenRead(dbPath);
|
||||
return await GetVersionAsync(reader, token);
|
||||
}
|
||||
|
||||
internal static async Task<XdbVersion> GetVersionAsync(FileStream reader, CancellationToken token = default)
|
||||
{
|
||||
XdbVersion ret = default;
|
||||
var buffer = ArrayPool<byte>.Shared.Rent(256);
|
||||
|
||||
try
|
||||
{
|
||||
var length = await reader.ReadAsync(buffer, 0, 256, token);
|
||||
if (length == 256)
|
||||
{
|
||||
ret = Parse(buffer);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(buffer);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static XdbVersion Parse(ReadOnlySpan<byte> buffer)
|
||||
{
|
||||
var ret = new XdbVersion
|
||||
{
|
||||
Ver = BinaryPrimitives.ReadUInt16LittleEndian(buffer.Slice(0, 2)),
|
||||
CachePolice = BinaryPrimitives.ReadUInt16LittleEndian(buffer.Slice(2, 2)),
|
||||
StartIndex = BinaryPrimitives.ReadUInt32LittleEndian(buffer.Slice(8, 4)),
|
||||
EndIndex = BinaryPrimitives.ReadUInt32LittleEndian(buffer.Slice(12, 4)),
|
||||
IPVer = BinaryPrimitives.ReadUInt16LittleEndian(buffer.Slice(16, 2)),
|
||||
BytesCount = BinaryPrimitives.ReadUInt16LittleEndian(buffer.Slice(18, 2))
|
||||
};
|
||||
|
||||
var createdAt = BinaryPrimitives.ReadUInt32LittleEndian(buffer.Slice(4, 4));
|
||||
var dtm = new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.FromHours(0));
|
||||
ret.CreatedTime = dtm.AddSeconds(createdAt);
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
// Copyright 2025 The Ip2Region Authors. All rights reserved.
|
||||
// Use of this source code is governed by a Apache2.0-style
|
||||
// license that can be found in the LICENSE file.
|
||||
// @Author Alan <lzh.shap@gmail.com>
|
||||
// @Date 2023/07/25
|
||||
// Updated by Argo Zhang <argo@live.ca> at 2025/11/21
|
||||
|
||||
namespace IP2Region.Net.XDB;
|
||||
|
||||
/// <summary>
|
||||
/// XdbVersion 结构体
|
||||
/// </summary>
|
||||
public struct XdbVersion
|
||||
{
|
||||
/// <summary>
|
||||
/// 获得/设置 版本号
|
||||
/// </summary>
|
||||
public ushort Ver { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 缓存策略
|
||||
/// </summary>
|
||||
public ushort CachePolice { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 文件生成时间
|
||||
/// </summary>
|
||||
public DateTimeOffset CreatedTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 索引起始地址
|
||||
/// </summary>
|
||||
public uint StartIndex { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 索引结束地址
|
||||
/// </summary>
|
||||
public uint EndIndex { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 IP版本
|
||||
/// </summary>
|
||||
public ushort IPVer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 指针字节数
|
||||
/// </summary>
|
||||
public ushort BytesCount { get; set; }
|
||||
}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
# IP2Region.Net
|
||||
|
||||
.NET client library for IP2Region
|
||||
|
||||
## Installation
|
||||
|
||||
Install the package with [NuGet](https://www.nuget.org/packages/IP2Region.Net)
|
||||
|
||||
```bash
|
||||
Install-Package IP2Region.Net
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```csharp
|
||||
using IP2Region.Net.Abstractions;
|
||||
using IP2Region.Net.XDB;
|
||||
|
||||
ISearcher searcher = new Searcher(CachePolicy , "your xdb file path");
|
||||
```
|
||||
### Cache Policy Description
|
||||
| Cache Policy | Description | Thread Safe |
|
||||
|-------------------------|------------------------------------------------------------------------------------------------------------|-------------|
|
||||
| CachePolicy.Content | Cache the entire `xdb` data. | Yes |
|
||||
| CachePolicy.VectorIndex | Cache `vecotorIndex` to speed up queries and reduce system io pressure by reducing one fixed IO operation. | Yes |
|
||||
| CachePolicy.File | Completely file-based queries | Yes |
|
||||
|
||||
### XDB File Description
|
||||
Generate using [maker](https://github.com/lionsoul2014/ip2region/tree/master/maker/csharp), or [download](https://github.com/lionsoul2014/ip2region/blob/master/data/ip2region.xdb) pre-generated xdb files
|
||||
|
||||
## ASP.NET Core Usage
|
||||
|
||||
```csharp
|
||||
services.AddIP2RegionService("your xdb file path", cachePolicy: CachePolicy.Content);
|
||||
```
|
||||
|
||||
NET6/7
|
||||
```csharp
|
||||
provider.GetRequiredService<ISearcher>()
|
||||
```
|
||||
|
||||
NET8+ support keyed service
|
||||
```csharp
|
||||
provider.GetRequiredKeyedService<ISearcher>("IP2Region.Net");
|
||||
```
|
||||
|
||||
## TargetFrameworks
|
||||
netstandard2.0;netstandard2.1;net6.0;net7.0;net8.0;net9.0;net10.0
|
||||
|
||||
## Performance
|
||||
// * Summary *
|
||||
|
||||
BenchmarkDotNet v0.15.6, Windows 11 (10.0.26200.7171)
|
||||
13th Gen Intel Core i7-13700 2.10GHz, 1 CPU, 24 logical and 16 physical cores
|
||||
.NET SDK 10.0.100
|
||||
[Host] : .NET 10.0.0 (10.0.0, 10.0.25.52411), X64 RyuJIT x86-64-v3
|
||||
DefaultJob : .NET 10.0.0 (10.0.0, 10.0.25.52411), X64 RyuJIT x86-64-v3
|
||||
|
||||
|
||||
| Method | Mean | Error | StdDev | Gen0 | Allocated |
|
||||
|------------ |-------------:|-----------:|-----------:|-------:|----------:|
|
||||
| ContentIPv4 | 53.70 ns | 0.296 ns | 0.277 ns | 0.0086 | 136 B |
|
||||
| VectorIPv4 | 4,446.04 ns | 18.673 ns | 15.593 ns | 0.0076 | 232 B |
|
||||
| FileIPv4 | 6,712.40 ns | 15.718 ns | 13.934 ns | 0.0153 | 264 B |
|
||||
| ContentIPv6 | 145.53 ns | 0.331 ns | 0.277 ns | 0.0126 | 200 B |
|
||||
| VectorIPv6 | 7,058.39 ns | 125.505 ns | 117.398 ns | 0.0381 | 712 B |
|
||||
| FileIPv6 | 10,657.97 ns | 53.907 ns | 50.425 ns | 0.0458 | 744 B |
|
||||
|
||||
// * Hints *
|
||||
Outliers
|
||||
Benchmarks.VectorIPv4: Default -> 2 outliers were removed (4.55 us, 4.58 us)
|
||||
Benchmarks.FileIPv4: Default -> 1 outlier was removed (6.79 us)
|
||||
Benchmarks.ContentIPv6: Default -> 2 outliers were removed (148.08 ns, 152.27 ns)
|
||||
|
||||
// * Legends *
|
||||
Mean : Arithmetic mean of all measurements
|
||||
Error : Half of 99.9% confidence interval
|
||||
StdDev : Standard deviation of all measurements
|
||||
Gen0 : GC Generation 0 collects per 1000 operations
|
||||
Allocated : Allocated memory per single operation (managed only, inclusive, 1KB = 1024B)
|
||||
1 ns : 1 Nanosecond (0.000000001 sec)
|
||||
|
||||
// * Diagnostic Output - MemoryDiagnoser *
|
||||
|
||||
|
||||
// ***** BenchmarkRunner: End *****
|
||||
Run time: 00:02:06 (126.09 sec), executed benchmarks: 6
|
||||
|
||||
Global total time: 00:02:13 (133.47 sec), executed benchmarks: 6
|
||||
// * Artifacts cleanup *
|
||||
Artifacts cleanup is finished
|
||||
|
||||
## Contributing
|
||||
Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
|
||||
|
||||
Please make sure to update tests as appropriate.
|
||||
|
||||
## License
|
||||
[Apache License 2.0](https://github.com/lionsoul2014/ip2region/blob/master/LICENSE.md)
|
||||
|
|
@ -0,0 +1,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
|
||||
|
|
@ -0,0 +1,180 @@
|
|||
:globe_with_meridians: [中文简体](README_zh.md) | [English](README.md)
|
||||
|
||||
# ip2region Erlang query client
|
||||
|
||||
### Introduction
|
||||
|
||||
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,[
|
||||
{poolargs, [
|
||||
{size, 1}, %% Default number of worker processes
|
||||
{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
|
||||
```
|
||||
|
||||
### Run
|
||||
|
||||
Place the xdb file in the `priv` directory, then start the Erlang node:
|
||||
|
||||
```bash
|
||||
$ rebar3 shell
|
||||
```
|
||||
|
||||
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").
|
||||
[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
|
||||
```
|
||||
|
||||
With dual-stack enabled, IPv6 addresses are supported in the same way:
|
||||
|
||||
```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, [
|
||||
ip2region
|
||||
]}.
|
||||
```
|
||||
|
||||
- Start the ip2region Application
|
||||
|
||||
```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
|
||||
===> 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
|
||||
```
|
||||
|
||||
### 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/
|
||||
$ sh xdb-benchmark.sh ipv4
|
||||
```
|
||||
|
||||
For IPv6:
|
||||
|
||||
```bash
|
||||
$ sh xdb-benchmark.sh ipv6
|
||||
```
|
||||
|
||||
Or use the Makefile targets from the `binding/erlang` directory:
|
||||
|
||||
```bash
|
||||
$ make bench-v4
|
||||
$ make bench-v6
|
||||
```
|
||||
|
||||
#### IPv4 benchmark example
|
||||
|
||||
```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 benchmark example
|
||||
|
||||
```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.
|
||||
```
|
||||
|
|
@ -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.
|
||||
```
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Run from binding/erlang so paths and rebar3 artifacts are correct.
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
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}"
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
-ifndef(IP2REGION_HRL).
|
||||
-define(IP2REGION_HRL, true).
|
||||
|
||||
-define(NONE, none).
|
||||
-define(APP_NAME, ip2region).
|
||||
|
||||
-define(XDB_HEADER_SIZE, 256).
|
||||
-define(XDB_VECTOR_COLS, 256).
|
||||
-define(XDB_VECTOR_INDEX_SIZE, 8).
|
||||
-define(XDB_VECTOR_INDEX_COUNT, (16#10000)). %% 256*256
|
||||
|
||||
%% IP versions as stored in xdb header
|
||||
-define(IP_VERSION_4, 4).
|
||||
-define(IP_VERSION_6, 6).
|
||||
|
||||
%% 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
|
||||
|
||||
%% Per-version ETS tables
|
||||
-define(XDB_VECTOR_INDEX_V4, ets_xdb_vector_index_v4).
|
||||
-define(XDB_VECTOR_INDEX_V6, ets_xdb_vector_index_v6).
|
||||
-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.
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
{erl_opts, [
|
||||
debug_info,
|
||||
export_all,
|
||||
nowarn_export_all
|
||||
]}.
|
||||
|
||||
{plugins, [rebar3_hex, rebar3_ex_doc]}.
|
||||
|
||||
{deps, [
|
||||
poolboy
|
||||
]}.
|
||||
|
||||
{shell, [
|
||||
% {config, "config/sys.config"},
|
||||
{apps, [ip2region]}
|
||||
]}.
|
||||
|
||||
{ex_doc, [
|
||||
{extras, ["README.md"]},
|
||||
{main, "README.md"},
|
||||
{source_url, "https://github.com/lionsoul2014/ip2region/tree/master/binding/erlang"}
|
||||
]}.
|
||||
|
||||
{hex, [{doc, ex_doc}]}.
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
{"1.2.0",
|
||||
[{<<"poolboy">>,{pkg,<<"poolboy">>,<<"1.5.2">>},0}]}.
|
||||
[
|
||||
{pkg_hash,[
|
||||
{<<"poolboy">>, <<"392B007A1693A64540CEAD79830443ABF5762F5D30CF50BC95CB2C1AAAFA006B">>}]},
|
||||
{pkg_hash_ext,[
|
||||
{<<"poolboy">>, <<"DAD79704CE5440F3D5A3681C8590B9DC25D1A561E8F5A9C995281012860901E3">>}]}
|
||||
].
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
{application, ip2region,
|
||||
[{description, "ip2region xdb client application"},
|
||||
{vsn, "0.1.0"},
|
||||
{registered, []},
|
||||
{mod, {ip2region_app, []}},
|
||||
{applications,
|
||||
[kernel,
|
||||
stdlib,
|
||||
poolboy
|
||||
]},
|
||||
{env,[
|
||||
{poolargs, [
|
||||
{size, 1},
|
||||
{max_overflow, 5}
|
||||
]},
|
||||
{db, [
|
||||
{ipv4, "ip2region.xdb"}
|
||||
]}
|
||||
]},
|
||||
{modules, []},
|
||||
|
||||
{licenses, ["Apache-2.0"]},
|
||||
{links, [{"Github", "https://github.com/lionsoul2014/ip2region/tree/master/binding/erlang"}]}
|
||||
]}.
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
%%%-------------------------------------------------------------------
|
||||
%% Copyright 2022 The Ip2Region Authors. All rights reserved.
|
||||
%% Use of this source code is governed by a Apache2.0-style
|
||||
%% license that can be found in the LICENSE file.
|
||||
%%
|
||||
%% @doc
|
||||
%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
-module(ip2region_app).
|
||||
|
||||
-behaviour(application).
|
||||
|
||||
-export([start/2, stop/1]).
|
||||
|
||||
start(_StartType, _StartArgs) ->
|
||||
ip2region_sup:start_link().
|
||||
|
||||
stop(_State) ->
|
||||
ok.
|
||||
|
||||
%% internal functions
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
%%%-------------------------------------------------------------------
|
||||
%% Copyright 2022 The Ip2Region Authors. All rights reserved.
|
||||
%% Use of this source code is governed by a Apache2.0-style
|
||||
%% license that can be found in the LICENSE file.
|
||||
%%
|
||||
%% @doc ip2region top level supervisor.
|
||||
%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(ip2region_sup).
|
||||
-behaviour(supervisor).
|
||||
-include("ip2region.hrl").
|
||||
|
||||
-export([start_link/0]).
|
||||
|
||||
-export([init/1, create_table/0]).
|
||||
|
||||
-define(SERVER, ?MODULE).
|
||||
|
||||
start_link() ->
|
||||
{ok, SupPid} = supervisor:start_link({local, ?SERVER}, ?MODULE, []),
|
||||
{ok, SupPid}.
|
||||
|
||||
%% sup_flags() = #{strategy => strategy(), % optional
|
||||
%% intensity => non_neg_integer(), % optional
|
||||
%% period => pos_integer()} % optional
|
||||
%% child_spec() = #{id => child_id(), % mandatory
|
||||
%% start => mfargs(), % mandatory
|
||||
%% restart => restart(), % optional
|
||||
%% shutdown => shutdown(), % optional
|
||||
%% type => worker(), % optional
|
||||
%% modules => modules()} % optional
|
||||
init([]) ->
|
||||
create_table(),
|
||||
SupFlags = #{strategy => one_for_one,
|
||||
intensity => 10,
|
||||
period => 5},
|
||||
ChildSpecs = pool_child_specs(),
|
||||
{ok, {SupFlags, ChildSpecs}}.
|
||||
|
||||
%% internal functions
|
||||
%%
|
||||
create_table() ->
|
||||
Opts = [named_table, set, public, {read_concurrency, true}, {keypos, 1}],
|
||||
%% Version-specific tables for dual-stack support
|
||||
ensure_table(?XDB_VECTOR_INDEX_V4, Opts),
|
||||
ensure_table(?XDB_VECTOR_INDEX_V6, Opts),
|
||||
ensure_table(?XDB_SEGMENT_INDEX_V4, Opts),
|
||||
ensure_table(?XDB_SEGMENT_INDEX_V6, Opts),
|
||||
ensure_table(?IP2REGION_CACHE_V4, Opts),
|
||||
ensure_table(?IP2REGION_CACHE_V6, Opts).
|
||||
|
||||
ensure_table(Name, Opts) ->
|
||||
case ets:whereis(Name) of
|
||||
undefined -> ets:new(Name, Opts);
|
||||
_ -> ok
|
||||
end.
|
||||
|
||||
pool_child_specs() ->
|
||||
{ok, DbConfig} = application:get_env(db),
|
||||
{ok, PoolArgsCfg} = application:get_env(poolargs),
|
||||
lists:foldl(
|
||||
fun({ipv4, File}, Acc) ->
|
||||
[make_pool_spec(?IP2REGION_POOL_V4, ipv4, File, PoolArgsCfg) | Acc];
|
||||
({ipv6, File}, Acc) ->
|
||||
[make_pool_spec(?IP2REGION_POOL_V6, ipv6, File, PoolArgsCfg) | Acc];
|
||||
(_, Acc) ->
|
||||
Acc
|
||||
end, [], DbConfig).
|
||||
|
||||
make_pool_spec(PoolName, Version, File, PoolArgsCfg) ->
|
||||
PoolArgs = [
|
||||
{strategy, fifo},
|
||||
{name, {local, PoolName}},
|
||||
{worker_module, ip2region_worker}
|
||||
| PoolArgsCfg
|
||||
],
|
||||
WorkerArgs = [{xdb_file, File}, {expected_version, Version}],
|
||||
poolboy:child_spec(PoolName, PoolArgs, WorkerArgs).
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
%%%-------------------------------------------------------------------
|
||||
%% Copyright 2022 The Ip2Region Authors. All rights reserved.
|
||||
%% Use of this source code is governed by a Apache2.0-style
|
||||
%% license that can be found in the LICENSE file.
|
||||
%%
|
||||
%% @doc
|
||||
%% ip2region utils: IPv4/IPv6 parsing and version detection.
|
||||
%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(ip2region_util).
|
||||
|
||||
-export([ipv4_to_n/1, ip_version/1, ip_to_bytes/1]).
|
||||
|
||||
-define(IS_UINT8(V), is_integer(V), V >= 0, V =< 255).
|
||||
-define(IS_UINT16(V), is_integer(V), V >= 0, V =< 65535).
|
||||
|
||||
-spec ip_version(Ip :: tuple() | list() | binary() | integer()) ->
|
||||
ipv4 | ipv6 | {error, atom()}.
|
||||
ip_version(Ip) when is_integer(Ip), Ip >= 0, Ip =< 16#FFFFFFFF -> ipv4;
|
||||
ip_version({A, B, C, D}) when ?IS_UINT8(A), ?IS_UINT8(B), ?IS_UINT8(C), ?IS_UINT8(D) -> ipv4;
|
||||
ip_version({A, B, C, D, E, F, G, H}) when ?IS_UINT16(A), ?IS_UINT16(B), ?IS_UINT16(C), ?IS_UINT16(D),
|
||||
?IS_UINT16(E), ?IS_UINT16(F), ?IS_UINT16(G), ?IS_UINT16(H) -> ipv6;
|
||||
ip_version(Ip) when is_binary(Ip) ->
|
||||
ip_version(binary_to_list(Ip));
|
||||
ip_version(Ip) when is_list(Ip) ->
|
||||
case inet_parse:address(Ip) of
|
||||
{ok, {_, _, _, _}} -> ipv4;
|
||||
{ok, {_, _, _, _, _, _, _, _}} -> ipv6;
|
||||
_ -> {error, bad_ip_format}
|
||||
end;
|
||||
ip_version(_) ->
|
||||
{error, bad_ip_format}.
|
||||
|
||||
-spec ip_to_bytes(Ip :: tuple() | list() | binary() | integer()) ->
|
||||
{ok, ipv4 | ipv6, binary()} | {error, atom()}.
|
||||
ip_to_bytes(Ip) when is_integer(Ip), Ip >= 0, Ip =< 16#FFFFFFFF ->
|
||||
{ok, ipv4, <<Ip:32>>};
|
||||
ip_to_bytes({A, B, C, D}) when ?IS_UINT8(A), ?IS_UINT8(B), ?IS_UINT8(C), ?IS_UINT8(D) ->
|
||||
{ok, ipv4, <<A, B, C, D>>};
|
||||
ip_to_bytes({A, B, C, D, E, F, G, H}) when ?IS_UINT16(A), ?IS_UINT16(B), ?IS_UINT16(C), ?IS_UINT16(D),
|
||||
?IS_UINT16(E), ?IS_UINT16(F), ?IS_UINT16(G), ?IS_UINT16(H) ->
|
||||
{ok, ipv6, <<A:16, B:16, C:16, D:16, E:16, F:16, G:16, H:16>>};
|
||||
ip_to_bytes(Ip) when is_binary(Ip) ->
|
||||
ip_to_bytes(binary_to_list(Ip));
|
||||
ip_to_bytes(Ip) when is_list(Ip) ->
|
||||
case inet_parse:address(Ip) of
|
||||
{ok, {A, B, C, D}} ->
|
||||
{ok, ipv4, <<A, B, C, D>>};
|
||||
{ok, {A, B, C, D, E, F, G, H}} ->
|
||||
{ok, ipv6, <<A:16, B:16, C:16, D:16, E:16, F:16, G:16, H:16>>};
|
||||
_ ->
|
||||
{error, bad_ip_format}
|
||||
end;
|
||||
ip_to_bytes(_) ->
|
||||
{error, bad_ip_format}.
|
||||
|
||||
-spec ipv4_to_n(Ip :: tuple() | list() | binary() | integer()) ->
|
||||
non_neg_integer() | {error, atom()}.
|
||||
ipv4_to_n(IntIp) when is_integer(IntIp), IntIp >= 0, IntIp =< 16#FFFFFFFF -> IntIp;
|
||||
ipv4_to_n({A, B, C, D}) when ?IS_UINT8(A), ?IS_UINT8(B), ?IS_UINT8(C), ?IS_UINT8(D) ->
|
||||
<<N:32>> = <<A, B, C, D>>,
|
||||
N;
|
||||
ipv4_to_n(Ip) when is_binary(Ip) ->
|
||||
ipv4_to_n(binary_to_list(Ip));
|
||||
ipv4_to_n(Ip) when is_list(Ip) ->
|
||||
case ip_to_bytes(Ip) of
|
||||
{ok, ipv4, <<N:32>>} -> N;
|
||||
_ -> {error, bad_ip_format}
|
||||
end;
|
||||
ipv4_to_n(_) ->
|
||||
{error, bad_ip_format}.
|
||||
|
|
@ -0,0 +1,241 @@
|
|||
%%%-------------------------------------------------------------------
|
||||
%% Copyright 2022 The Ip2Region Authors. All rights reserved.
|
||||
%% Use of this source code is governed by a Apache2.0-style
|
||||
%% license that can be found in the LICENSE file.
|
||||
%%
|
||||
%% @doc
|
||||
%% ip2region xdb client worker, now version-aware (IPv4/IPv6).
|
||||
%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(ip2region_worker).
|
||||
-behaviour(gen_server).
|
||||
-include("ip2region.hrl").
|
||||
|
||||
%% API
|
||||
-export([start/1, stop/1, start_link/1]).
|
||||
-export([search/2]).
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
|
||||
|
||||
-record(state, {
|
||||
xdb_fd,
|
||||
version :: ipv4 | ipv6
|
||||
}).
|
||||
|
||||
%%==========================================
|
||||
%% API
|
||||
%% =========================================
|
||||
start(Args) ->
|
||||
Opts = [{spawn_opt, [{min_heap_size, 6000}]}],
|
||||
gen_server:start(?MODULE, Args, Opts).
|
||||
|
||||
start_link(Args) ->
|
||||
Opts = [{spawn_opt, [{min_heap_size, 6000}]}],
|
||||
gen_server:start_link(?MODULE, Args, Opts).
|
||||
|
||||
stop(Pid) ->
|
||||
gen_server:call(Pid, stop).
|
||||
|
||||
search(Pid, Ip) ->
|
||||
gen_server:call(Pid, {search, Ip}).
|
||||
|
||||
%%==========================================
|
||||
%% gen_server callbacks
|
||||
%% =========================================
|
||||
init(Args) ->
|
||||
process_flag(trap_exit, true),
|
||||
AppName =
|
||||
case application:get_application() of
|
||||
{ok, AName} -> AName;
|
||||
_ -> ?APP_NAME
|
||||
end,
|
||||
PrivDir = code:priv_dir(AppName),
|
||||
XdbFileName =
|
||||
case proplists:get_value(xdb_file, Args) of
|
||||
undefined -> filename:join([PrivDir, "ip2region.xdb"]);
|
||||
Path ->
|
||||
case filename:pathtype(Path) of
|
||||
absolute -> Path;
|
||||
_ -> filename:join([PrivDir, Path])
|
||||
end
|
||||
end,
|
||||
error_logger:info_report(io_lib:format("XdbFile:~s~n", [XdbFileName])),
|
||||
{ok, IoDevice} = file:open(XdbFileName, [read, binary]),
|
||||
{ok, HeaderBin} = file:read(IoDevice, ?XDB_HEADER_SIZE),
|
||||
{ok, Header} = ip2region_xdb:parse_header(HeaderBin),
|
||||
ExpectedVersion = proplists:get_value(expected_version, Args),
|
||||
case resolve_version(Header) of
|
||||
{ok, Version} when ExpectedVersion =:= undefined; ExpectedVersion =:= Version ->
|
||||
load_vector_index(IoDevice, Version),
|
||||
{ok, #state{xdb_fd = IoDevice, version = Version}};
|
||||
{ok, Version} ->
|
||||
file:close(IoDevice),
|
||||
{stop, {xdb_version_mismatch, ExpectedVersion, Version}};
|
||||
{error, Reason} ->
|
||||
file:close(IoDevice),
|
||||
{stop, Reason}
|
||||
end.
|
||||
|
||||
handle_call(Request, From, State) ->
|
||||
try
|
||||
do_call(Request, From, State)
|
||||
catch
|
||||
Class:Error:Stacktrace ->
|
||||
error_logger:error_report(io_lib:format("~p handle call error, Req:~p ~p, stacktrace:~p~n",
|
||||
[?MODULE, Request, {Class, Error}, Stacktrace])),
|
||||
{reply, {error, {Class, Error}}, State}
|
||||
end.
|
||||
|
||||
handle_cast(Msg, State) ->
|
||||
try
|
||||
do_cast(Msg, State)
|
||||
catch
|
||||
Class:Error:Stacktrace ->
|
||||
error_logger:error_report(io_lib:format("~p handle cast error, Msg:~p, ~p, stacktrace:~w~n",
|
||||
[?MODULE, Msg, {Class, Error}, Stacktrace])),
|
||||
{noreply, State}
|
||||
end.
|
||||
|
||||
handle_info(Info, State) ->
|
||||
try
|
||||
do_info(Info, State)
|
||||
catch
|
||||
Class:Error:Stacktrace ->
|
||||
error_logger:error_report(io_lib:format("~p handle info error, Info:~p, ~p, stacktrace:~p~n",
|
||||
[?MODULE, Info, {Class, Error}, Stacktrace])),
|
||||
{noreply, State}
|
||||
end.
|
||||
|
||||
terminate(_Reason, State) ->
|
||||
#state{xdb_fd = XdbFd} = State,
|
||||
case is_pid(XdbFd) of
|
||||
true -> file:close(XdbFd);
|
||||
_ -> skip
|
||||
end,
|
||||
ok.
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
%%==========================================
|
||||
%% Internal function
|
||||
%% =========================================
|
||||
do_call({search, Ip}, _From, #state{xdb_fd = IoDevice} = State) ->
|
||||
Reply = search_ip(IoDevice, Ip, State),
|
||||
{reply, Reply, State};
|
||||
|
||||
do_call(stop, _From, State) ->
|
||||
{stop, normal, stopped, State};
|
||||
|
||||
do_call(Request, From, State) ->
|
||||
error_logger:error_report(io_lib:format("unknown request: ~p, from:~p", [Request, From])),
|
||||
{reply, {error, unknown_request}, State}.
|
||||
|
||||
do_cast(Msg, State) ->
|
||||
error_logger:error_report(io_lib:format("unknown msg: ~p", [Msg])),
|
||||
{noreply, State}.
|
||||
|
||||
do_info(Info, State) ->
|
||||
error_logger:error_report(io:format("unknown info: ~p", [Info])),
|
||||
{noreply, State}.
|
||||
|
||||
-spec resolve_version(#xdb_header{}) -> {ok, ipv4 | ipv6} | {error, term()}.
|
||||
resolve_version(Header) ->
|
||||
case ip2region_xdb:header_version(Header) of
|
||||
2 -> {ok, ipv4};
|
||||
3 ->
|
||||
case ip2region_xdb:header_ip_version(Header) of
|
||||
?IP_VERSION_4 -> {ok, ipv4};
|
||||
?IP_VERSION_6 -> {ok, ipv6};
|
||||
V -> {error, {invalid_xdb_ip_version, V}}
|
||||
end;
|
||||
V -> {error, {invalid_xdb_version, V}}
|
||||
end.
|
||||
|
||||
load_vector_index(IoDevice, Version) ->
|
||||
%% The vector index ETS table is global and shared by all pool workers.
|
||||
%% Only the first worker (or the first worker after a restart) populates it.
|
||||
%% Subsequent workers skip the file:read here; search_ip/2 uses file:pread/3,
|
||||
%% so the current file pointer position does not affect correctness.
|
||||
Table = ip2region_xdb:vector_index_table(Version),
|
||||
case ets:info(Table, size) of
|
||||
?XDB_VECTOR_INDEX_COUNT ->
|
||||
ok;
|
||||
undefined ->
|
||||
Opts = [named_table, set, public, {read_concurrency, true}, {keypos, 1}],
|
||||
ets:new(Table, Opts),
|
||||
load_vector_index_data(IoDevice, Table);
|
||||
_ ->
|
||||
%% A previous worker may have crashed midway; reload to be safe.
|
||||
load_vector_index_data(IoDevice, Table)
|
||||
end.
|
||||
|
||||
load_vector_index_data(IoDevice, Table) ->
|
||||
{ok, VectorIndexBin} =
|
||||
file:read(IoDevice, ?XDB_VECTOR_INDEX_COUNT * ?XDB_VECTOR_INDEX_SIZE),
|
||||
load_vector_index_aux(VectorIndexBin, 0, Table).
|
||||
|
||||
load_vector_index_aux(<<>>, _Index, _Table) -> ok;
|
||||
load_vector_index_aux(<<SPtr:32/little, EPtr:32/little, VectorIndexBin/binary>>, Index, Table) ->
|
||||
ets:insert(Table, {Index, SPtr, EPtr}),
|
||||
load_vector_index_aux(VectorIndexBin, Index + 1, Table).
|
||||
|
||||
search_ip(IoDevice, Ip, #state{version = Version}) ->
|
||||
SegSize = ip2region_xdb:segment_index_size(Version),
|
||||
VectorTable = ip2region_xdb:vector_index_table(Version),
|
||||
SegmentTable = ip2region_xdb:segment_index_table(Version),
|
||||
<<A:8, B:8, _/binary>> = Ip,
|
||||
VectorIdx = A * ?XDB_VECTOR_COLS + B,
|
||||
[{_, SPtr, EPtr}] = ets:lookup(VectorTable, VectorIdx),
|
||||
search_ip(IoDevice, Ip, SPtr, EPtr, 0,
|
||||
(EPtr - SPtr) div SegSize, SegSize, Version, SegmentTable).
|
||||
|
||||
search_ip(IoDevice, Ip, SPtr, EPtr, Low, High, SegSize, Version, SegmentTable) when Low =< High ->
|
||||
Middle = (Low + High) bsr 1,
|
||||
SPtr2 = SPtr + Middle * SegSize,
|
||||
{SIp, EIp, DataLen, DataPtr} = read_segment_index(IoDevice, SPtr2, SegSize, SegmentTable),
|
||||
case ip_in_range(Ip, SIp, EIp, Version) of
|
||||
below ->
|
||||
search_ip(IoDevice, Ip, SPtr, EPtr, Low, Middle - 1, SegSize, Version, SegmentTable);
|
||||
above ->
|
||||
search_ip(IoDevice, Ip, SPtr, EPtr, Middle + 1, High, SegSize, Version, SegmentTable);
|
||||
inside ->
|
||||
{ok, DataBin} = read_file(IoDevice, DataPtr, DataLen),
|
||||
unicode:characters_to_nfc_list(DataBin)
|
||||
end;
|
||||
search_ip(_IoDevice, _Ip, _SPtr, _EPtr, _Low, _High, _SegSize, _Version, _SegmentTable) ->
|
||||
{error, unknown}.
|
||||
|
||||
ip_in_range(Ip, SIp, EIp, ipv4) ->
|
||||
<<InputInt:32>> = Ip,
|
||||
if
|
||||
InputInt < SIp -> below;
|
||||
InputInt > EIp -> above;
|
||||
true -> inside
|
||||
end;
|
||||
ip_in_range(Ip, SIp, EIp, ipv6) ->
|
||||
if
|
||||
Ip < SIp -> below;
|
||||
Ip > EIp -> above;
|
||||
true -> inside
|
||||
end.
|
||||
|
||||
read_file(IoDevice, Position, DataLength) ->
|
||||
file:pread(IoDevice, Position, DataLength).
|
||||
|
||||
read_segment_index(IoDevice, SPtr, SegSize, SegmentTable) ->
|
||||
case ets:lookup(SegmentTable, SPtr) of
|
||||
[{_SPtr, SIp, EIp, DataLen, DataPtr}] ->
|
||||
{SIp, EIp, DataLen, DataPtr};
|
||||
_ ->
|
||||
{ok, Bin} = read_file(IoDevice, SPtr, SegSize),
|
||||
{SIp, EIp, DataLen, DataPtr} = decode_segment_index(Bin, SegSize),
|
||||
ets:insert_new(SegmentTable, {SPtr, SIp, EIp, DataLen, DataPtr}),
|
||||
{SIp, EIp, DataLen, DataPtr}
|
||||
end.
|
||||
|
||||
decode_segment_index(Bin, ?XDB_SEGMENT_INDEX_SIZE_V4) ->
|
||||
<<SIp:32/little, EIp:32/little, DataLen:16/little, DataPtr:32/little>> = Bin,
|
||||
{SIp, EIp, DataLen, DataPtr};
|
||||
decode_segment_index(Bin, ?XDB_SEGMENT_INDEX_SIZE_V6) ->
|
||||
<<SIp:16/binary, EIp:16/binary, DataLen:16/little, DataPtr:32/little>> = Bin,
|
||||
{SIp, EIp, DataLen, DataPtr}.
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
%%%-------------------------------------------------------------------
|
||||
%% Copyright 2022 The Ip2Region Authors. All rights reserved.
|
||||
%% Use of this source code is governed by a Apache2.0-style
|
||||
%% license that can be found in the LICENSE file.
|
||||
%%
|
||||
%% @doc
|
||||
%% ip2region xdb binary format helpers.
|
||||
%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(ip2region_xdb).
|
||||
|
||||
-export([parse_header/1]).
|
||||
-export([header_version/1, header_index_policy/1, header_created_at/1,
|
||||
header_start_index_ptr/1, header_end_index_ptr/1,
|
||||
header_ip_version/1, header_runtime_ptr_bytes/1]).
|
||||
-export([segment_index_size/1]).
|
||||
-export([vector_index_table/1, segment_index_table/1, cache_table/1]).
|
||||
|
||||
-include("ip2region.hrl").
|
||||
|
||||
-spec parse_header(binary()) -> {ok, #xdb_header{}} | {error, invalid_header}.
|
||||
parse_header(Bin) when is_binary(Bin), byte_size(Bin) >= 20 ->
|
||||
<<Version:16/little, IndexPolicy:16/little, CreatedAt:32/little,
|
||||
StartIndexPtr:32/little, EndIndexPtr:32/little,
|
||||
IpVersion:16/little, RuntimePtrBytes:16/little, _/binary>> = Bin,
|
||||
{ok, #xdb_header{
|
||||
version = Version,
|
||||
index_policy = IndexPolicy,
|
||||
created_at = CreatedAt,
|
||||
start_index_ptr = StartIndexPtr,
|
||||
end_index_ptr = EndIndexPtr,
|
||||
ip_version = IpVersion,
|
||||
runtime_ptr_bytes = RuntimePtrBytes
|
||||
}};
|
||||
parse_header(_) ->
|
||||
{error, invalid_header}.
|
||||
|
||||
header_version(#xdb_header{version = V}) -> V.
|
||||
header_index_policy(#xdb_header{index_policy = P}) -> P.
|
||||
header_created_at(#xdb_header{created_at = T}) -> T.
|
||||
header_start_index_ptr(#xdb_header{start_index_ptr = P}) -> P.
|
||||
header_end_index_ptr(#xdb_header{end_index_ptr = P}) -> P.
|
||||
header_ip_version(#xdb_header{ip_version = V}) -> V.
|
||||
header_runtime_ptr_bytes(#xdb_header{runtime_ptr_bytes = B}) -> B.
|
||||
|
||||
-spec segment_index_size(ipv4 | ipv6) -> pos_integer().
|
||||
segment_index_size(ipv4) -> ?XDB_SEGMENT_INDEX_SIZE_V4;
|
||||
segment_index_size(ipv6) -> ?XDB_SEGMENT_INDEX_SIZE_V6.
|
||||
|
||||
-spec vector_index_table(ipv4 | ipv6) -> atom().
|
||||
vector_index_table(ipv4) -> ?XDB_VECTOR_INDEX_V4;
|
||||
vector_index_table(ipv6) -> ?XDB_VECTOR_INDEX_V6.
|
||||
|
||||
-spec segment_index_table(ipv4 | ipv6) -> atom().
|
||||
segment_index_table(ipv4) -> ?XDB_SEGMENT_INDEX_V4;
|
||||
segment_index_table(ipv6) -> ?XDB_SEGMENT_INDEX_V6.
|
||||
|
||||
-spec cache_table(ipv4 | ipv6) -> atom().
|
||||
cache_table(ipv4) -> ?IP2REGION_CACHE_V4;
|
||||
cache_table(ipv6) -> ?IP2REGION_CACHE_V6.
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
%%%-------------------------------------------------------------------
|
||||
%% Copyright 2022 The Ip2Region Authors. All rights reserved.
|
||||
%% Use of this source code is governed by a Apache2.0-style
|
||||
%% license that can be found in the LICENSE file.
|
||||
%%
|
||||
%% @doc
|
||||
%% ip2region xdb client search api with IPv4/IPv6 auto-routing.
|
||||
%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(xdb).
|
||||
-include("ip2region.hrl").
|
||||
|
||||
-export([search/1]).
|
||||
|
||||
-spec search(Ip :: tuple() | list() | binary() | integer()) ->
|
||||
Result :: list() | {error, term()}.
|
||||
search(Ip) ->
|
||||
case ip2region_util:ip_to_bytes(Ip) of
|
||||
{ok, ipv4, IpBin} ->
|
||||
do_search(?IP2REGION_POOL_V4, ipv4, IpBin);
|
||||
{ok, ipv6, IpBin} ->
|
||||
do_search(?IP2REGION_POOL_V6, ipv6, IpBin);
|
||||
Ret ->
|
||||
Ret
|
||||
end.
|
||||
|
||||
do_search(PoolName, Version, IpBin) ->
|
||||
CacheTable = ip2region_xdb:cache_table(Version),
|
||||
case ets:lookup(CacheTable, IpBin) of
|
||||
[{_, Region}] -> Region;
|
||||
_ ->
|
||||
case whereis(PoolName) of
|
||||
undefined ->
|
||||
{error, pool_not_configured};
|
||||
_ ->
|
||||
Worker = poolboy:checkout(PoolName, true, infinity),
|
||||
try
|
||||
case ip2region_worker:search(Worker, IpBin) of
|
||||
{error, _} = Err -> Err;
|
||||
Region ->
|
||||
ets:insert(CacheTable, {IpBin, Region}),
|
||||
Region
|
||||
end
|
||||
after
|
||||
poolboy:checkin(PoolName, Worker)
|
||||
end
|
||||
end
|
||||
end.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue