Merge branch 'master' of github.com:lionsoul2014/ip2region

This commit is contained in:
lionsoul2014 2025-11-21 22:44:41 +08:00
commit e95452c1c8
23 changed files with 681 additions and 269 deletions

View File

@ -0,0 +1,25 @@
root = true
# To learn more about .editorconfig see https://aka.ms/editorconfigdocs
[*]
charset = utf-8
indent_style = space
trim_trailing_whitespace = true
insert_final_newline = true
spelling_exclusion_path = .\exclusion.dic
[*.csproj]
charset = utf-8
insert_final_newline = true
[*.{xml,config,csproj,nuspec,props,resx,targets,yml,tasks,json}]
indent_size = 2
[*.sh]
end_of_line = lf
[*.cs]
csharp_style_namespace_declarations = file_scoped:silent
[*.cs]
file_header_template = Copyright 2025 The Ip2Region Authors. All rights reserved.\nUse of this source code is governed by a Apache2.0-style\nlicense that can be found in the LICENSE file.\n@Author Alan <lzh.shap@gmail.com>\n@Date 2023/07/25\nUpdated by Argo Zhang <argo@live.ca> at 2025/11/21

View File

@ -0,0 +1,50 @@
// 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;
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";
[Benchmark]
[BenchmarkCategory("IPv4")]
public void ContentIPv4() => _contentV4Searcher.Search(_testIPv4Address);
[Benchmark]
[BenchmarkCategory("IPv4")]
public void VectorIPv4() => _vectorV4Searcher.Search(_testIPv4Address);
[Benchmark]
[BenchmarkCategory("IPv4")]
public void FileIPv4() => _fileV4Searcher.Search(_testIPv4Address);
[Benchmark]
[BenchmarkCategory("IPv6")]
public void ContentIPv6() => _contentV6Searcher.Search(_testIPv6Address);
[Benchmark]
[BenchmarkCategory("IPv6")]
public void VectorIPv6() => _vectorV6Searcher.Search(_testIPv6Address);
[Benchmark]
[BenchmarkCategory("IPv6")]
public void FileIPv6() => _fileV6Searcher.Search(_testIPv6Address);
}

View File

@ -2,14 +2,14 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.13.2" /> <PackageReference Include="BenchmarkDotNet" Version="0.15.6" />
<PackageReference Include="BenchmarkDotNet.Annotations" Version="0.13.2" /> <PackageReference Include="BenchmarkDotNet.Annotations" Version="0.15.6" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@ -21,6 +21,10 @@
<Link>IP2Region/ip2region_v4.xdb</Link> <Link>IP2Region/ip2region_v4.xdb</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
</ItemGroup> <Content Include="..\..\..\data\ip2region_v6.xdb">
<Link>IP2Region/ip2region_v6.xdb</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project> </Project>

View File

@ -1,29 +1,11 @@
using BenchmarkDotNet.Attributes; // Copyright 2025 The Ip2Region Authors. All rights reserved.
// Use of this source code is governed by a Apache2.0-style
// license that can be found in the LICENSE file.
// @Author Alan <lzh.shap@gmail.com>
// @Date 2023/07/25
// Updated by Argo Zhang <argo@live.ca> at 2025/11/21
using BenchmarkDotNet.Running; using BenchmarkDotNet.Running;
using IP2Region.Net.Abstractions; using IP2Region.Net.BenchMark;
using IP2Region.Net.XDB;
BenchmarkRunner.Run(typeof(Program).Assembly); BenchmarkRunner.Run<Benchmarks>();
public class CachePolicyCompare
{
private static readonly string XdbPath = Path.Combine(AppContext.BaseDirectory, "IP2Region", "ip2region_v4.xdb");
private readonly ISearcher _contentSearcher = new Searcher(CachePolicy.Content, XdbPath);
private readonly ISearcher _vectorSearcher = new Searcher(CachePolicy.VectorIndex,XdbPath);
private readonly ISearcher _fileSearcher = new Searcher(CachePolicy.File,XdbPath);
private readonly string _testIpAddress = "114.114.114.114";
[Benchmark]
[BenchmarkCategory(nameof(CachePolicy.Content))]
public void CachePolicy_Content() => _contentSearcher.Search(_testIpAddress);
[Benchmark]
[BenchmarkCategory(nameof(CachePolicy.VectorIndex))]
public void CachePolicy_VectorIndex() => _vectorSearcher.Search(_testIpAddress);
[Benchmark]
[BenchmarkCategory(nameof(CachePolicy.File))]
public void CachePolicy_File() => _fileSearcher.Search(_testIpAddress);
}

