173 lines
5.4 KiB
Markdown
173 lines
5.4 KiB
Markdown
# 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
|