From 9a14e327360994248f381dfc7641630561457c23 Mon Sep 17 00:00:00 2001 From: lion Date: Tue, 16 Sep 2025 12:35:22 +0800 Subject: [PATCH] add xdb file verify util and call it before the search/bench test --- binding/php/ReadMe.md | 21 +++++++++-- binding/php/bench_test.php | 10 ++++++ binding/php/search_test.php | 10 ++++++ binding/php/xdb/Searcher.class.php | 56 +++++++++++++++++++++++++++--- 4 files changed, 90 insertions(+), 7 deletions(-) diff --git a/binding/php/ReadMe.md b/binding/php/ReadMe.md index c00a073..b9227c4 100644 --- a/binding/php/ReadMe.md +++ b/binding/php/ReadMe.md @@ -2,8 +2,8 @@ # 使用方式 -第三方 composer 地址 (请自行确认仓库对 IPv6 的支持): -1. [https://github.com/zoujingli/ip2region](https://github.com/zoujingli/ip2region) +第三方 composer 地址: +1. [https://github.com/zoujingli/ip2region](https://github.com/zoujingli/ip2region) - 已提供 IPv6 支持。 2. [https://github.com/chinayin/ip2region-core-php](https://github.com/chinayin/ip2region-core-php) @@ -24,6 +24,23 @@ $version = IPv6::default(); // 备注:以下演示直接使用 $dbFile 和 $version 变量 ``` +### XDB 文件验证 +建议您主动去验证 xdb 文件的适用性,因为后期的一些新功能可能会导致目前的 Searcher 版本无法适用你使用的 xdb 文件,验证可以避免运行过程中的一些不可预测的错误。 你不需要每次都去验证,例如在服务启动的时候,或者手动调用命令验证确认版本匹配即可,不要在每次创建的 Searcher 的时候运行验证,这样会影响查询的响应速度,尤其是高并发的使用场景。 +```php +use \ip2region\xdb\Util; + +$err = Util::verify($dbFile); +if ($err != null) { + // 适用性验证失败!!! + // 当前查询客户端实现不适用于 dbPath 指定的 xdb 文件的查询. + // 应该停止启动服务,使用合适的 xdb 文件或者升级到适合 dbPath 的 Searcher 实现。 + printf("failed to verify xdb file `%s`: %s\n", $dbFile, $err); + return; +} + +// 验证通过,当前使用的 Searcher 可以安全的用于对 dbPath 指向的 xdb 的查询操作 +``` + ### 完全基于文件的查询 ```php // require or autoload the xdb\Searcher diff --git a/binding/php/bench_test.php b/binding/php/bench_test.php index 75fa111..20e5309 100644 --- a/binding/php/bench_test.php +++ b/binding/php/bench_test.php @@ -70,6 +70,16 @@ if ($handle === false) { return; } +// verify the xdb file +// @Note: do NOT call it every time you create a searcher since this will slow +// down the search response. +// @see the Util.verify function for details. +$err = Util::verify($handle); +if ($err != null) { + printf("failed to verify xdb file `%s`: %s\n", $dbFile, $err); + return; +} + // load header $header = Util::loadHeader($handle); if ($header == null) { diff --git a/binding/php/search_test.php b/binding/php/search_test.php index 6874af8..6d73322 100644 --- a/binding/php/search_test.php +++ b/binding/php/search_test.php @@ -66,6 +66,16 @@ if ($handle === false) { return; } +// verify the xdb file +// @Note: do NOT call it every time you create a searcher since this will slow +// down the search response. +// @see the Util.verify function for details. +$err = Util::verify($handle); +if ($err != null) { + printf("failed to verify xdb file `%s`: %s\n", $dbFile, $err); + return; +} + // load header $header = Util::loadHeader($handle); if ($header == null) { diff --git a/binding/php/xdb/Searcher.class.php b/binding/php/xdb/Searcher.class.php index 24c7ac5..7b598f1 100644 --- a/binding/php/xdb/Searcher.class.php +++ b/binding/php/xdb/Searcher.class.php @@ -126,6 +126,57 @@ class Util { return ((ord($b[$idx])) | (ord($b[$idx+1]) << 8)); } + // 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. + // returns: null for everything is ok or the error string. + public static function verify($handle) { + // load the header + $header = self::loadHeader($handle); + if ($header == null) { + return 'failed to load the header'; + } + + // get the runtime ptr bytes + $runtimePtrBytes = 0; + if ($header['version'] == Structure_20) { + $runtimePtrBytes = 4; + } else if ($header['version'] == Structure_30) { + $runtimePtrBytes = $header['runtimePtrBytes']; + } else { + return "invalid structure version `{$header['version']}`"; + } + + // 1, confirm the xdb file size + // to ensure that the maximum file pointer does not overflow + $stat = fstat($handle); + if ($stat == false) { + return 'failed to stat the xdb file'; + } + + $maxFilePtr = (1 << ($runtimePtrBytes * 8)) - 1; + // print_r([$stat['size'], $maxFilePtr]); + if ($stat['size'] > $maxFilePtr) { + return "xdb file exceeds the maximum supported bytes: {$maxFilePtr}"; + } + + return null; + } + + public static function verifyFromFile($dbFile) { + $handle = fopen($dbFile, 'r'); + if ($handle === false) { + return null; + } + + $r = self::verify($handle); + fclose($handle); + return $r; + } + // load header info from a specified file handle public static function loadHeader($handle) { if (fseek($handle, 0) == -1) { @@ -473,11 +524,6 @@ class Searcher { throw new Exception("failed to read segment index with ptr={$p}"); } - // printf("l=%d, h=%d, sip=%s, eip=%s\n", - // $l, $h, - // Util::ipToString(strrev(substr($buff, 0, 4))), - // Util::ipToString(strrev(substr($buff, 4, 4))) - // ); if ($this->version->ipSubCompare($ipBytes, $buff, 0) < 0) { $h = $m - 1; } else if ($this->version->ipSubCompare($ipBytes, $buff, $bytes) > 0) {