View File

@ -1,40 +1,51 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
<PackageReference Include="NUnit" Version="4.4.0" /> <PackageReference Include="xunit" Version="2.*" />
<PackageReference Include="NUnit3TestAdapter" Version="5.2.0" /> <PackageReference Include="xunit.runner.visualstudio" Version="3.*">
<PackageReference Include="NUnit.Analyzers" Version="4.10.0"> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference>
</PackageReference> <PackageReference Include="coverlet.collector" Version="6.0.4">
<PackageReference Include="coverlet.collector" Version="6.0.4"> <PrivateAssets>all</PrivateAssets>
<PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference>
</PackageReference> </ItemGroup>
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\IP2Region.Net\IP2Region.Net.csproj" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Content Include="..\..\..\data\ipv4_source.txt"> <ProjectReference Include="..\IP2Region.Net\IP2Region.Net.csproj" />
<Link>TestData/ipv4_source.txt</Link> </ItemGroup>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> <ItemGroup>
<Content Include="..\..\..\data\ip2region_v4.xdb"> <Content Include="..\..\..\data\ipv4_source.txt">
<Link>TestData/ip2region_v4.xdb</Link> <Link>TestData/ipv4_source.txt</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
</ItemGroup> <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> </Project>

View File

@ -1,80 +1,161 @@
// Copyright 2025 The Ip2Region Authors. All rights reserved.
// Use of this source code is governed by a Apache2.0-style
// license that can be found in the LICENSE file.
// @Author Alan <lzh.shap@gmail.com>
// @Date 2023/07/25
// Updated by Argo Zhang <argo@live.ca> at 2025/11/21
using IP2Region.Net.Abstractions;
using IP2Region.Net.XDB; using IP2Region.Net.XDB;
using Microsoft.Extensions.DependencyInjection;
using System.Net;
using Xunit;
namespace IP2Region.Net.Test; namespace IP2Region.Net.Test;
[TestFixture]
public class SearcherTest public class SearcherTest
{ {
private readonly string _xdbPath = Path.Combine(AppContext.BaseDirectory, "TestData", "ip2region_v4.xdb"); private readonly string _xdbPathV4 = Path.Combine(AppContext.BaseDirectory, "TestData", "ip2region_v4.xdb");
private readonly string _xdbPathV6 = Path.Combine(AppContext.BaseDirectory, "TestData", "ip2region_v6.xdb");
public static IEnumerable<string> Ips()
{
yield return "114.114.114.114";
yield return "119.29.29.29";
yield return "223.5.5.5";
yield return "180.76.76.76";
yield return "8.8.8.8";
}
[TestCaseSource(nameof(Ips))] [Theory]
[Parallelizable(ParallelScope.All)] [InlineData("58.251.27.201", "中国|广东省|深圳市|联通", "v4")]
public void TestSearchCacheContent(string ip) [InlineData("114.114.114.114", "中国|江苏省|南京市|0", "v4")]
[InlineData("119.29.29.29", "中国|北京|北京市|腾讯", "v4")]
[InlineData("223.5.5.5", "中国|浙江省|杭州市|阿里云", "v4")]
[InlineData("180.76.76.76", "中国|北京|北京市|百度", "v4")]
[InlineData("8.8.8.8", "美国|0|0|Level3", "v4")]
[InlineData("240e:3b7:3272:d8d0:db09:c067:8d59:539e", "中国|广东省|深圳市|家庭宽带", "v6")]
public void TestSearchCacheContent(string ip, string expected, string version)
{ {
var contentSearcher = new Searcher(CachePolicy.Content,_xdbPath); var _xdbPath = version == "v4" ? _xdbPathV4 : _xdbPathV6;
var contentSearcher = new Searcher(CachePolicy.Content, _xdbPath);
var region = contentSearcher.Search(ip); var region = contentSearcher.Search(ip);
Console.WriteLine(region); Assert.Equal(expected, region);
} }
[TestCaseSource(nameof(Ips))] [Theory]
[Parallelizable(ParallelScope.All)] [InlineData("58.251.27.201", "中国|广东省|深圳市|联通", "v4")]
public void TestSearchCacheVector(string ip) [InlineData("114.114.114.114", "中国|江苏省|南京市|0", "v4")]
[InlineData("119.29.29.29", "中国|北京|北京市|腾讯", "v4")]
[InlineData("223.5.5.5", "中国|浙江省|杭州市|阿里云", "v4")]
[InlineData("180.76.76.76", "中国|北京|北京市|百度", "v4")]
[InlineData("8.8.8.8", "美国|0|0|Level3", "v4")]
[InlineData("240e:3b7:3272:d8d0:db09:c067:8d59:539e", "中国|广东省|深圳市|家庭宽带", "v6")]
public void TestSearchCacheVector(string ip, string expected, string version)
{ {
var vectorSearcher = new Searcher(CachePolicy.VectorIndex,_xdbPath); var _xdbPath = version == "v4" ? _xdbPathV4 : _xdbPathV6;
var vectorSearcher = new Searcher(CachePolicy.VectorIndex, _xdbPath);
var region = vectorSearcher.Search(ip); var region = vectorSearcher.Search(ip);
Console.WriteLine(region); Assert.Equal(expected, region);
} }
[TestCaseSource(nameof(Ips))] [Theory]
[Parallelizable(ParallelScope.All)] [InlineData("58.251.0.0", "中国|广东省|深圳市|联通", "v4")]
public void TestSearchCacheFile(string ip) [InlineData("58.251.255.255", "中国|广东省|深圳市|联通", "v4")]
[InlineData("58.251.27.201", "中国|广东省|深圳市|联通", "v4")]
[InlineData("114.114.114.114", "中国|江苏省|南京市|0", "v4")]
[InlineData("119.29.29.29", "中国|北京|北京市|腾讯", "v4")]
[InlineData("223.5.5.5", "中国|浙江省|杭州市|阿里云", "v4")]
[InlineData("180.76.76.76", "中国|北京|北京市|百度", "v4")]
[InlineData("8.8.8.8", "美国|0|0|Level3", "v4")]
[InlineData("240e:3b7:3272:d8d0:db09:c067:8d59:539e", "中国|广东省|深圳市|家庭宽带", "v6")]
[InlineData("240e:044d:2d00:0000:0000:0000:0000:0000", "美国|加利福尼亚州|洛杉矶|移动网络", "v6")]
public void TestSearchCacheFile(string ip, string expected, string version)
{ {
var fileSearcher = new Searcher(CachePolicy.File,_xdbPath); var _xdbPath = version == "v4" ? _xdbPathV4 : _xdbPathV6;
var fileSearcher = new Searcher(CachePolicy.File, _xdbPath);
var region = fileSearcher.Search(ip); var region = fileSearcher.Search(ip);
Console.WriteLine(region); Assert.Equal(expected, region);
} }
[TestCase(CachePolicy.Content)] [Fact]
[TestCase(CachePolicy.VectorIndex)] public void IoCount_File_Ok()
[TestCase(CachePolicy.File)]
public void TestBenchSearch(CachePolicy cachePolicy)
{ {
Searcher searcher = new Searcher(cachePolicy,_xdbPath); var searcher = new Searcher(CachePolicy.File, _xdbPathV4);
var srcPath = Path.Combine(AppContext.BaseDirectory, "TestData", "ipv4_source.txt"); searcher.Search("58.251.27.201");
Assert.Equal(3, searcher.IoCount);
searcher.Search("58.251.27.201");
Assert.Equal(3, searcher.IoCount);
}
[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);
}
[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);
}
[Theory]
[InlineData("58.251.255.255", "中国|广东省|深圳市|联通")]
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);
var ip = XDB.Util.IpAddressToUInt32(ipAddress);
region = fileSearcher.Search(ip);
Assert.Equal(expected, region);
}
[Theory]
[InlineData("58.251.255.255", "中国|广东省|深圳市|联通")]
public void AddIP2RegionService_Ok(string ipStr, string expected)
{
var services = new ServiceCollection();
services.AddIP2RegionService(_xdbPathV4, CachePolicy.File);
var provider = services.BuildServiceProvider();
var searcher = provider.GetRequiredService<ISearcher>();
var region = searcher.Search(ipStr);
Assert.Equal(expected, region);
searcher = provider.GetRequiredKeyedService<ISearcher>("IP2Region.Net");
region = searcher.Search(ipStr);
Assert.Equal(expected, region);
}
[Theory]
[InlineData(CachePolicy.Content, "v4")]
[InlineData(CachePolicy.VectorIndex, "v4")]
[InlineData(CachePolicy.File, "v4")]
[InlineData(CachePolicy.Content, "v6")]
[InlineData(CachePolicy.VectorIndex, "v6")]
[InlineData(CachePolicy.File, "v6")]
public void TestBenchSearch(CachePolicy cachePolicy, string version)
{
var _xdbPath = version == "v4" ? _xdbPathV4 : _xdbPathV6;
var searcher = new Searcher(cachePolicy, _xdbPath);
var srcPath = Path.Combine(AppContext.BaseDirectory, "TestData", $"ip{version}_source.txt");
foreach (var line in File.ReadLines(srcPath)) foreach (var line in File.ReadLines(srcPath))
{ {
var ps = line.Trim().Split("|", 3); var ps = line.Trim().Split("|", 3);
var sip = ps[0];
var eip = ps[1];
if (ps.Length != 3) var s1 = searcher.Search(sip);
{ var s2 = searcher.Search(eip);
throw new ArgumentException($"invalid ip segment line {line}", nameof(line)); Assert.Equal(s1, ps[2]);
} Assert.Equal(s2, ps[2]);
var sip = Util.IpAddressToUInt32(ps[0]);
var eip = Util.IpAddressToUInt32(ps[1]);
var mip = Util.GetMidIp(sip, eip);
uint[] temp = { sip, Util.GetMidIp(sip, mip), mip, Util.GetMidIp(mip, eip), eip };
foreach (var ip in temp)
{
var region = searcher.Search(ip);
if (region != ps[2])
{
throw new Exception($"failed search {ip} with ({region}!={ps[2]})");
}
}
} }
} }
} }

