mirror of
https://github.com/lionsoul2014/ip2region.git
synced 2026-01-25 17:16:11 +00:00
Merge branch 'master' of github.com:lionsoul2014/ip2region
This commit is contained in:
commit
e95452c1c8
25
binding/csharp/.editorconfig
Normal file
25
binding/csharp/.editorconfig
Normal 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
|
||||
50
binding/csharp/IP2Region.Net.BenchMark/Benmarks.cs
Normal file
50
binding/csharp/IP2Region.Net.BenchMark/Benmarks.cs
Normal 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);
|
||||
}
|
||||
@ -2,14 +2,14 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.13.2" />
|
||||
<PackageReference Include="BenchmarkDotNet.Annotations" Version="0.13.2" />
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.15.6" />
|
||||
<PackageReference Include="BenchmarkDotNet.Annotations" Version="0.15.6" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@ -21,6 +21,10 @@
|
||||
<Link>IP2Region/ip2region_v4.xdb</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<Content Include="..\..\..\data\ip2region_v6.xdb">
|
||||
<Link>IP2Region/ip2region_v6.xdb</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@ -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 IP2Region.Net.Abstractions;
|
||||
using IP2Region.Net.XDB;
|
||||
using IP2Region.Net.BenchMark;
|
||||
|
||||
BenchmarkRunner.Run(typeof(Program).Assembly);
|
||||
|
||||
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);
|
||||
}
|
||||
BenchmarkRunner.Run<Benchmarks>();
|
||||
|
||||
@ -1,40 +1,51 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.0" />
|
||||
<PackageReference Include="NUnit" Version="4.4.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="5.2.0" />
|
||||
<PackageReference Include="NUnit.Analyzers" Version="4.10.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</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.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>
|
||||
<ProjectReference Include="..\IP2Region.Net\IP2Region.Net.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0" />
|
||||
</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>
|
||||
</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>
|
||||
|
||||
@ -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 Microsoft.Extensions.DependencyInjection;
|
||||
using System.Net;
|
||||
using Xunit;
|
||||
|
||||
namespace IP2Region.Net.Test;
|
||||
|
||||
[TestFixture]
|
||||
public class SearcherTest
|
||||
{
|
||||
private readonly string _xdbPath = Path.Combine(AppContext.BaseDirectory, "TestData", "ip2region_v4.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";
|
||||
}
|
||||
private readonly string _xdbPathV4 = Path.Combine(AppContext.BaseDirectory, "TestData", "ip2region_v4.xdb");
|
||||
private readonly string _xdbPathV6 = Path.Combine(AppContext.BaseDirectory, "TestData", "ip2region_v6.xdb");
|
||||
|
||||
[TestCaseSource(nameof(Ips))]
|
||||
[Parallelizable(ParallelScope.All)]
|
||||
public void TestSearchCacheContent(string ip)
|
||||
[Theory]
|
||||
[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")]
|
||||
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);
|
||||
Console.WriteLine(region);
|
||||
Assert.Equal(expected, region);
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(Ips))]
|
||||
[Parallelizable(ParallelScope.All)]
|
||||
public void TestSearchCacheVector(string ip)
|
||||
[Theory]
|
||||
[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")]
|
||||
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);
|
||||
Console.WriteLine(region);
|
||||
Assert.Equal(expected, region);
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(Ips))]
|
||||
[Parallelizable(ParallelScope.All)]
|
||||
public void TestSearchCacheFile(string ip)
|
||||
[Theory]
|
||||
[InlineData("58.251.0.0", "中国|广东省|深圳市|联通", "v4")]
|
||||
[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);
|
||||
Console.WriteLine(region);
|
||||
Assert.Equal(expected, region);
|
||||
}
|
||||
|
||||
[TestCase(CachePolicy.Content)]
|
||||
[TestCase(CachePolicy.VectorIndex)]
|
||||
[TestCase(CachePolicy.File)]
|
||||
public void TestBenchSearch(CachePolicy cachePolicy)
|
||||
[Fact]
|
||||
public void IoCount_File_Ok()
|
||||
{
|
||||
Searcher searcher = new Searcher(cachePolicy,_xdbPath);
|
||||
var srcPath = Path.Combine(AppContext.BaseDirectory, "TestData", "ipv4_source.txt");
|
||||
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);
|
||||
}
|
||||
|
||||
[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))
|
||||
{
|
||||
var ps = line.Trim().Split("|", 3);
|
||||
var sip = ps[0];
|
||||
var eip = ps[1];
|
||||
|
||||
if (ps.Length != 3)
|
||||
{
|
||||
throw new ArgumentException($"invalid ip segment line {line}", nameof(line));
|
||||
}
|
||||
|
||||
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]})");
|
||||
}
|
||||
}
|
||||
var s1 = searcher.Search(sip);
|
||||
var s2 = searcher.Search(eip);
|
||||
Assert.Equal(s1, ps[2]);
|
||||
Assert.Equal(s2, ps[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1 +0,0 @@
|
||||
global using NUnit.Framework;
|
||||
@ -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;
|
||||
|
||||
[TestFixture]
|
||||
public class UtilTest
|
||||
{
|
||||
[TestCase("114.114.114.114")]
|
||||
public void TestIpAddressToUInt32(string value)
|
||||
[Fact]
|
||||
public void IpAddressToUInt32_Ok()
|
||||
{
|
||||
var uintIp = XDB.Util.IpAddressToUInt32(value);
|
||||
Console.WriteLine(uintIp);
|
||||
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);
|
||||
}
|
||||
}
|
||||
49
binding/csharp/IP2Region.Net.Test/XdbTest.cs
Normal file
49
binding/csharp/IP2Region.Net.Test/XdbTest.cs
Normal 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")));
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
<Solution>
|
||||
<Folder Name="/Solution Items/">
|
||||
<File Path=".editorconfig" />
|
||||
<File Path="README.md" />
|
||||
</Folder>
|
||||
<Project Path="IP2Region.Net.BenchMark/IP2Region.Net.BenchMark.csproj" />
|
||||
|
||||
@ -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
|
||||
// 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
|
||||
{
|
||||
/// <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>
|
||||
string? Search(uint ipAddress);
|
||||
|
||||
/// <summary>
|
||||
/// 获得 内部 IO 访问次数
|
||||
/// </summary>
|
||||
int IoCount { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<id>IP2Region.Net</id>
|
||||
<version>2.1.0</version>
|
||||
<version>2.1.1</version>
|
||||
<title>IP2Region.Net</title>
|
||||
<authors>Alan Lee</authors>
|
||||
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
|
||||
@ -15,7 +15,7 @@
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<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>
|
||||
<UserSecretsId>c2f07fe1-a300-4de3-8200-1278ed8cb5b7</UserSecretsId>
|
||||
</PropertyGroup>
|
||||
@ -30,30 +30,34 @@
|
||||
</ItemGroup>
|
||||
|
||||
<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 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 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 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 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 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 Condition="'$(TargetFramework)' == 'net10.0'">
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.*" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="..\CHANGELOG.md">
|
||||
<Link>CHANGELOG.md</Link>
|
||||
</None>
|
||||
|
||||
@ -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
|
||||
// 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.Abstractions;
|
||||
|
||||
internal abstract class AbstractCacheStrategy
|
||||
internal abstract class AbstractCacheStrategy(string xdbPath)
|
||||
{
|
||||
protected const int HeaderInfoLength = 256;
|
||||
protected const int VectorIndexRows = 256;
|
||||
protected const int VectorIndexCols = 256;
|
||||
protected const int VectorIndexSize = 8;
|
||||
|
||||
protected readonly FileStream XdbFileStream;
|
||||
private const int BufferSize = 4096;
|
||||
private const int BufferSize = 64 * 1024;
|
||||
|
||||
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,
|
||||
useAsync: true);
|
||||
IoCount = 0;
|
||||
}
|
||||
|
||||
protected int GetVectorIndexStartPos(uint ip)
|
||||
{
|
||||
var il0 = ip >> 24 & 0xFF;
|
||||
var il1 = ip >> 16 & 0xFF;
|
||||
var idx = il0 * VectorIndexCols * VectorIndexSize + il1 * VectorIndexSize;
|
||||
return (int)idx;
|
||||
}
|
||||
public virtual ReadOnlyMemory<byte> GetVectorIndex(int offset) => GetData(HeaderInfoLength + offset, VectorIndexSize);
|
||||
|
||||
internal abstract ReadOnlyMemory<byte> GetVectorIndex(uint ip);
|
||||
|
||||
internal virtual ReadOnlyMemory<byte> GetData(int offset, int length)
|
||||
public virtual ReadOnlyMemory<byte> GetData(long offset, int length)
|
||||
{
|
||||
byte[] buffer = ArrayPool<byte>.Shared.Rent(length);
|
||||
int totalBytesRead = 0;
|
||||
@ -45,14 +36,13 @@ internal abstract class AbstractCacheStrategy
|
||||
XdbFileStream.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
int bytesRead;
|
||||
do
|
||||
while (totalBytesRead < length)
|
||||
{
|
||||
int bytesToRead = Math.Min(BufferSize, length - totalBytesRead);
|
||||
bytesRead = XdbFileStream.Read(buffer, totalBytesRead, bytesToRead);
|
||||
bytesRead = XdbFileStream.Read(buffer, totalBytesRead, length);
|
||||
totalBytesRead += bytesRead;
|
||||
|
||||
|
||||
IoCount++;
|
||||
} while (bytesRead > 0 && totalBytesRead < length);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
@ -61,4 +51,4 @@ internal abstract class AbstractCacheStrategy
|
||||
|
||||
return new ReadOnlyMemory<byte>(buffer, 0, totalBytesRead);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
// 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.Internal.Abstractions;
|
||||
using IP2Region.Net.XDB;
|
||||
|
||||
namespace IP2Region.Net.Internal;
|
||||
|
||||
internal class CacheStrategyFactory
|
||||
class CacheStrategyFactory(string xdbPath)
|
||||
{
|
||||
private readonly string _xdbPath;
|
||||
|
||||
public CacheStrategyFactory(string xdbPath)
|
||||
public AbstractCacheStrategy CreateCacheStrategy(CachePolicy cachePolicy) => cachePolicy switch
|
||||
{
|
||||
_xdbPath = 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))
|
||||
};
|
||||
}
|
||||
}
|
||||
CachePolicy.Content => new ContentCacheStrategy(xdbPath),
|
||||
CachePolicy.VectorIndex => new VectorIndexCacheStrategy(xdbPath),
|
||||
_ => new FileCacheStrategy(xdbPath),
|
||||
};
|
||||
}
|
||||
|
||||
@ -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
|
||||
// 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.Internal.Abstractions;
|
||||
|
||||
namespace IP2Region.Net.Internal;
|
||||
|
||||
internal class ContentCacheStrategy : AbstractCacheStrategy
|
||||
class ContentCacheStrategy : AbstractCacheStrategy
|
||||
{
|
||||
private readonly ReadOnlyMemory<byte> _cacheData;
|
||||
|
||||
@ -19,14 +20,5 @@ internal class ContentCacheStrategy : AbstractCacheStrategy
|
||||
XdbFileStream.Dispose();
|
||||
}
|
||||
|
||||
internal override ReadOnlyMemory<byte> GetVectorIndex(uint ip)
|
||||
{
|
||||
int idx = GetVectorIndexStartPos(ip);
|
||||
return _cacheData.Slice(HeaderInfoLength + idx, VectorIndexSize);
|
||||
}
|
||||
|
||||
internal override ReadOnlyMemory<byte> GetData(int offset, int length)
|
||||
{
|
||||
return _cacheData.Slice(offset, length);
|
||||
}
|
||||
}
|
||||
public override ReadOnlyMemory<byte> GetData(long offset = 0, int length = 0) => _cacheData.Slice((int)offset, length);
|
||||
}
|
||||
|
||||
@ -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
|
||||
// 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.Internal.Abstractions;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
// 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.Internal.Abstractions;
|
||||
|
||||
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)
|
||||
{
|
||||
var vectorLength = VectorIndexRows * VectorIndexCols * VectorIndexSize;
|
||||
_vectorIndex = base.GetData(HeaderInfoLength, vectorLength);
|
||||
XdbFileStream.Seek(HeaderInfoLength, SeekOrigin.Begin);
|
||||
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)
|
||||
{
|
||||
var idx = GetVectorIndexStartPos(ip);
|
||||
return _vectorIndex.Slice(idx, VectorIndexSize);
|
||||
}
|
||||
}
|
||||
public override ReadOnlyMemory<byte> GetVectorIndex(int offset) => _vectorCache.Slice(offset, VectorIndexSize);
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
/// <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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
// license that can be found in the LICENSE file.
|
||||
// @Author Alan Lee <lzh.shap@gmail.com>
|
||||
// @Date 2023/07/23
|
||||
// @Author Alan <lzh.shap@gmail.com>
|
||||
// @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.Internal;
|
||||
using IP2Region.Net.Internal.Abstractions;
|
||||
using System.Buffers.Binary;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
|
||||
namespace IP2Region.Net.XDB;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="ISearcher"/> 实现类
|
||||
/// </summary>
|
||||
public class Searcher : ISearcher
|
||||
{
|
||||
const int SegmentIndexSize = 14;
|
||||
|
||||
private readonly AbstractCacheStrategy _cacheStrategy;
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
public int IoCount => _cacheStrategy.IoCount;
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
public Searcher(CachePolicy cachePolicy, string dbPath)
|
||||
{
|
||||
var factory = new CacheStrategyFactory(dbPath);
|
||||
_cacheStrategy = factory.CreateCacheStrategy(cachePolicy);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
public string? Search(string ipStr)
|
||||
{
|
||||
var ip = Util.IpAddressToUInt32(ipStr);
|
||||
return Search(ip);
|
||||
var ipAddress = IPAddress.Parse(ipStr);
|
||||
return SearchCore(ipAddress.GetAddressBytes());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
public string? Search(IPAddress ipAddress)
|
||||
{
|
||||
var ip = Util.IpAddressToUInt32(ipAddress);
|
||||
return Search(ip);
|
||||
return SearchCore(ipAddress.GetAddressBytes());
|
||||
}
|
||||
|
||||
public string? Search(uint ip)
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
public string? Search(uint ipAddress)
|
||||
{
|
||||
var index = _cacheStrategy.GetVectorIndex(ip);
|
||||
uint sPtr = MemoryMarshal.Read<uint>(index.Span);
|
||||
uint ePtr = MemoryMarshal.Read<uint>(index.Span.Slice(4));
|
||||
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));
|
||||
|
||||
var length = ipBytes.Length;
|
||||
var indexSize = length * 2 + 6;
|
||||
var l = 0;
|
||||
var h = (ePtr - sPtr) / indexSize;
|
||||
var dataLen = 0;
|
||||
uint dataPtr = 0;
|
||||
uint l = 0;
|
||||
uint h = (ePtr -sPtr) / SegmentIndexSize;
|
||||
long dataPtr = 0;
|
||||
|
||||
while (l <= h)
|
||||
{
|
||||
var mid = Util.GetMidIp(l, h);
|
||||
var pos = sPtr + mid * SegmentIndexSize;
|
||||
int m = (int)(l + h) >> 1;
|
||||
|
||||
var buffer = _cacheStrategy.GetData((int)pos, SegmentIndexSize);
|
||||
uint sip = MemoryMarshal.Read<uint>(buffer.Span);
|
||||
uint eip = MemoryMarshal.Read<uint>(buffer.Span.Slice(4));
|
||||
var p = (int)sPtr + m * indexSize;
|
||||
var buff = _cacheStrategy.GetData(p, indexSize);
|
||||
|
||||
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
|
||||
{
|
||||
dataLen = MemoryMarshal.Read<ushort>(buffer.Span.Slice(8));
|
||||
dataPtr = MemoryMarshal.Read<uint>(buffer.Span.Slice(10));
|
||||
dataLen = BinaryPrimitives.ReadUInt16LittleEndian(buff.Span.Slice(length * 2, 2));
|
||||
dataPtr = BinaryPrimitives.ReadUInt32LittleEndian(buff.Span.Slice(length * 2 + 2, 4));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (dataLen == 0)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
var regionBuff = _cacheStrategy.GetData((int)dataPtr,dataLen);
|
||||
var regionBuff = _cacheStrategy.GetData((int)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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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.Runtime.InteropServices;
|
||||
|
||||
namespace IP2Region.Net.XDB;
|
||||
|
||||
/// <summary>
|
||||
/// 工具类
|
||||
/// </summary>
|
||||
public static class Util
|
||||
{
|
||||
public static uint IpAddressToUInt32(string ipAddress)
|
||||
@ -10,7 +22,7 @@ public static class Util
|
||||
var address = IPAddress.Parse(ipAddress);
|
||||
return IpAddressToUInt32(address);
|
||||
}
|
||||
|
||||
|
||||
public static uint IpAddressToUInt32(IPAddress ipAddress)
|
||||
{
|
||||
byte[] bytes = ipAddress.GetAddressBytes();
|
||||
@ -20,4 +32,60 @@ public static class Util
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
49
binding/csharp/IP2Region.Net/XDB/XdbVersion.cs
Normal file
49
binding/csharp/IP2Region.Net/XDB/XdbVersion.cs
Normal 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; }
|
||||
}
|
||||
@ -43,20 +43,19 @@ NET8+ support keyed service
|
||||
provider.GetRequiredKeyedService<ISearcher>("IP2Region.Net");
|
||||
```
|
||||
|
||||
## TargetFrameworks
|
||||
netstandard2.0;netstandard2.1;net6.0;net7.0;net8.0;net9.0;net10.0
|
||||
|
||||
## Performance
|
||||
|
||||
``` ini
|
||||
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
|
||||
.NET SDK=7.0.306
|
||||
[Host] : .NET 6.0.20 (6.0.2023.32017), Arm64 RyuJIT AdvSIMD
|
||||
DefaultJob : .NET 6.0.20 (6.0.2023.32017), Arm64 RyuJIT AdvSIMD
|
||||
```
|
||||
| Method | Mean | Error | StdDev |
|
||||
|------------------------ |-------------:|----------:|----------:|
|
||||
| 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 |
|
||||
| Method | Mean | Error | StdDev | Median |
|
||||
|------------ |------------:|----------:|------------:|------------:|
|
||||
| ContentIPv4 | 101.5 ns | 2.04 ns | 4.57 ns | 103.1 ns |
|
||||
| VectorIPv4 | 7,488.1 ns | 222.91 ns | 657.26 ns | 7,819.2 ns |
|
||||
| FileIPv4 | 11,686.9 ns | 59.81 ns | 55.95 ns | 11,707.6 ns |
|
||||
| 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 |
|
||||
| FileIPv6 | 19,721.0 ns | 807.55 ns | 2,381.08 ns | 20,905.7 ns |
|
||||
|
||||
## Contributing
|
||||
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.
|
||||
|
||||
## 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)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user