View File

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

View File

@ -1,14 +1,27 @@
using IP2Region.Net.XDB; // Copyright 2025 The Ip2Region Authors. All rights reserved.
// Use of this source code is governed by a Apache2.0-style
// license that can be found in the LICENSE file.
// @Author Alan <lzh.shap@gmail.com>
// @Date 2023/07/25
// Updated by Argo Zhang <argo@live.ca> at 2025/11/21
using Xunit;
namespace IP2Region.Net.Test; namespace IP2Region.Net.Test;
[TestFixture]
public class UtilTest public class UtilTest
{ {
[TestCase("114.114.114.114")] [Fact]
public void TestIpAddressToUInt32(string value) public void IpAddressToUInt32_Ok()
{ {
var uintIp = XDB.Util.IpAddressToUInt32(value); var uintIp = XDB.Util.IpAddressToUInt32("114.114.114.114");
Console.WriteLine(uintIp); Assert.Equal((uint)1920103026, uintIp);
}
[Fact]
public void GetMidIp_Ok()
{
var uintIp = XDB.Util.GetMidIp(1, 10);
Assert.Equal((uint)5, uintIp);
} }
} }

View File

@ -0,0 +1,49 @@
// Copyright 2025 The Ip2Region Authors. All rights reserved.
// Use of this source code is governed by a Apache2.0-style
// license that can be found in the LICENSE file.
// @Author Alan <lzh.shap@gmail.com>
// @Date 2023/07/25
// Updated by Argo Zhang <argo@live.ca> at 2025/11/21
using Xunit;
namespace IP2Region.Net.Test;
public class XdbTest
{
[Fact]
public async Task VersionIPV4_Ok()
{
var db = Path.Combine(AppContext.BaseDirectory, "TestData", $"ip2region_v4.xdb");
var version = await XDB.Util.GetVersionAsync(db);
Assert.Equal(3, version.Ver);
Assert.Equal(1, version.CachePolice);
Assert.Equal("2025-09-06 02:24:16", version.CreatedTime.ToString("yyyy-MM-dd HH:mm:ss"));
Assert.Equal((uint)955933, version.StartIndex);
Assert.Equal((uint)11042415, version.EndIndex);
Assert.Equal(4, version.IPVer);
Assert.Equal(4, version.BytesCount);
}
[Fact]
public async Task VersionIPV6_Ok()
{
var db = Path.Combine(AppContext.BaseDirectory, "TestData", $"ip2region_v6.xdb");
var version = await XDB.Util.GetVersionAsync(db);
Assert.Equal(3, version.Ver);
Assert.Equal(1, version.CachePolice);
Assert.Equal("2025-10-17 04:41:04", version.CreatedTime.ToString("yyyy-MM-dd HH:mm:ss"));
Assert.Equal((uint)3094259, version.StartIndex);
Assert.Equal((uint)36258303, version.EndIndex);
Assert.Equal(6, version.IPVer);
Assert.Equal(4, version.BytesCount);
}
[Fact]
public async Task GetVersionAsync_Error()
{
await Assert.ThrowsAsync<ArgumentNullException>(async () => await XDB.Util.GetVersionAsync(null!));
await Assert.ThrowsAsync<FileNotFoundException>(async () => await XDB.Util.GetVersionAsync(Path.Combine(AppContext.BaseDirectory, "test.xdb")));
}
}

View File

@ -1,5 +1,6 @@
<Solution> <Solution>
<Folder Name="/Solution Items/"> <Folder Name="/Solution Items/">
<File Path=".editorconfig" />
<File Path="README.md" /> <File Path="README.md" />
</Folder> </Folder>
<Project Path="IP2Region.Net.BenchMark/IP2Region.Net.BenchMark.csproj" /> <Project Path="IP2Region.Net.BenchMark/IP2Region.Net.BenchMark.csproj" />

View File

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

View File

@ -1,4 +1,11 @@
using IP2Region.Net.Abstractions; // Copyright 2025 The Ip2Region Authors. All rights reserved.
// Use of this source code is governed by a Apache2.0-style
// license that can be found in the LICENSE file.
// @Author Alan <lzh.shap@gmail.com>
// @Date 2023/07/25
// Updated by Argo Zhang <argo@live.ca> at 2025/11/21
using IP2Region.Net.Abstractions;
using IP2Region.Net.XDB; using IP2Region.Net.XDB;
using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.DependencyInjection.Extensions;

View File

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<id>IP2Region.Net</id> <id>IP2Region.Net</id>
<version>2.1.0</version> <version>2.1.1</version>
<title>IP2Region.Net</title> <title>IP2Region.Net</title>
<authors>Alan Lee</authors> <authors>Alan Lee</authors>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression> <PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
@ -15,7 +15,7 @@
<RepositoryType>git</RepositoryType> <RepositoryType>git</RepositoryType>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<TargetFrameworks>netstandard2.0;netstandard2.1;net6.0;net7.0;net8.0;net9.0</TargetFrameworks> <TargetFrameworks>netstandard2.0;netstandard2.1;net6.0;net7.0;net8.0;net9.0;net10.0</TargetFrameworks>
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
<UserSecretsId>c2f07fe1-a300-4de3-8200-1278ed8cb5b7</UserSecretsId> <UserSecretsId>c2f07fe1-a300-4de3-8200-1278ed8cb5b7</UserSecretsId>
</PropertyGroup> </PropertyGroup>
@ -30,30 +30,34 @@
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'"> <ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.0" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.*" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.1'"> <ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.1'">
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.0" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net6.0'"> <ItemGroup Condition="'$(TargetFramework)' == 'net6.0'">
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.*" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'"> <ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.*" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'"> <ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.0" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.*" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net9.0'"> <ItemGroup Condition="'$(TargetFramework)' == 'net9.0'">
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.0" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.*" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup Condition="'$(TargetFramework)' == 'net10.0'">
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.*" />
</ItemGroup>
<ItemGroup>
<None Include="..\CHANGELOG.md"> <None Include="..\CHANGELOG.md">
<Link>CHANGELOG.md</Link> <Link>CHANGELOG.md</Link>
</None> </None>

View File

@ -1,42 +1,33 @@
// Copyright 2023 The Ip2Region Authors. All rights reserved. // Copyright 2025 The Ip2Region Authors. All rights reserved.
// Use of this source code is governed by a Apache2.0-style // Use of this source code is governed by a Apache2.0-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// @Author Alan <lzh.shap@gmail.com> // @Author Alan <lzh.shap@gmail.com>
// @Date 2023/07/25 // @Date 2023/07/25
// Updated by Argo Zhang <argo@live.ca> at 2025/11/21
using System.Buffers; using System.Buffers;
namespace IP2Region.Net.Internal.Abstractions; namespace IP2Region.Net.Internal.Abstractions;
internal abstract class AbstractCacheStrategy internal abstract class AbstractCacheStrategy(string xdbPath)
{ {
protected const int HeaderInfoLength = 256; protected const int HeaderInfoLength = 256;
protected const int VectorIndexRows = 256;
protected const int VectorIndexCols = 256;
protected const int VectorIndexSize = 8; protected const int VectorIndexSize = 8;
protected readonly FileStream XdbFileStream; private const int BufferSize = 64 * 1024;
private const int BufferSize = 4096;
internal int IoCount { get; private set; } public int IoCount { get; private set; }
protected AbstractCacheStrategy(string xdbPath) protected FileStream XdbFileStream = new(xdbPath, FileMode.Open, FileAccess.Read, FileShare.Read, BufferSize, FileOptions.RandomAccess);
public void ResetIoCount()
{ {
XdbFileStream = new FileStream(xdbPath, FileMode.Open, FileAccess.Read, FileShare.Read, BufferSize, IoCount = 0;
useAsync: true);
} }
protected int GetVectorIndexStartPos(uint ip) public virtual ReadOnlyMemory<byte> GetVectorIndex(int offset) => GetData(HeaderInfoLength + offset, VectorIndexSize);
{
var il0 = ip >> 24 & 0xFF;
var il1 = ip >> 16 & 0xFF;
var idx = il0 * VectorIndexCols * VectorIndexSize + il1 * VectorIndexSize;
return (int)idx;
}
internal abstract ReadOnlyMemory<byte> GetVectorIndex(uint ip); public virtual ReadOnlyMemory<byte> GetData(long offset, int length)
internal virtual ReadOnlyMemory<byte> GetData(int offset, int length)
{ {
byte[] buffer = ArrayPool<byte>.Shared.Rent(length); byte[] buffer = ArrayPool<byte>.Shared.Rent(length);
int totalBytesRead = 0; int totalBytesRead = 0;
@ -45,14 +36,13 @@ internal abstract class AbstractCacheStrategy
XdbFileStream.Seek(offset, SeekOrigin.Begin); XdbFileStream.Seek(offset, SeekOrigin.Begin);
int bytesRead; int bytesRead;
do while (totalBytesRead < length)
{ {
int bytesToRead = Math.Min(BufferSize, length - totalBytesRead); bytesRead = XdbFileStream.Read(buffer, totalBytesRead, length);
bytesRead = XdbFileStream.Read(buffer, totalBytesRead, bytesToRead);
totalBytesRead += bytesRead; totalBytesRead += bytesRead;
IoCount++; IoCount++;
} while (bytesRead > 0 && totalBytesRead < length); }
} }
finally finally
{ {
@ -61,4 +51,4 @@ internal abstract class AbstractCacheStrategy
return new ReadOnlyMemory<byte>(buffer, 0, totalBytesRead); return new ReadOnlyMemory<byte>(buffer, 0, totalBytesRead);
} }
} }

View File

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

View File

@ -1,14 +1,15 @@
// Copyright 2023 The Ip2Region Authors. All rights reserved. // Copyright 2025 The Ip2Region Authors. All rights reserved.
// Use of this source code is governed by a Apache2.0-style // Use of this source code is governed by a Apache2.0-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// @Author Alan <lzh.shap@gmail.com> // @Author Alan <lzh.shap@gmail.com>
// @Date 2023/07/25 // @Date 2023/07/25
// Updated by Argo Zhang <argo@live.ca> at 2025/11/21
using IP2Region.Net.Internal.Abstractions; using IP2Region.Net.Internal.Abstractions;
namespace IP2Region.Net.Internal; namespace IP2Region.Net.Internal;
internal class ContentCacheStrategy : AbstractCacheStrategy class ContentCacheStrategy : AbstractCacheStrategy
{ {
private readonly ReadOnlyMemory<byte> _cacheData; private readonly ReadOnlyMemory<byte> _cacheData;
@ -19,14 +20,5 @@ internal class ContentCacheStrategy : AbstractCacheStrategy
XdbFileStream.Dispose(); XdbFileStream.Dispose();
} }
internal override ReadOnlyMemory<byte> GetVectorIndex(uint ip) public override ReadOnlyMemory<byte> GetData(long offset = 0, int length = 0) => _cacheData.Slice((int)offset, length);
{ }
int idx = GetVectorIndexStartPos(ip);
return _cacheData.Slice(HeaderInfoLength + idx, VectorIndexSize);
}
internal override ReadOnlyMemory<byte> GetData(int offset, int length)
{
return _cacheData.Slice(offset, length);
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -1,8 +1,20 @@
// Copyright 2025 The Ip2Region Authors. All rights reserved.
// Use of this source code is governed by a Apache2.0-style
// license that can be found in the LICENSE file.
// @Author Alan <lzh.shap@gmail.com>
// @Date 2023/07/25
// Updated by Argo Zhang <argo@live.ca> at 2025/11/21
using System.Buffers;
using System.Buffers.Binary;
using System.Net; using System.Net;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace IP2Region.Net.XDB; namespace IP2Region.Net.XDB;
/// <summary>
/// 工具类
/// </summary>
public static class Util public static class Util
{ {
public static uint IpAddressToUInt32(string ipAddress) public static uint IpAddressToUInt32(string ipAddress)
@ -10,7 +22,7 @@ public static class Util
var address = IPAddress.Parse(ipAddress); var address = IPAddress.Parse(ipAddress);
return IpAddressToUInt32(address); return IpAddressToUInt32(address);
} }
public static uint IpAddressToUInt32(IPAddress ipAddress) public static uint IpAddressToUInt32(IPAddress ipAddress)
{ {
byte[] bytes = ipAddress.GetAddressBytes(); byte[] bytes = ipAddress.GetAddressBytes();
@ -20,4 +32,60 @@ public static class Util
public static uint GetMidIp(uint x, uint y) public static uint GetMidIp(uint x, uint y)
=> (x & y) + ((x ^ y) >> 1); => (x & y) + ((x ^ y) >> 1);
}
public static async Task<XdbVersion> GetVersionAsync(string dbPath, CancellationToken token = default)
{
if (string.IsNullOrEmpty(dbPath))
{
throw new ArgumentNullException(nameof(dbPath));
}
if (!File.Exists(dbPath))
{
throw new FileNotFoundException("xdb file not found.", dbPath);
}
using var reader = File.OpenRead(dbPath);
return await GetVersionAsync(reader, token);
}
internal static async Task<XdbVersion> GetVersionAsync(FileStream reader, CancellationToken token = default)
{
XdbVersion ret = default;
var buffer = ArrayPool<byte>.Shared.Rent(256);
try
{
var length = await reader.ReadAsync(buffer, 0, 256, token);
if (length == 256)
{
ret = Parse(buffer);
}
}
finally
{
ArrayPool<byte>.Shared.Return(buffer);
}
return ret;
}
private static XdbVersion Parse(ReadOnlySpan<byte> buffer)
{
var ret = new XdbVersion
{
Ver = BinaryPrimitives.ReadUInt16LittleEndian(buffer.Slice(0, 2)),
CachePolice = BinaryPrimitives.ReadUInt16LittleEndian(buffer.Slice(2, 2)),
StartIndex = BinaryPrimitives.ReadUInt32LittleEndian(buffer.Slice(8, 4)),
EndIndex = BinaryPrimitives.ReadUInt32LittleEndian(buffer.Slice(12, 4)),
IPVer = BinaryPrimitives.ReadUInt16LittleEndian(buffer.Slice(16, 2)),
BytesCount = BinaryPrimitives.ReadUInt16LittleEndian(buffer.Slice(18, 2))
};
var createdAt = BinaryPrimitives.ReadUInt32LittleEndian(buffer.Slice(4, 4));
var dtm = new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.FromHours(0));
ret.CreatedTime = dtm.AddSeconds(createdAt);
return ret;
}
}

View File

@ -0,0 +1,49 @@
// Copyright 2025 The Ip2Region Authors. All rights reserved.
// Use of this source code is governed by a Apache2.0-style
// license that can be found in the LICENSE file.
// @Author Alan <lzh.shap@gmail.com>
// @Date 2023/07/25
// Updated by Argo Zhang <argo@live.ca> at 2025/11/21
namespace IP2Region.Net.XDB;
/// <summary>
/// XdbVersion 结构体
/// </summary>
public struct XdbVersion
{
/// <summary>
/// 获得/设置 版本号
/// </summary>
public ushort Ver { get; set; }
/// <summary>
/// 获得/设置 缓存策略
/// </summary>
public ushort CachePolice { get; set; }
/// <summary>
/// 获得/设置 文件生成时间
/// </summary>
public DateTimeOffset CreatedTime { get; set; }
/// <summary>
/// 获得/设置 索引起始地址
/// </summary>
public uint StartIndex { get; set; }
/// <summary>
/// 获得/设置 索引结束地址
/// </summary>
public uint EndIndex { get; set; }
/// <summary>
/// 获得/设置 IP版本
/// </summary>
public ushort IPVer { get; set; }
/// <summary>
/// 获得/设置 指针字节数
/// </summary>
public ushort BytesCount { get; set; }
}

View File

@ -43,20 +43,19 @@ NET8+ support keyed service
provider.GetRequiredKeyedService<ISearcher>("IP2Region.Net"); provider.GetRequiredKeyedService<ISearcher>("IP2Region.Net");
``` ```
## TargetFrameworks
netstandard2.0;netstandard2.1;net6.0;net7.0;net8.0;net9.0;net10.0
## Performance ## Performance
``` ini | Method | Mean | Error | StdDev | Median |
BenchmarkDotNet=v0.13.2, OS=macOS 13.4.1 (c) (22F770820d) [Darwin 22.5.0] |------------ |------------:|----------:|------------:|------------:|
Apple M1, 1 CPU, 8 logical and 8 physical cores | ContentIPv4 | 101.5 ns | 2.04 ns | 4.57 ns | 103.1 ns |
.NET SDK=7.0.306 | VectorIPv4 | 7,488.1 ns | 222.91 ns | 657.26 ns | 7,819.2 ns |
[Host] : .NET 6.0.20 (6.0.2023.32017), Arm64 RyuJIT AdvSIMD | FileIPv4 | 11,686.9 ns | 59.81 ns | 55.95 ns | 11,707.6 ns |
DefaultJob : .NET 6.0.20 (6.0.2023.32017), Arm64 RyuJIT AdvSIMD | ContentIPv6 | 296.1 ns | 1.84 ns | 1.72 ns | 296.2 ns |
``` | VectorIPv6 | 15,025.1 ns | 938.17 ns | 2,766.21 ns | 16,642.9 ns |
| Method | Mean | Error | StdDev | | FileIPv6 | 19,721.0 ns | 807.55 ns | 2,381.08 ns | 20,905.7 ns |
|------------------------ |-------------:|----------:|----------:|
| CachePolicy_Content | 58.32 ns | 0.182 ns | 0.170 ns |
| CachePolicy_File | 16,417.56 ns | 50.569 ns | 47.302 ns |
| CachePolicy_VectorIndex | 9,348.11 ns | 38.492 ns | 65.363 ns |
## Contributing ## Contributing
Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
@ -64,4 +63,4 @@ Pull requests are welcome. For major changes, please open an issue first to disc
Please make sure to update tests as appropriate. Please make sure to update tests as appropriate.
## License ## License
[Apache License 2.0](https://github.com/lionsoul2014/ip2region/blob/master/LICENSE.md) [Apache License 2.0](https://github.com/lionsoul2014/ip2region/blob/master/LICENSE.md)