├── .actrc ├── .gitattributes ├── .github └── workflows │ ├── build.yml │ └── release.yml ├── .gitignore ├── .vscode ├── launch.json ├── settings.json ├── solution-explorer │ ├── class.cs-template │ ├── class.ts-template │ ├── class.vb-template │ ├── default.ts-template │ ├── enum.cs-template │ ├── interface.cs-template │ ├── interface.ts-template │ ├── template-list.json │ └── template-parameters.js └── tasks.json ├── Build.ps1 ├── LICENSE ├── QQWry.DependencyInjection ├── QQWry.DependencyInjection.csproj └── QQWryServiceCollectionExtensions.cs ├── QQWry ├── IIpSearch.cs ├── IpLocation.cs ├── QQWry.csproj ├── QQWryIpSearch.cs └── QQWryOptions.cs ├── QQWrySln.sln ├── QQWrySln.sln.DotSettings ├── QQWryTest ├── QQWry.Test.csproj └── QQWryIpSearchTest.cs ├── README.md ├── Sample ├── BenchmarkTest.cs ├── Java2QQWry.cs ├── Mode2 │ ├── IpDbAccessor.cs │ └── QQWryIpSearchMode2.cs ├── Program.cs └── Sample.csproj └── qqwry.dat /.actrc: -------------------------------------------------------------------------------- 1 | -P ubuntu-latest=catthehacker/ubuntu:act-latest 2 | -P ubuntu-20.04=catthehacker/ubuntu:act-20.04 3 | -P ubuntu-18.04=catthehacker/ubuntu:act-18.04 4 | ubuntu-16.04=catthehacker/ubuntu:act-16.04 -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | 17 | - name: Install libicu-dev 18 | run: sudo apt-get update;sudo apt-get install libicu-dev -y 19 | 20 | - name: Setup .NET 21 | uses: actions/setup-dotnet@v1 22 | with: 23 | dotnet-version: '6.0.x' 24 | 25 | - name: Build and Test 26 | shell: pwsh 27 | run: | 28 | .\Build.ps1 -version "1.0.0" 29 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*.*.*' # Push events to matching v*, i.e. v1.0, v20.15.10 7 | 8 | # Allows you to run this workflow manually from the Actions tab 9 | workflow_dispatch: 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - name: Checkout code 17 | uses: actions/checkout@v2 18 | 19 | - name: Get the version 20 | id: get_version 21 | shell: bash 22 | run: echo "VERSION=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_OUTPUT 23 | 24 | - name: Setup .NET Core 25 | uses: actions/setup-dotnet@v1 26 | with: 27 | dotnet-version: '6.0.x' 28 | 29 | - name: Build and Test and Pack 30 | shell: pwsh 31 | run: | 32 | .\Build.ps1 -version ${{ steps.get_version.outputs.VERSION }} -nugetKey ${{ secrets.NugetKey }} 33 | 34 | 35 | - name: Create Release 36 | id: action-gh-release 37 | uses: softprops/action-gh-release@v1 38 | if: startsWith(github.ref, 'refs/tags/') 39 | env: 40 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 41 | with: 42 | name: Release ${{ steps.get_version.outputs.VERSION }} 43 | prerelease: false 44 | draft: false 45 | generate_release_notes: true 46 | files: | 47 | ./artifacts/QQWry.${{ steps.get_version.outputs.VERSION }}.nupkg 48 | ./artifacts/QQWry.DependencyInjection.${{ steps.get_version.outputs.VERSION }}.nupkg 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015/2017 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # Visual Studio 2017 auto generated files 33 | Generated\ Files/ 34 | 35 | # MSTest test Results 36 | [Tt]est[Rr]esult*/ 37 | [Bb]uild[Ll]og.* 38 | 39 | # NUNIT 40 | *.VisualState.xml 41 | TestResult.xml 42 | 43 | # Build Results of an ATL Project 44 | [Dd]ebugPS/ 45 | [Rr]eleasePS/ 46 | dlldata.c 47 | 48 | # Benchmark Results 49 | BenchmarkDotNet.Artifacts/ 50 | 51 | # .NET Core 52 | project.lock.json 53 | project.fragment.lock.json 54 | artifacts/ 55 | **/Properties/launchSettings.json 56 | 57 | # StyleCop 58 | StyleCopReport.xml 59 | 60 | # Files built by Visual Studio 61 | *_i.c 62 | *_p.c 63 | *_i.h 64 | *.ilk 65 | *.meta 66 | *.obj 67 | *.iobj 68 | *.pch 69 | *.pdb 70 | *.ipdb 71 | *.pgc 72 | *.pgd 73 | *.rsp 74 | *.sbr 75 | *.tlb 76 | *.tli 77 | *.tlh 78 | *.tmp 79 | *.tmp_proj 80 | *.log 81 | *.vspscc 82 | *.vssscc 83 | .builds 84 | *.pidb 85 | *.svclog 86 | *.scc 87 | 88 | # Chutzpah Test files 89 | _Chutzpah* 90 | 91 | # Visual C++ cache files 92 | ipch/ 93 | *.aps 94 | *.ncb 95 | *.opendb 96 | *.opensdf 97 | *.sdf 98 | *.cachefile 99 | *.VC.db 100 | *.VC.VC.opendb 101 | 102 | # Visual Studio profiler 103 | *.psess 104 | *.vsp 105 | *.vspx 106 | *.sap 107 | 108 | # Visual Studio Trace Files 109 | *.e2e 110 | 111 | # TFS 2012 Local Workspace 112 | $tf/ 113 | 114 | # Guidance Automation Toolkit 115 | *.gpState 116 | 117 | # ReSharper is a .NET coding add-in 118 | _ReSharper*/ 119 | *.[Rr]e[Ss]harper 120 | *.DotSettings.user 121 | 122 | # JustCode is a .NET coding add-in 123 | .JustCode 124 | 125 | # TeamCity is a build add-in 126 | _TeamCity* 127 | 128 | # DotCover is a Code Coverage Tool 129 | *.dotCover 130 | 131 | # AxoCover is a Code Coverage Tool 132 | .axoCover/* 133 | !.axoCover/settings.json 134 | 135 | # Visual Studio code coverage results 136 | *.coverage 137 | *.coveragexml 138 | 139 | # NCrunch 140 | _NCrunch_* 141 | .*crunch*.local.xml 142 | nCrunchTemp_* 143 | 144 | # MightyMoose 145 | *.mm.* 146 | AutoTest.Net/ 147 | 148 | # Web workbench (sass) 149 | .sass-cache/ 150 | 151 | # Installshield output folder 152 | [Ee]xpress/ 153 | 154 | # DocProject is a documentation generator add-in 155 | DocProject/buildhelp/ 156 | DocProject/Help/*.HxT 157 | DocProject/Help/*.HxC 158 | DocProject/Help/*.hhc 159 | DocProject/Help/*.hhk 160 | DocProject/Help/*.hhp 161 | DocProject/Help/Html2 162 | DocProject/Help/html 163 | 164 | # Click-Once directory 165 | publish/ 166 | 167 | # Publish Web Output 168 | *.[Pp]ublish.xml 169 | *.azurePubxml 170 | # Note: Comment the next line if you want to checkin your web deploy settings, 171 | # but database connection strings (with potential passwords) will be unencrypted 172 | *.pubxml 173 | *.publishproj 174 | 175 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 176 | # checkin your Azure Web App publish settings, but sensitive information contained 177 | # in these scripts will be unencrypted 178 | PublishScripts/ 179 | 180 | # NuGet Packages 181 | *.nupkg 182 | # The packages folder can be ignored because of Package Restore 183 | **/[Pp]ackages/* 184 | # except build/, which is used as an MSBuild target. 185 | !**/[Pp]ackages/build/ 186 | # Uncomment if necessary however generally it will be regenerated when needed 187 | #!**/[Pp]ackages/repositories.config 188 | # NuGet v3's project.json files produces more ignorable files 189 | *.nuget.props 190 | *.nuget.targets 191 | 192 | # Microsoft Azure Build Output 193 | csx/ 194 | *.build.csdef 195 | 196 | # Microsoft Azure Emulator 197 | ecf/ 198 | rcf/ 199 | 200 | # Windows Store app package directories and files 201 | AppPackages/ 202 | BundleArtifacts/ 203 | Package.StoreAssociation.xml 204 | _pkginfo.txt 205 | *.appx 206 | 207 | # Visual Studio cache files 208 | # files ending in .cache can be ignored 209 | *.[Cc]ache 210 | # but keep track of directories ending in .cache 211 | !*.[Cc]ache/ 212 | 213 | # Others 214 | ClientBin/ 215 | ~$* 216 | *~ 217 | *.dbmdl 218 | *.dbproj.schemaview 219 | *.jfm 220 | *.pfx 221 | *.publishsettings 222 | orleans.codegen.cs 223 | 224 | # Including strong name files can present a security risk 225 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 226 | #*.snk 227 | 228 | # Since there are multiple workflows, uncomment next line to ignore bower_components 229 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 230 | #bower_components/ 231 | 232 | # RIA/Silverlight projects 233 | Generated_Code/ 234 | 235 | # Backup & report files from converting an old project file 236 | # to a newer Visual Studio version. Backup files are not needed, 237 | # because we have git ;-) 238 | _UpgradeReport_Files/ 239 | Backup*/ 240 | UpgradeLog*.XML 241 | UpgradeLog*.htm 242 | ServiceFabricBackup/ 243 | *.rptproj.bak 244 | 245 | # SQL Server files 246 | *.mdf 247 | *.ldf 248 | *.ndf 249 | 250 | # Business Intelligence projects 251 | *.rdl.data 252 | *.bim.layout 253 | *.bim_*.settings 254 | *.rptproj.rsuser 255 | 256 | # Microsoft Fakes 257 | FakesAssemblies/ 258 | 259 | # GhostDoc plugin setting file 260 | *.GhostDoc.xml 261 | 262 | # Node.js Tools for Visual Studio 263 | .ntvs_analysis.dat 264 | node_modules/ 265 | 266 | # Visual Studio 6 build log 267 | *.plg 268 | 269 | # Visual Studio 6 workspace options file 270 | *.opt 271 | 272 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 273 | *.vbw 274 | 275 | # Visual Studio LightSwitch build output 276 | **/*.HTMLClient/GeneratedArtifacts 277 | **/*.DesktopClient/GeneratedArtifacts 278 | **/*.DesktopClient/ModelManifest.xml 279 | **/*.Server/GeneratedArtifacts 280 | **/*.Server/ModelManifest.xml 281 | _Pvt_Extensions 282 | 283 | # Paket dependency manager 284 | .paket/paket.exe 285 | paket-files/ 286 | 287 | # FAKE - F# Make 288 | .fake/ 289 | 290 | # JetBrains Rider 291 | .idea/ 292 | *.sln.iml 293 | 294 | # CodeRush 295 | .cr/ 296 | 297 | # Python Tools for Visual Studio (PTVS) 298 | __pycache__/ 299 | *.pyc 300 | 301 | # Cake - Uncomment if you are using it 302 | # tools/** 303 | # !tools/packages.config 304 | 305 | # Tabs Studio 306 | *.tss 307 | 308 | # Telerik's JustMock configuration file 309 | *.jmconfig 310 | 311 | # BizTalk build output 312 | *.btp.cs 313 | *.btm.cs 314 | *.odx.cs 315 | *.xsd.cs 316 | 317 | # OpenCover UI analysis results 318 | OpenCover/ 319 | 320 | # Azure Stream Analytics local run output 321 | ASALocalRun/ 322 | 323 | # MSBuild Binary and Structured Log 324 | *.binlog 325 | 326 | # NVidia Nsight GPU debugger configuration file 327 | *.nvuser 328 | 329 | # MFractors (Xamarin productivity tool) working folder 330 | .mfractor/ 331 | act/actions-setup-dotnet@v1 332 | act/actions-setup-dotnet@v1 333 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to find out which attributes exist for C# debugging 3 | // Use hover for the description of the existing attributes 4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": ".NET Core Launch (console)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | // If you have changed target frameworks, make sure to update the program path. 13 | "program": "${workspaceFolder}/Sample/bin/Debug/net5/Sample.dll", 14 | "args": [], 15 | "cwd": "${workspaceFolder}/Sample", 16 | // For more information about the 'console' field, see https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md#console-terminal-window 17 | "console": "integratedTerminal", 18 | "stopAtEntry": false 19 | }, 20 | { 21 | "name": ".NET Core Attach", 22 | "type": "coreclr", 23 | "request": "attach", 24 | "processId": "${command:pickProcess}" 25 | } 26 | ] 27 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "FSharp.fsacRuntime": "netcore" 3 | } -------------------------------------------------------------------------------- /.vscode/solution-explorer/class.cs-template: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace {{namespace}} 4 | { 5 | public class {{name}} 6 | { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.vscode/solution-explorer/class.ts-template: -------------------------------------------------------------------------------- 1 | export class {{name}} { 2 | 3 | } -------------------------------------------------------------------------------- /.vscode/solution-explorer/class.vb-template: -------------------------------------------------------------------------------- 1 | Imports System 2 | 3 | Namespace {{namespace}} 4 | 5 | Public Class {{name}} 6 | 7 | End Class 8 | 9 | End Namespace 10 | -------------------------------------------------------------------------------- /.vscode/solution-explorer/default.ts-template: -------------------------------------------------------------------------------- 1 | export default {{name}} { 2 | 3 | } -------------------------------------------------------------------------------- /.vscode/solution-explorer/enum.cs-template: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace {{namespace}} 4 | { 5 | public enum {{name}} 6 | { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.vscode/solution-explorer/interface.cs-template: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace {{namespace}} 4 | { 5 | public interface {{name}} 6 | { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.vscode/solution-explorer/interface.ts-template: -------------------------------------------------------------------------------- 1 | export interface {{name}} { 2 | 3 | } -------------------------------------------------------------------------------- /.vscode/solution-explorer/template-list.json: -------------------------------------------------------------------------------- 1 | { 2 | "templates": [ 3 | { 4 | "name": "Class", 5 | "extension": "cs", 6 | "file": "./class.cs-template", 7 | "parameters": "./template-parameters.js" 8 | }, 9 | { 10 | "name": "Interface", 11 | "extension": "cs", 12 | "file": "./interface.cs-template", 13 | "parameters": "./template-parameters.js" 14 | }, 15 | { 16 | "name": "Enum", 17 | "extension": "cs", 18 | "file": "./enum.cs-template", 19 | "parameters": "./template-parameters.js" 20 | }, 21 | { 22 | "name": "Class", 23 | "extension": "ts", 24 | "file": "./class.ts-template", 25 | "parameters": "./template-parameters.js" 26 | }, 27 | { 28 | "name": "Interface", 29 | "extension": "ts", 30 | "file": "./interface.ts-template", 31 | "parameters": "./template-parameters.js" 32 | }, 33 | { 34 | "name": "Default", 35 | "extension": "ts", 36 | "file": "./default.ts-template", 37 | "parameters": "./template-parameters.js" 38 | }, 39 | { 40 | "name": "Class", 41 | "extension": "vb", 42 | "file": "./class.vb-template", 43 | "parameters": "./template-parameters.js" 44 | } 45 | ] 46 | } -------------------------------------------------------------------------------- /.vscode/solution-explorer/template-parameters.js: -------------------------------------------------------------------------------- 1 | var path = require("path"); 2 | 3 | module.exports = function(filename, projectPath, folderPath) { 4 | var namespace = "Unknown"; 5 | if (projectPath) { 6 | namespace = path.basename(projectPath, path.extname(projectPath)); 7 | if (folderPath) { 8 | namespace += "." + folderPath.replace(path.dirname(projectPath), "").substring(1).replace(/[\\\/]/g, "."); 9 | } 10 | } 11 | 12 | return { 13 | namespace: namespace, 14 | name: path.basename(filename, path.extname(filename)) 15 | } 16 | }; -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/Sample/Sample.csproj" 11 | ], 12 | "problemMatcher": "$tsc" 13 | }, 14 | { 15 | "label": "publish", 16 | "command": "dotnet", 17 | "type": "process", 18 | "args": [ 19 | "publish", 20 | "${workspaceFolder}/Sample/Sample.csproj" 21 | ], 22 | "problemMatcher": "$tsc" 23 | }, 24 | { 25 | "label": "watch", 26 | "command": "dotnet", 27 | "type": "process", 28 | "args": [ 29 | "watch", 30 | "run", 31 | "${workspaceFolder}/Sample/Sample.csproj" 32 | ], 33 | "problemMatcher": "$tsc" 34 | } 35 | ] 36 | } -------------------------------------------------------------------------------- /Build.ps1: -------------------------------------------------------------------------------- 1 | param( 2 | [Parameter(Mandatory = $true)][string]$version = $(throw "Parameter missing: -version Version"), 3 | [string]$nugetKey 4 | ) 5 | 6 | $solutionPath = ".\QQWrySln.sln" 7 | $rootFolder = (Get-Item -Path "./" -Verbose).FullName 8 | $outputFolder = (Join-Path $rootFolder "artifacts") 9 | if (Test-Path $outputFolder) { Remove-Item $outputFolder -Force -Recurse } 10 | 11 | Write-Output "Version:$version" 12 | 13 | function CheckProcess ([string]$action) { 14 | if (-Not $?) { 15 | Write-Host ("$action failed") 16 | Set-Location $rootFolder 17 | exit $LASTEXITCODE 18 | } 19 | } 20 | 21 | #build 22 | dotnet build $solutionPath --configuration Release 23 | 24 | CheckProcess "build" 25 | 26 | #test 27 | dotnet test $solutionPath --configuration Release --no-restore --verbosity normal 28 | 29 | CheckProcess "test" 30 | 31 | #pack 32 | dotnet pack .\QQWry -o $outputFolder -p:Version=$version --configuration Release --no-restore --verbosity normal 33 | 34 | CheckProcess "pack QQWry" 35 | 36 | dotnet pack .\QQWry.DependencyInjection -o $outputFolder -p:Version=$version --configuration Release --no-restore --verbosity normal 37 | 38 | CheckProcess "pack QQWry.DependencyInjection" 39 | 40 | #nuget push 41 | if ($nugetKey) { 42 | dotnet nuget push "$outputFolder\*.nupkg" --source https://api.nuget.org/v3/index.json --skip-duplicate --api-key $nugetKey 43 | 44 | CheckProcess "nuget push" 45 | } 46 | else { 47 | Write-Output "Skip nuget push" 48 | } 49 | 50 | Set-Location $outputFolder 51 | 52 | Get-ChildItem 53 | 54 | Set-Location $rootFolder -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Jadyn 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /QQWry.DependencyInjection/QQWry.DependencyInjection.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0;net6.0 5 | true 6 | JadynWong 7 | https://github.com/JadynWong/IP_qqwry 8 | https://github.com/JadynWong/IP_qqwry 9 | git 10 | false 11 | QQWry IP查询 DependencyInjection 12 | MIT 13 | README.md 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | True 27 | \ 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /QQWry.DependencyInjection/QQWryServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Microsoft.Extensions.DependencyInjection.Extensions; 4 | 5 | namespace QQWry.DependencyInjection 6 | { 7 | public static class QQWryServiceCollectionExtensions 8 | { 9 | public static IServiceCollection AddQQWry(this IServiceCollection services, QQWryOptions options) 10 | { 11 | if (services == null) 12 | { 13 | throw new ArgumentNullException(nameof(services)); 14 | } 15 | if (options == null) 16 | { 17 | throw new ArgumentNullException(nameof(options)); 18 | } 19 | 20 | services.TryAddSingleton(); 21 | 22 | services.TryAddSingleton(options); 23 | 24 | return services; 25 | } 26 | 27 | public static IServiceCollection AddQQWry(this IServiceCollection services, Action optionAction) 28 | { 29 | if (services == null) 30 | { 31 | throw new ArgumentNullException(nameof(services)); 32 | } 33 | if (optionAction == null) 34 | { 35 | throw new ArgumentNullException(nameof(optionAction)); 36 | } 37 | var options = new QQWryOptions(); 38 | 39 | return AddQQWry(services, options); 40 | } 41 | 42 | public static IServiceCollection AddQQWry(this IServiceCollection services) 43 | { 44 | if (services == null) 45 | { 46 | throw new ArgumentNullException(nameof(services)); 47 | } 48 | var options = new QQWryOptions(); 49 | 50 | return AddQQWry(services, options); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /QQWry/IIpSearch.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | namespace QQWry 5 | { 6 | public interface IIpSearch 7 | { 8 | /// 9 | /// 数据库IP数量 10 | /// 11 | int IpCount { get; } 12 | 13 | /// 14 | /// 数据库版本 15 | /// 16 | string Version { get; } 17 | 18 | /// 19 | /// 检查是否是IP地址 20 | /// 21 | /// 22 | /// 23 | bool CheckIp(string ip); 24 | 25 | /// 26 | /// 获取IP信息 27 | /// 28 | /// 29 | /// 30 | IpLocation GetIpLocation(string strIp); 31 | 32 | /// 33 | /// 检查是否是IP地址 34 | /// 35 | /// 36 | /// 37 | /// 38 | Task GetIpLocationAsync(string strIp, CancellationToken token = default); 39 | 40 | /// 41 | /// 初始化 42 | /// 43 | /// 44 | bool Init(); 45 | 46 | /// 47 | /// 初始化 48 | /// 49 | /// 50 | /// 51 | Task InitAsync(CancellationToken token = default); 52 | } 53 | } -------------------------------------------------------------------------------- /QQWry/IpLocation.cs: -------------------------------------------------------------------------------- 1 | namespace QQWry 2 | { 3 | public record struct IpLocation 4 | { 5 | public string Ip, Country, Area; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /QQWry/QQWry.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0;net6.0;net45 5 | true 6 | JadynWong 7 | https://github.com/JadynWong/IP_qqwry 8 | https://github.com/JadynWong/IP_qqwry 9 | git 10 | false 11 | QQWry IP查询 12 | MIT 13 | latest 14 | README.md 15 | 16 | 17 | 18 | 19 | True 20 | \ 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /QQWry/QQWryIpSearch.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Text; 5 | using System.Text.RegularExpressions; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | 9 | namespace QQWry 10 | { 11 | /// 12 | /// QQWryIpSearch 请作为单例使用 数据库缓存在内存 13 | /// 14 | public class QQWryIpSearch : IIpSearch, IDisposable 15 | { 16 | private static readonly Encoding _encodingGb2312; 17 | 18 | /// 19 | /// IP地址正则验证 20 | /// 21 | private static Regex _ipAddressRegex = new(@"(\b(?:(?:2(?:[0-4][0-9]|5[0-5])|[0-1]?[0-9]?[0-9])\.){3}(?:(?:2([0-4][0-9]|5[0-5])|[0-1]?[0-9]?[0-9]))\b)", RegexOptions.Compiled); 22 | 23 | private readonly SemaphoreSlim _initLock = new(initialCount: 1, maxCount: 1); 24 | 25 | private readonly object _versionLock = new(); 26 | 27 | private readonly long _loopbackIP = IpToLong("127.0.0.1"); 28 | 29 | private readonly QQWryOptions _qqwryOptions; 30 | 31 | /// 32 | /// 数据库 缓存 33 | /// 34 | private byte[] _qqwryDbBytes; 35 | 36 | /// 37 | /// Ip索引 缓存 38 | /// 39 | private long[] _ipIndexCache; 40 | 41 | /// 42 | /// 起始定位 43 | /// 44 | private long _startPosition; 45 | 46 | /// 47 | /// 是否初始化 48 | /// 49 | private bool? _init; 50 | 51 | private int? _ipCount; 52 | 53 | private string _version; 54 | 55 | /// 56 | /// 57 | /// 记录总数 58 | /// 59 | public int IpCount 60 | { 61 | get 62 | { 63 | if (!_ipCount.HasValue) 64 | { 65 | Init(); 66 | _ipCount = _ipIndexCache.Length; 67 | } 68 | 69 | return _ipCount.Value; 70 | } 71 | } 72 | 73 | /// 74 | /// 75 | /// 版本信息 76 | /// 77 | public string Version 78 | { 79 | get 80 | { 81 | if (!string.IsNullOrWhiteSpace(_version)) 82 | { 83 | return _version; 84 | } 85 | lock (_versionLock) 86 | { 87 | if (!string.IsNullOrWhiteSpace(_version)) 88 | { 89 | return _version; 90 | } 91 | _version = GetIpLocation("255.255.255.255").Area; 92 | return _version; 93 | } 94 | } 95 | } 96 | 97 | static QQWryIpSearch() 98 | { 99 | #if NET45 100 | 101 | #else 102 | Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); 103 | #endif 104 | _encodingGb2312 = Encoding.GetEncoding("gb2312"); 105 | } 106 | 107 | public QQWryIpSearch(QQWryOptions options) 108 | { 109 | _qqwryOptions = options; 110 | } 111 | 112 | /// 113 | /// 114 | /// 初始化 115 | /// 116 | /// 117 | public virtual bool Init() 118 | { 119 | if (_init != null) 120 | { 121 | return _init.Value; 122 | } 123 | _initLock.Wait(); 124 | try 125 | { 126 | if (_init != null) 127 | { 128 | return _init.Value; 129 | } 130 | 131 | EnsureFileExist(_qqwryOptions.DbPath); 132 | 133 | #if DEBUG 134 | System.Diagnostics.Debug.WriteLine(format: $"使用IP数据库{_qqwryOptions.DbPath}"); 135 | #endif 136 | 137 | _qqwryDbBytes = FileToBytes(_qqwryOptions.DbPath); 138 | 139 | _ipIndexCache = BlockToArray(ReadIpBlock(_qqwryDbBytes, out _startPosition)); 140 | 141 | _ipCount = null; 142 | _version = null; 143 | _init = true; 144 | } 145 | finally 146 | { 147 | _initLock.Release(); 148 | } 149 | 150 | if (_qqwryDbBytes == null) 151 | { 152 | throw new InvalidOperationException("无法打开IP数据库" + _qqwryOptions.DbPath + "!"); 153 | } 154 | 155 | return true; 156 | 157 | } 158 | 159 | /// 160 | /// 161 | /// 获取指定IP所在地理位置 162 | /// 163 | /// 要查询的IP地址 164 | /// 165 | public virtual IpLocation GetIpLocation(string strIp) 166 | { 167 | var loc = new IpLocation 168 | { 169 | Ip = strIp 170 | }; 171 | 172 | var ip = IpToLong(strIp); 173 | 174 | if (ip == _loopbackIP) 175 | { 176 | loc.Country = "本机内部环回地址"; 177 | loc.Area = string.Empty; 178 | return loc; 179 | } 180 | 181 | if (!Init()) 182 | { 183 | return loc; 184 | } 185 | 186 | return ReadLocation(loc, ip, _startPosition, _ipIndexCache, _qqwryDbBytes); 187 | } 188 | 189 | /// 190 | /// 191 | /// 初始化 192 | /// 193 | /// 194 | /// 195 | public virtual async Task InitAsync(CancellationToken token = default) 196 | { 197 | if (_init != null) 198 | { 199 | return _init.Value; 200 | } 201 | 202 | await _initLock.WaitAsync(token); 203 | 204 | try 205 | { 206 | if (_init != null) 207 | { 208 | return _init.Value; 209 | } 210 | 211 | 212 | EnsureFileExist(_qqwryOptions.DbPath); 213 | 214 | #if DEBUG 215 | System.Diagnostics.Debug.WriteLine("使用IP数据库{0}", _qqwryOptions.DbPath); 216 | #endif 217 | _qqwryDbBytes = FileToBytes(_qqwryOptions.DbPath); 218 | 219 | _ipIndexCache = BlockToArray(ReadIpBlock(_qqwryDbBytes, out _startPosition)); 220 | 221 | _ipCount = null; 222 | _version = null; 223 | _init = true; 224 | } 225 | finally 226 | { 227 | _initLock.Release(); 228 | } 229 | 230 | if (_qqwryDbBytes == null) 231 | { 232 | throw new InvalidOperationException("无法打开IP数据库" + _qqwryOptions.DbPath + "!"); 233 | } 234 | 235 | return true; 236 | } 237 | 238 | /// 239 | /// 240 | /// 获取指定IP所在地理位置 241 | /// 242 | /// 要查询的IP地址 243 | /// 244 | /// 245 | public virtual async Task GetIpLocationAsync(string strIp, CancellationToken token = default) 246 | { 247 | var loc = new IpLocation 248 | { 249 | Ip = strIp 250 | }; 251 | 252 | long ip = IpToLong(strIp); 253 | if (ip == _loopbackIP) 254 | { 255 | loc.Country = "本机内部环回地址"; 256 | loc.Area = string.Empty; 257 | return loc; 258 | } 259 | if (!await InitAsync(token)) 260 | { 261 | return loc; 262 | } 263 | return ReadLocation(loc, ip, _startPosition, _ipIndexCache, _qqwryDbBytes); 264 | } 265 | 266 | /// 267 | /// 268 | /// 检查IP合法性 269 | /// 270 | /// 271 | /// 272 | public bool CheckIp(string ip) 273 | { 274 | return _ipAddressRegex.IsMatch(ip); 275 | } 276 | 277 | /// 278 | /// 279 | /// 释放 280 | /// 281 | public void Dispose() 282 | { 283 | _initLock?.Dispose(); 284 | _qqwryDbBytes = null; 285 | _qqwryDbBytes = null; 286 | _ipIndexCache = null; 287 | _init = null; 288 | } 289 | 290 | /// 291 | /// 将字符串形式的IP转换位long 292 | /// 293 | /// 294 | /// 295 | private static long IpToLong(string strIp) 296 | { 297 | var ipBytes = new byte[8]; 298 | var strArr = strIp.Split(new char[] { '.' }); 299 | for (var i = 0; i < 4; i++) 300 | { 301 | ipBytes[i] = byte.Parse(strArr[3 - i]); 302 | } 303 | return BitConverter.ToInt64(ipBytes, 0); 304 | } 305 | 306 | /// 307 | /// 将索引区字节块中的起始IP转换成Long数组 308 | /// 309 | /// 310 | private static long[] BlockToArray(byte[] ipBlock) 311 | { 312 | var ipArray = new long[ipBlock.Length / 7]; 313 | var ipIndex = 0; 314 | var temp = new byte[8]; 315 | for (var i = 0; i < ipBlock.Length; i += 7) 316 | { 317 | Array.Copy(ipBlock, i, temp, 0, 4); 318 | ipArray[ipIndex] = BitConverter.ToInt64(temp, 0); 319 | ipIndex++; 320 | } 321 | return ipArray; 322 | } 323 | 324 | /// 325 | /// 从IP数组中搜索指定IP并返回其索引 326 | /// 327 | /// 328 | /// IP数组 329 | /// 指定搜索的起始位置 330 | /// 指定搜索的结束位置 331 | /// 332 | private static int SearchIp(long ip, long[] ipArray, int start, int end) 333 | { 334 | //二分法 https://baike.baidu.com/item/%E4%BA%8C%E5%88%86%E6%B3%95%E6%9F%A5%E6%89%BE 335 | while (true) 336 | { 337 | //计算中间索引 338 | var middle = (start + end) / 2; 339 | if (middle == start) 340 | { 341 | return middle; 342 | } 343 | else if (ip < ipArray[middle]) 344 | { 345 | end = middle; 346 | } 347 | else 348 | { 349 | start = middle; 350 | } 351 | } 352 | } 353 | 354 | /// 355 | /// 读取IP文件中索引区块 356 | /// 357 | /// 358 | private static byte[] ReadIpBlock(byte[] bytes, out long startPosition) 359 | { 360 | long offset = 0; 361 | startPosition = ReadLongX(bytes, offset, 4); 362 | offset += 4; 363 | var endPosition = ReadLongX(bytes, offset, 4); 364 | offset = startPosition; 365 | var count = (endPosition - startPosition) / 7 + 1;//总记录数 366 | 367 | var ipBlock = new byte[count * 7]; 368 | for (var i = 0; i < ipBlock.Length; i++) 369 | { 370 | ipBlock[i] = bytes[offset + i]; 371 | } 372 | return ipBlock; 373 | } 374 | 375 | /// 376 | /// 从IP文件中读取指定字节并转换位long 377 | /// 378 | /// 379 | /// 需要转换的字节数,主意不要超过8字节 380 | /// 381 | private static long ReadLongX(byte[] bytes, long offset, int bytesCount) 382 | { 383 | var cBytes = new byte[8]; 384 | for (var i = 0; i < bytesCount; i++) 385 | { 386 | cBytes[i] = bytes[offset + i]; 387 | } 388 | return BitConverter.ToInt64(cBytes, 0); 389 | } 390 | 391 | /// 392 | /// 从IP文件中读取字符串 393 | /// 394 | /// 395 | /// 转向标志 396 | /// 397 | /// 398 | private static string ReadString(byte[] bytes, int flag, ref long offset) 399 | { 400 | if (flag == 1 || flag == 2)//转向标志 401 | { 402 | offset = ReadLongX(bytes, offset, 3); 403 | } 404 | else 405 | { 406 | offset -= 1; 407 | } 408 | var list = new List(); 409 | var b = (byte)bytes[offset]; 410 | offset += 1; 411 | while (b > 0) 412 | { 413 | list.Add(b); 414 | b = (byte)bytes[offset]; 415 | offset += 1; 416 | } 417 | return _encodingGb2312.GetString(list.ToArray()); 418 | } 419 | 420 | private static byte[] FileToBytes(string fileName) 421 | { 422 | using (FileStream fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read)) 423 | { 424 | byte[] bytes = new byte[fileStream.Length]; 425 | 426 | fileStream.Read(bytes, 0, bytes.Length); 427 | 428 | fileStream.Close(); 429 | 430 | return bytes; 431 | } 432 | } 433 | 434 | private static void EnsureFileExist(string ipDbPath) 435 | { 436 | var dir = Path.GetDirectoryName(ipDbPath); 437 | if (!Directory.Exists(dir)) 438 | { 439 | Directory.CreateDirectory(dir ?? throw new InvalidOperationException()); 440 | } 441 | if (!File.Exists(ipDbPath)) 442 | { 443 | #if DEBUG 444 | System.Diagnostics.Debug.WriteLine(format: "无法找到IP数据库{0}", ipDbPath); 445 | #endif 446 | throw new Exception($"无法找到IP数据库{ipDbPath}"); 447 | } 448 | 449 | } 450 | 451 | private static IpLocation ReadLocation(IpLocation loc, long ip, long startPosition, long[] ipIndex, byte[] qqwryDbBytes) 452 | { 453 | long offset = SearchIp(ip, ipIndex, 0, ipIndex.Length) * 7 + 4; 454 | 455 | //偏移 456 | var arrayOffset = startPosition + offset; 457 | //跳过结束IP 458 | arrayOffset = ReadLongX(qqwryDbBytes, arrayOffset, 3) + 4; 459 | //读取标志 460 | var flag = qqwryDbBytes[arrayOffset]; 461 | arrayOffset += 1; 462 | //表示国家和地区被转向 463 | if (flag == 1) 464 | { 465 | arrayOffset = ReadLongX(qqwryDbBytes, arrayOffset, 3); 466 | //再读标志 467 | flag = qqwryDbBytes[arrayOffset]; 468 | arrayOffset += 1; 469 | } 470 | var countryOffset = arrayOffset; 471 | loc.Country = ReadString(qqwryDbBytes, flag, ref arrayOffset); 472 | 473 | if (flag == 2) 474 | { 475 | arrayOffset = countryOffset + 3; 476 | } 477 | 478 | flag = qqwryDbBytes[arrayOffset]; 479 | arrayOffset += 1; 480 | loc.Area = ReadString(qqwryDbBytes, flag, ref arrayOffset); 481 | 482 | if (" CZ88.NET".Equals(loc.Area, StringComparison.CurrentCultureIgnoreCase)) 483 | { 484 | loc.Area = string.Empty; 485 | } 486 | 487 | return loc; 488 | } 489 | } 490 | } 491 | -------------------------------------------------------------------------------- /QQWry/QQWryOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | // ReSharper disable InconsistentNaming 5 | namespace QQWry 6 | { 7 | public class QQWryOptions 8 | { 9 | public QQWryOptions() 10 | { 11 | 12 | } 13 | 14 | public QQWryOptions(string dbPath) 15 | { 16 | DbPath = dbPath; 17 | } 18 | 19 | private string _dbPath = string.Empty; 20 | 21 | /// 22 | /// DbPath 23 | /// 24 | public string DbPath 25 | { 26 | get 27 | { 28 | if (string.IsNullOrWhiteSpace(_dbPath)) 29 | { 30 | return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "qqwry.dat"); 31 | } 32 | else 33 | { 34 | return _dbPath; 35 | } 36 | } 37 | set 38 | { 39 | _dbPath = value; 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /QQWrySln.sln: -------------------------------------------------------------------------------- 1 | Microsoft Visual Studio Solution File, Format Version 12.00 2 | # Visual Studio Version 17 3 | VisualStudioVersion = 17.6.33723.286 4 | MinimumVisualStudioVersion = 10.0.40219.1 5 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample", "Sample\Sample.csproj", "{6D0553F4-06F0-4276-B76E-E11F594E6771}" 6 | EndProject 7 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QQWry", "QQWry\QQWry.csproj", "{BB221A72-B661-4FF6-A6FF-0D8B9AF7F052}" 8 | EndProject 9 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QQWry.DependencyInjection", "QQWry.DependencyInjection\QQWry.DependencyInjection.csproj", "{8E9103A8-904C-4628-A4F6-956AC02D7E8D}" 10 | EndProject 11 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QQWry.Test", "QQWryTest\QQWry.Test.csproj", "{6D25D7F6-15A5-48A3-8817-00F0074BBDAD}" 12 | EndProject 13 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "solution items", "solution items", "{A960908C-6C2D-4C05-8C5E-86B372826708}" 14 | ProjectSection(SolutionItems) = preProject 15 | Build.ps1 = Build.ps1 16 | LICENSE = LICENSE 17 | README.md = README.md 18 | EndProjectSection 19 | EndProject 20 | Global 21 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 22 | Debug|Any CPU = Debug|Any CPU 23 | Release|Any CPU = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 26 | {6D0553F4-06F0-4276-B76E-E11F594E6771}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {6D0553F4-06F0-4276-B76E-E11F594E6771}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {6D0553F4-06F0-4276-B76E-E11F594E6771}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {6D0553F4-06F0-4276-B76E-E11F594E6771}.Release|Any CPU.Build.0 = Release|Any CPU 30 | {BB221A72-B661-4FF6-A6FF-0D8B9AF7F052}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {BB221A72-B661-4FF6-A6FF-0D8B9AF7F052}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {BB221A72-B661-4FF6-A6FF-0D8B9AF7F052}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {BB221A72-B661-4FF6-A6FF-0D8B9AF7F052}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {8E9103A8-904C-4628-A4F6-956AC02D7E8D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {8E9103A8-904C-4628-A4F6-956AC02D7E8D}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {8E9103A8-904C-4628-A4F6-956AC02D7E8D}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {8E9103A8-904C-4628-A4F6-956AC02D7E8D}.Release|Any CPU.Build.0 = Release|Any CPU 38 | {6D25D7F6-15A5-48A3-8817-00F0074BBDAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {6D25D7F6-15A5-48A3-8817-00F0074BBDAD}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {6D25D7F6-15A5-48A3-8817-00F0074BBDAD}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {6D25D7F6-15A5-48A3-8817-00F0074BBDAD}.Release|Any CPU.Build.0 = Release|Any CPU 42 | EndGlobalSection 43 | GlobalSection(SolutionProperties) = preSolution 44 | HideSolutionNode = FALSE 45 | EndGlobalSection 46 | GlobalSection(ExtensibilityGlobals) = postSolution 47 | SolutionGuid = {7CDAEBC0-B8FB-496A-8C20-7D4ED2EEC407} 48 | EndGlobalSection 49 | EndGlobal 50 | -------------------------------------------------------------------------------- /QQWrySln.sln.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | True 3 | True -------------------------------------------------------------------------------- /QQWryTest/QQWry.Test.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | false 6 | QQWry.Test 7 | QQWry.Test 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | all 16 | runtime; build; native; contentfiles; analyzers; buildtransitive 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | Always 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /QQWryTest/QQWryIpSearchTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Xunit; 6 | 7 | // ReSharper disable InconsistentNaming 8 | namespace QQWry.Test 9 | { 10 | public class QQWryIpSearchTest 11 | { 12 | protected QQWryIpSearch GetInstance() 13 | { 14 | var dbPath = Path.Combine(AppContext.BaseDirectory, "qqwry.dat"); 15 | var option = new QQWryOptions(dbPath); 16 | return new QQWryIpSearch(option); 17 | } 18 | 19 | protected string GetRandomIp(IIpSearch ipSearch) 20 | { 21 | while (true) 22 | { 23 | var s = ""; 24 | for (var i = 0; i <= 3; i++) 25 | { 26 | var q = Random.Shared.Next(0, 255).ToString(); 27 | if (i < 3) 28 | { 29 | s += (q + ".").ToString(); 30 | } 31 | else 32 | { 33 | s += q.ToString(); 34 | } 35 | } 36 | if (ipSearch.CheckIp(s)) 37 | { 38 | return s; 39 | } 40 | } 41 | } 42 | 43 | [Fact] 44 | public void CheckTest() 45 | { 46 | var ipSearch = GetInstance(); 47 | 48 | var ip = GetRandomIp(ipSearch); 49 | 50 | Assert.NotEmpty(ip); 51 | 52 | ipSearch.Dispose(); 53 | } 54 | 55 | [Fact] 56 | public void InitTest() 57 | { 58 | var ipSearch = GetInstance(); 59 | 60 | var inited = ipSearch.Init(); 61 | 62 | Assert.True(inited); 63 | 64 | Assert.True(ipSearch.IpCount > 0); 65 | 66 | Assert.NotNull(ipSearch.Version); 67 | 68 | ipSearch.Dispose(); 69 | } 70 | 71 | [Fact] 72 | public async Task InitAsyncTest() 73 | { 74 | var ipSearch = GetInstance(); 75 | 76 | var inited = await ipSearch.InitAsync(); 77 | 78 | Assert.True(inited); 79 | 80 | Assert.True(ipSearch.IpCount > 0); 81 | 82 | Assert.NotNull(ipSearch.Version); 83 | 84 | ipSearch.Dispose(); 85 | } 86 | 87 | [Fact] 88 | public void GetIpLocationTest() 89 | { 90 | var ipSearch = GetInstance(); 91 | 92 | var preSearchIpArray = new string[10]; 93 | for (int i = 0; i < preSearchIpArray.Length; i++) 94 | { 95 | preSearchIpArray[i] = GetRandomIp(ipSearch); 96 | } 97 | 98 | foreach (var ip in preSearchIpArray) 99 | { 100 | var ipLocation = ipSearch.GetIpLocation(ip); 101 | 102 | Assert.NotNull(ipLocation.Area); 103 | } 104 | 105 | Assert.True(ipSearch.IpCount > 0); 106 | 107 | Assert.NotNull(ipSearch.Version); 108 | 109 | ipSearch.Dispose(); 110 | } 111 | 112 | [Fact] 113 | public async Task GetIpLocationAsyncTest() 114 | { 115 | var ipSearch = GetInstance(); 116 | 117 | var preSearchIpArray = new string[10]; 118 | for (int i = 0; i < preSearchIpArray.Length; i++) 119 | { 120 | preSearchIpArray[i] = GetRandomIp(ipSearch); 121 | } 122 | 123 | foreach (var ip in preSearchIpArray) 124 | { 125 | var ipLocation = await ipSearch.GetIpLocationAsync(ip); 126 | 127 | Assert.NotNull(ipLocation.Area); 128 | } 129 | 130 | Assert.True(ipSearch.IpCount > 0); 131 | 132 | Assert.NotNull(ipSearch.Version); 133 | 134 | ipSearch.Dispose(); 135 | } 136 | 137 | [Fact] 138 | public async Task MultiThreadingSafeTest() 139 | { 140 | var ipSearch = GetInstance(); 141 | 142 | var maxTask = 300; 143 | 144 | var ips = Enumerable.Range(0, maxTask).Select(_ => GetRandomIp(ipSearch)).ToList(); 145 | 146 | await Parallel.ForEachAsync(ips, async (ip, ParallelLoopState) => 147 | { 148 | var ipLocation = await ipSearch.GetIpLocationAsync(ip); 149 | Assert.NotNull(ipLocation.Area); 150 | Assert.True(ipSearch.IpCount > 0); 151 | Assert.NotNull(ipSearch.Version); 152 | }); 153 | } 154 | 155 | [Fact] 156 | public async Task SingleThreadingSafeTest() 157 | { 158 | var ipSearch = GetInstance(); 159 | 160 | var maxTask = 300; 161 | 162 | var ips = Enumerable.Range(0, maxTask).Select(_ => GetRandomIp(ipSearch)).ToList(); 163 | 164 | foreach (var ip in ips) 165 | { 166 | var ipLocation = await ipSearch.GetIpLocationAsync(ip); 167 | Assert.NotNull(ipLocation.Area); 168 | Assert.True(ipSearch.IpCount > 0); 169 | Assert.NotNull(ipSearch.Version); 170 | } 171 | } 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # IP_qqwry [纯真IP数据库](http://www.cz88.net/)操作 2 | 3 | [![build](https://github.com/JadynWong/IP_qqwry/actions/workflows/build.yml/badge.svg)](https://github.com/JadynWong/IP_qqwry/actions/workflows/build.yml) 4 | [![release](https://github.com/JadynWong/IP_qqwry/actions/workflows/release.yml/badge.svg)](https://github.com/JadynWong/IP_qqwry/actions/workflows/release.yml) 5 | 6 | | Packages | Version | 7 | | ---- | ---- | 8 | | QQWry | [![NuGet](https://img.shields.io/nuget/v/QQWry.svg?style=flat)](https://www.nuget.org/packages/QQWry) | 9 | | QQWry.DependencyInjection | [![NuGet](https://img.shields.io/nuget/v/QQWry.DependencyInjection.svg?style=flat)](https://www.nuget.org/packages/QQWry.DependencyInjection) | 10 | 11 | 12 | ## QQWry 13 | 14 | ```csharp 15 | var config = new QQWryOptions() 16 | { 17 | DbPath = MapRootPath("~/IP/qqwry.dat") 18 | }; 19 | 20 | var ipSearch = new QQWryIpSearch(config); 21 | 22 | foreach (var ip in preSearchIpArray) 23 | { 24 | var ipLocation = ipSearch.GetIpLocation(ip); 25 | Write(ipLocation); 26 | } 27 | Console.WriteLine("记录总数" + ipSearch.IpCount); 28 | Console.WriteLine("版本" + ipSearch.Version); 29 | ``` 30 | 31 | ## QQWry.DependencyInjection 32 | 33 | ```csharp 34 | var service = new ServiceCollection(); 35 | 36 | service.AddQQWry(config); 37 | 38 | var serviceProvider = service.BuildServiceProvider(); 39 | 40 | using (var scope = serviceProvider.CreateScope()) 41 | { 42 | var ipSearchInterface = scope.ServiceProvider.GetRequiredService(); 43 | foreach (var ip in preSearchIpArray) 44 | { 45 | var ipLocation = ipSearchInterface.GetIpLocation(ip); 46 | Write(ipLocation); 47 | } 48 | Console.WriteLine("记录总数" + ipSearchInterface.IpCount); 49 | Console.WriteLine("版本" + ipSearchInterface.Version); 50 | } 51 | ``` 52 | 53 | ## IIpSearch 54 | 55 | ```csharp 56 | /// 57 | /// 数据库IP数量 58 | /// 59 | int IpCount { get; } 60 | 61 | /// 62 | /// 数据库版本 63 | /// 64 | string Version { get; } 65 | 66 | /// 67 | /// 检查是否是IP地址 68 | /// 69 | /// 70 | /// 71 | bool CheckIp(string ip); 72 | 73 | /// 74 | /// 获取IP信息 75 | /// 76 | /// 77 | /// 78 | IpLocation GetIpLocation(string strIp); 79 | 80 | /// 81 | /// 检查是否是IP地址 82 | /// 83 | /// 84 | /// 85 | /// 86 | Task GetIpLocationAsync(string strIp, CancellationToken token = default); 87 | 88 | /// 89 | /// 初始化 90 | /// 91 | /// 92 | bool Init(); 93 | 94 | /// 95 | /// 初始化 96 | /// 97 | /// 98 | /// 99 | Task InitAsync(CancellationToken token = default); 100 | ``` 101 | -------------------------------------------------------------------------------- /Sample/BenchmarkTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using BenchmarkDotNet.Attributes; 5 | using QQWry; 6 | 7 | namespace Sample 8 | { 9 | [SimpleJob(baseline: true)] 10 | [RPlotExporter, RankColumn] 11 | public class BenchmarkTest 12 | { 13 | private QQWryIpSearch QQWryIpSearch; 14 | 15 | private QQWryIpSearchMode2 QQWryIpSearchMode2; 16 | 17 | private Java2QQWry Java2QQWry; 18 | 19 | public static QQWryOptions Config; 20 | 21 | [Params(10, 50, 100)] 22 | public int Range; 23 | 24 | private string[] data; 25 | 26 | [GlobalSetup] 27 | public void Setup() 28 | { 29 | Config = new QQWryOptions() 30 | { 31 | DbPath = BenchmarkTest.MapRootPath("qqwry.dat") 32 | }; 33 | QQWryIpSearchMode2 = new QQWryIpSearchMode2(Config); 34 | QQWryIpSearch = new QQWryIpSearch(Config); 35 | 36 | //预热 37 | QQWryIpSearchMode2.GetIpLocation(GetRandomIp(QQWryIpSearchMode2)); 38 | QQWryIpSearch.GetIpLocation(GetRandomIp(QQWryIpSearchMode2)); 39 | 40 | Java2QQWry = new Java2QQWry(Config.DbPath); 41 | Java2QQWry.SearchIPLocation(GetRandomIp(QQWryIpSearchMode2)); 42 | 43 | //加载数据 44 | data = new string[Range]; 45 | for (int i = 0; i < data.Length; i++) 46 | { 47 | data[i] = GetRandomIp(QQWryIpSearchMode2); 48 | } 49 | } 50 | 51 | [Benchmark] 52 | public string[] QQWryIpSearchExecute() 53 | { 54 | return data.Select(x => QQWryIpSearch.GetIpLocation(x).Country).ToArray(); 55 | } 56 | 57 | [Benchmark] 58 | public string[] QQWryIpSearchMode2Execute() 59 | { 60 | return data.Select(x => QQWryIpSearchMode2.GetIpLocation(x).Country).ToArray(); 61 | } 62 | 63 | [Benchmark] 64 | public string[] Java2QQWryExecute() 65 | { 66 | return data.Select(x => Java2QQWry.SearchIPLocation(x).country).ToArray(); 67 | } 68 | 69 | /// 70 | /// Maps a virtual path to a physical disk path. 71 | /// 72 | /// The path to map. E.g. "~/bin" 73 | /// The physical path. E.g. "c:\\inetpub\\wwwroot\\bin" 74 | public static string MapRootPath(string path) 75 | { 76 | path = path.Replace("~/", "").TrimStart('/').Replace('/', '\\'); 77 | return Path.Combine(AppDomain.CurrentDomain.BaseDirectory ?? string.Empty, path); 78 | } 79 | 80 | public static string GetRandomIp(IIpSearch ipSearch) 81 | { 82 | while (true) 83 | { 84 | var s = ""; 85 | for (var i = 0; i <= 3; i++) 86 | { 87 | var q = Random.Shared.Next(0, 255).ToString(); 88 | if (i < 3) 89 | { 90 | s += (q + ".").ToString(); 91 | } 92 | else 93 | { 94 | s += q.ToString(); 95 | } 96 | } 97 | if (ipSearch.CheckIp(s)) 98 | { 99 | return s; 100 | } 101 | } 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /Sample/Java2QQWry.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Text; 5 | 6 | namespace Sample 7 | { 8 | /// 9 | /// QQWry 的摘要说明。 10 | /// 11 | public class Java2QQWry 12 | { 13 | //第一种模式 14 | #region 第一种模式 15 | /**/ 16 | /// 17 | ///第一种模式 18 | /// 19 | #endregion 20 | private const byte REDIRECT_MODE_1 = 0x01; 21 | //第二种模式 22 | #region 第二种模式 23 | /**/ 24 | /// 25 | ///第二种模式 26 | /// 27 | #endregion 28 | private const byte REDIRECT_MODE_2 = 0x02; 29 | //每条记录长度 30 | #region 每条记录长度 31 | /**/ 32 | /// 33 | ///每条记录长度 34 | /// 35 | #endregion 36 | private const int IP_RECORD_LENGTH = 7; 37 | //数据库文件 38 | #region 数据库文件 39 | /**/ 40 | /// 41 | ///文件对象 42 | /// 43 | #endregion 44 | private Stream ipFile; 45 | private const string unCountry = "未知国家"; 46 | private const string unArea = "未知地区"; 47 | //索引开始位置 48 | #region 索引开始位置 49 | /**/ 50 | /// 51 | ///索引开始位置 52 | /// 53 | #endregion 54 | private long ipBegin; 55 | //索引结束位置 56 | #region 索引结束位置 57 | /**/ 58 | /// 59 | ///索引结束位置 60 | /// 61 | #endregion 62 | private long ipEnd; 63 | //IP地址对象 64 | #region IP地址对象 65 | /**/ 66 | /// 67 | /// IP对象 68 | /// 69 | #endregion 70 | private IPLocation loc; 71 | //存储文本内容 72 | #region 存储文本内容 73 | /**/ 74 | /// 75 | ///存储文本内容 76 | /// 77 | #endregion 78 | private byte[] buf; 79 | //存储3字节 80 | #region 存储3字节 81 | /**/ 82 | /// 83 | ///存储3字节 84 | /// 85 | #endregion 86 | private byte[] b3; 87 | //存储4字节 88 | #region 存储4字节 89 | /**/ 90 | /// 91 | ///存储4字节IP地址 92 | /// 93 | #endregion 94 | private byte[] b4; 95 | //构造函数 96 | #region 构造函数 97 | /**/ 98 | /// 99 | ///构造函数 100 | /// 101 | ///IP数据库文件绝对路径 102 | #endregion 103 | public Java2QQWry(string ipfile) 104 | { 105 | buf = new byte[100]; 106 | b3 = new byte[3]; 107 | b4 = new byte[4]; 108 | try 109 | { 110 | using (var fs = new FileStream(ipfile, FileMode.Open)) 111 | { 112 | try 113 | { 114 | ipFile = new MemoryStream(); 115 | fs.CopyTo(ipFile); 116 | ipFile.Position = 0; 117 | ipBegin = ReadLongX(ipFile, 4);//readLong4(0); 118 | ipFile.Position = 4; 119 | ipEnd = ReadLongX(ipFile, 4);//readLong4(4); 120 | } 121 | catch (Exception ex) 122 | { 123 | Console.WriteLine(ex); 124 | } 125 | } 126 | 127 | } 128 | catch (Exception ex) 129 | { 130 | throw new Exception(ex.Message); 131 | } 132 | 133 | loc = new IPLocation(); 134 | } 135 | //根据IP地址搜索 136 | #region 根据IP地址搜索 137 | /**/ 138 | /// 139 | ///搜索IP地址搜索 140 | /// 141 | /// 142 | /// 143 | #endregion 144 | public IPLocation SearchIPLocation(string ip) 145 | { 146 | //将字符IP转换为字节 147 | string[] ipSp = ip.Split('.'); 148 | if (ipSp.Length != 4) 149 | { 150 | throw new ArgumentOutOfRangeException("不是合法的IP地址!"); 151 | } 152 | byte[] IP = new byte[4]; 153 | for (int i = 0; i < IP.Length; i++) 154 | { 155 | IP[i] = (byte)(Int32.Parse(ipSp[i]) & 0xFF); 156 | } 157 | IPLocation local = null; 158 | long offset = locateIP(IP); 159 | if (offset != -1) 160 | { 161 | local = getIPLocation(offset); 162 | } 163 | if (local == null) 164 | { 165 | local = new IPLocation(); 166 | local.area = unArea; 167 | local.country = unCountry; 168 | } 169 | return local; 170 | } 171 | //取得具体信息 172 | #region 取得具体信息 173 | /**/ 174 | /// 175 | ///取得具体信息 176 | /// 177 | /// 178 | /// 179 | #endregion 180 | private IPLocation getIPLocation(long offset) 181 | { 182 | ipFile.Position = offset + 4; 183 | //读取第一个字节判断是否是标志字节 184 | byte one = (byte)ipFile.ReadByte(); 185 | if (one == REDIRECT_MODE_1) 186 | { 187 | //第一种模式 188 | //读取国家偏移 189 | long countryOffset = ReadLongX(ipFile, 3);//readLong3(); 190 | //转至偏移处 191 | ipFile.Position = countryOffset; 192 | //再次检查标志字节 193 | byte b = (byte)ipFile.ReadByte(); 194 | if (b == REDIRECT_MODE_2) 195 | { 196 | loc.country = readString(ReadLongX(ipFile, 3));//readString(readLong3()); 197 | ipFile.Position = countryOffset + 4; 198 | } 199 | else 200 | loc.country = readString(countryOffset); 201 | //读取地区标志 202 | loc.area = readArea(ipFile.Position); 203 | } 204 | else if (one == REDIRECT_MODE_2) 205 | { 206 | //第二种模式 207 | loc.country = readString(ReadLongX(ipFile, 3));//readString(readLong3()); 208 | loc.area = readArea(offset + 8); 209 | } 210 | else 211 | { 212 | //普通模式 213 | loc.country = readString(--ipFile.Position); 214 | loc.area = readString(ipFile.Position); 215 | } 216 | return loc; 217 | } 218 | //取得地区信息 219 | #region 取得地区信息 220 | /**/ 221 | /// 222 | ///读取地区名称 223 | /// 224 | /// 225 | /// 226 | #endregion 227 | private string readArea(long offset) 228 | { 229 | ipFile.Position = offset; 230 | byte one = (byte)ipFile.ReadByte(); 231 | if (one == REDIRECT_MODE_1 || one == REDIRECT_MODE_2) 232 | { 233 | ipFile.Position = offset + 1; 234 | long areaOffset = ReadLongX(ipFile, 3);//readLong3(offset + 1); 235 | if (areaOffset == 0) 236 | return unArea; 237 | else 238 | { 239 | return readString(areaOffset); 240 | } 241 | } 242 | else 243 | { 244 | return readString(offset); 245 | } 246 | } 247 | //读取字符串 248 | #region 读取字符串 249 | /**/ 250 | /// 251 | ///读取字符串 252 | /// 253 | /// 254 | /// 255 | #endregion 256 | private string readString(long offset) 257 | { 258 | ipFile.Position = offset; 259 | int i = 0; 260 | for (i = 0, buf[i] = (byte)ipFile.ReadByte(); buf[i] != (byte)(0); buf[++i] = (byte)ipFile.ReadByte()) ; 261 | if (i > 0) 262 | return Encoding.GetEncoding("gb2312").GetString(buf, 0, i); 263 | else 264 | return ""; 265 | } 266 | //查找IP地址所在的绝对偏移量 267 | #region 查找IP地址所在的绝对偏移量 268 | /**/ 269 | /// 270 | ///查找IP地址所在的绝对偏移量 271 | /// 272 | /// 273 | /// 274 | #endregion 275 | private long locateIP(byte[] ip) 276 | { 277 | long m = 0; 278 | int r; 279 | //比较第一个IP项 280 | readIP(ipBegin, b4); 281 | r = compareIP(ip, b4); 282 | if (r == 0) 283 | return ipBegin; 284 | else if (r < 0) 285 | return -1; 286 | //开始二分搜索 287 | for (long i = ipBegin, j = ipEnd; i < j;) 288 | { 289 | m = this.getMiddleOffset(i, j); 290 | readIP(m, b4); 291 | r = compareIP(ip, b4); 292 | if (r > 0) 293 | i = m; 294 | else if (r < 0) 295 | { 296 | if (m == j) 297 | { 298 | j -= IP_RECORD_LENGTH; 299 | m = j; 300 | } 301 | else 302 | { 303 | j = m; 304 | } 305 | } 306 | else 307 | { 308 | ipFile.Position = m + 4; 309 | return ReadLongX(ipFile, 3);//readLong3(m + 4); 310 | } 311 | 312 | } 313 | ipFile.Position = m + 4; 314 | m = ReadLongX(ipFile, 3);//readLong3(m + 4); 315 | readIP(m, b4); 316 | r = compareIP(ip, b4); 317 | if (r <= 0) 318 | return m; 319 | else 320 | return -1; 321 | } 322 | //读出4字节的IP地址 323 | #region 读出4字节的IP地址 324 | /**/ 325 | /// 326 | ///从当前位置读取四字节,此四字节是IP地址 327 | /// 328 | /// 329 | /// 330 | #endregion 331 | private void readIP(long offset, byte[] ip) 332 | { 333 | ipFile.Position = offset; 334 | ipFile.Read(ip, 0, ip.Length); 335 | byte tmp = ip[0]; 336 | ip[0] = ip[3]; 337 | ip[3] = tmp; 338 | tmp = ip[1]; 339 | ip[1] = ip[2]; 340 | ip[2] = tmp; 341 | } 342 | //比较IP地址是否相同 343 | #region 比较IP地址是否相同 344 | /**/ 345 | /// 346 | ///比较IP地址是否相同 347 | /// 348 | /// 349 | /// 350 | ///0:相等,1:ip大于beginIP,-1:小于 351 | #endregion 352 | private int compareIP(byte[] ip, byte[] beginIP) 353 | { 354 | for (int i = 0; i < 4; i++) 355 | { 356 | int r = compareByte(ip[i], beginIP[i]); 357 | if (r != 0) 358 | return r; 359 | } 360 | return 0; 361 | } 362 | //比较两个字节是否相等 363 | #region 比较两个字节是否相等 364 | /**/ 365 | /// 366 | ///比较两个字节是否相等 367 | /// 368 | /// 369 | /// 370 | /// 371 | #endregion 372 | private int compareByte(byte bsrc, byte bdst) 373 | { 374 | if ((bsrc & 0xFF) > (bdst & 0xFF)) 375 | return 1; 376 | else if ((bsrc ^ bdst) == 0) 377 | return 0; 378 | else 379 | return -1; 380 | } 381 | //根据当前位置读取4字节 382 | #region 根据当前位置读取4字节 383 | /**/ 384 | /// 385 | ///从当前位置读取4字节,转换为长整型 386 | /// 387 | /// 388 | /// 389 | #endregion 390 | private long readLong4(long offset) 391 | { 392 | long ret = 0; 393 | ipFile.Position = offset; 394 | ret |= Convert.ToInt64(ipFile.ReadByte() & 0xFF); 395 | ret |= Convert.ToInt64((ipFile.ReadByte() << 8) & 0xFF00); 396 | ret |= Convert.ToInt64((ipFile.ReadByte() << 16) & 0xFF0000); 397 | ret |= Convert.ToInt64((ipFile.ReadByte() << 24) & 0xFF000000); 398 | return ret; 399 | } 400 | //根据当前位置,读取3字节 401 | #region 根据当前位置,读取3字节 402 | /**/ 403 | /// 404 | ///根据当前位置,读取3字节 405 | /// 406 | /// 407 | /// 408 | #endregion 409 | private long readLong3(long offset) 410 | { 411 | long ret = 0; 412 | ipFile.Position = offset; 413 | ret |= Convert.ToInt64(ipFile.ReadByte() & 0xFF); 414 | ret |= Convert.ToInt64((ipFile.ReadByte() << 8) & 0xFF00); 415 | ret |= Convert.ToInt64((ipFile.ReadByte() << 16) & 0xFF0000); 416 | return ret; 417 | } 418 | //从当前位置读取3字节 419 | #region 从当前位置读取3字节 420 | /**/ 421 | /// 422 | ///从当前位置读取3字节 423 | /// 424 | /// 425 | #endregion 426 | private long readLong3() 427 | { 428 | long ret = 0; 429 | ret |= Convert.ToInt64(ipFile.ReadByte() & 0xFF); 430 | ret |= Convert.ToInt64((ipFile.ReadByte() << 8) & 0xFF00); 431 | ret |= Convert.ToInt64((ipFile.ReadByte() << 16) & 0xFF0000); 432 | return ret; 433 | } 434 | 435 | /// 436 | /// 从IP文件中读取指定字节并转换位long 437 | /// 438 | /// 439 | /// 需要转换的字节数,主意不要超过8字节 440 | /// 441 | private static long ReadLongX(Stream stream, int bytesCount) 442 | { 443 | var bytes = new byte[8]; 444 | stream.Read(bytes, 0, bytesCount); 445 | return BitConverter.ToInt64(bytes, 0); 446 | } 447 | 448 | //取得begin和end之间的偏移量 449 | #region 取得begin和end之间的偏移量 450 | /**/ 451 | /// 452 | ///取得begin和end中间的偏移 453 | /// 454 | /// 455 | /// 456 | /// 457 | #endregion 458 | private long getMiddleOffset(long begin, long end) 459 | { 460 | long records = (end - begin) / IP_RECORD_LENGTH; 461 | records >>= 1; 462 | if (records == 0) 463 | records = 1; 464 | return begin + records * IP_RECORD_LENGTH; 465 | } 466 | 467 | static Java2QQWry() 468 | { 469 | Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); 470 | } 471 | } //class QQWry 472 | public class IPLocation 473 | { 474 | public String country; 475 | public String area; 476 | public IPLocation() 477 | { 478 | country = area = ""; 479 | } 480 | public IPLocation getCopy() 481 | { 482 | IPLocation ret = new IPLocation(); 483 | ret.country = country; 484 | ret.area = area; 485 | return ret; 486 | } 487 | } 488 | } 489 | -------------------------------------------------------------------------------- /Sample/Mode2/IpDbAccessor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Text; 5 | 6 | namespace QQWry 7 | { 8 | internal class IpDbAccessor 9 | { 10 | private byte[] InnerBytes; 11 | 12 | public long Position { get; set; } 13 | 14 | public IpDbAccessor(byte[] bytes) 15 | { 16 | InnerBytes = bytes; 17 | } 18 | 19 | public byte ReadByte() 20 | { 21 | var ret = InnerBytes[Position]; 22 | Position++; 23 | return ret; 24 | } 25 | 26 | public void Read(byte[] buffer, int offset, int count) 27 | { 28 | for (var i = 0; i < count; i++) 29 | { 30 | buffer[offset + i] = ReadByte(); 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Sample/Mode2/QQWryIpSearchMode2.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text; 4 | using System.Text.RegularExpressions; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | namespace QQWry 9 | { 10 | /// 11 | /// QQWryIpSearch 请作为单例使用 数据库缓存在内存 12 | /// 13 | public class QQWryIpSearchMode2 : IDisposable, IIpSearch 14 | { 15 | private readonly SemaphoreSlim _initLock = new SemaphoreSlim(initialCount: 1, maxCount: 1); 16 | 17 | private readonly object _versionLock = new(); 18 | 19 | /// 20 | ///第一种模式 21 | /// 22 | private const byte REDIRECT_MODE_1 = 0x01; 23 | 24 | /// 25 | ///第二种模式 26 | /// 27 | private const byte REDIRECT_MODE_2 = 0x02; 28 | 29 | /// 30 | ///每条记录长度 31 | /// 32 | private const int IP_RECORD_LENGTH = 7; 33 | 34 | private const string unCountry = "未知国家"; 35 | private const string unArea = "未知地区"; 36 | 37 | private readonly Encoding _encodingGb2312; 38 | 39 | /// 40 | /// 数据库 缓存 41 | /// 42 | private byte[] _qqwryDbBytes; 43 | 44 | /// 45 | /// 起始定位 46 | /// 47 | private long _startPosition; 48 | 49 | /// 50 | /// 结束定位 51 | /// 52 | private long _endPosition; 53 | 54 | /// 55 | /// 是否初始化 56 | /// 57 | private bool? _init; 58 | 59 | /// 60 | /// IP地址正则验证 61 | /// 62 | private static Regex IpAddressRegex => new Regex(@"(\b(?:(?:2(?:[0-4][0-9]|5[0-5])|[0-1]?[0-9]?[0-9])\.){3}(?:(?:2([0-4][0-9]|5[0-5])|[0-1]?[0-9]?[0-9]))\b)", RegexOptions.Compiled); 63 | 64 | private readonly QQWryOptions _qqwryOptions; 65 | private int? _ipCount; 66 | private string _version; 67 | 68 | /// 69 | /// 70 | /// 记录总数 71 | /// 72 | public int IpCount 73 | { 74 | get 75 | { 76 | if (!_ipCount.HasValue) 77 | { 78 | Init(); 79 | } 80 | 81 | return _ipCount.Value; 82 | } 83 | } 84 | 85 | /// 86 | /// 87 | /// 版本信息 88 | /// 89 | public string Version 90 | { 91 | get 92 | { 93 | if (!string.IsNullOrWhiteSpace(_version)) 94 | { 95 | return _version; 96 | } 97 | lock (_versionLock) 98 | { 99 | if (!string.IsNullOrWhiteSpace(_version)) 100 | { 101 | return _version; 102 | } 103 | _version = GetIpLocation("255.255.255.255").Area; 104 | return _version; 105 | } 106 | } 107 | } 108 | 109 | static QQWryIpSearchMode2() 110 | { 111 | #if NET452 112 | 113 | #else 114 | Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); 115 | #endif 116 | } 117 | 118 | public QQWryIpSearchMode2(QQWryOptions options) 119 | { 120 | _qqwryOptions = options; 121 | _encodingGb2312 = Encoding.GetEncoding("gb2312"); 122 | } 123 | 124 | /// 125 | /// 126 | /// 初始化 127 | /// 128 | /// 129 | public virtual bool Init() 130 | { 131 | if (_init != null) 132 | { 133 | return _init.Value; 134 | } 135 | _initLock.Wait(); 136 | try 137 | { 138 | if (_init != null) 139 | { 140 | return _init.Value; 141 | } 142 | 143 | EnsureFileExist(_qqwryOptions.DbPath); 144 | 145 | #if DEBUG 146 | System.Diagnostics.Debug.WriteLine(format: $"使用IP数据库{_qqwryOptions.DbPath}"); 147 | #endif 148 | 149 | _qqwryDbBytes = FileToBytes(_qqwryOptions.DbPath); 150 | 151 | var ipFile = new IpDbAccessor(_qqwryDbBytes); 152 | _startPosition = ReadLongX(ipFile, 4); 153 | _endPosition = ReadLongX(ipFile, 4); 154 | 155 | //总记录数 156 | _ipCount = Convert.ToInt32((_endPosition - _startPosition) / IP_RECORD_LENGTH + 1); 157 | 158 | _version = null; 159 | _init = true; 160 | } 161 | finally 162 | { 163 | _initLock.Release(); 164 | } 165 | 166 | if (_qqwryDbBytes == null) 167 | { 168 | throw new InvalidOperationException("无法打开IP数据库" + _qqwryOptions.DbPath + "!"); 169 | } 170 | 171 | return true; 172 | 173 | } 174 | 175 | /// 176 | /// 177 | /// 获取指定IP所在地理位置 178 | /// 179 | /// 要查询的IP地址 180 | /// 181 | public virtual IpLocation GetIpLocation(string strIp) 182 | { 183 | var loc = new IpLocation 184 | { 185 | Ip = strIp 186 | }; 187 | 188 | var ip = IpToLong(strIp); 189 | 190 | if (ip == IpToLong("127.0.0.1")) 191 | { 192 | loc.Country = "本机内部环回地址"; 193 | loc.Area = string.Empty; 194 | return loc; 195 | } 196 | 197 | if (!Init()) 198 | { 199 | return loc; 200 | } 201 | 202 | return ReadLocation(loc, strIp); 203 | } 204 | 205 | /// 206 | /// 207 | /// 初始化 208 | /// 209 | /// 210 | /// 211 | public virtual async Task InitAsync(CancellationToken token = default) 212 | { 213 | if (_init != null) 214 | { 215 | return _init.Value; 216 | } 217 | 218 | await _initLock.WaitAsync(token); 219 | 220 | try 221 | { 222 | if (_init != null) 223 | { 224 | return _init.Value; 225 | } 226 | 227 | EnsureFileExist(_qqwryOptions.DbPath); 228 | 229 | #if DEBUG 230 | System.Diagnostics.Debug.WriteLine("使用IP数据库{0}", _qqwryOptions.DbPath); 231 | #endif 232 | _qqwryDbBytes = FileToBytes(_qqwryOptions.DbPath); 233 | 234 | var ipFile = new IpDbAccessor(_qqwryDbBytes); 235 | _startPosition = ReadLongX(ipFile, 4); 236 | _endPosition = ReadLongX(ipFile, 4); 237 | 238 | //总记录数 239 | _ipCount = Convert.ToInt32((_endPosition - _startPosition) / IP_RECORD_LENGTH + 1); 240 | 241 | _version = null; 242 | _init = true; 243 | } 244 | finally 245 | { 246 | _initLock.Release(); 247 | } 248 | 249 | if (_qqwryDbBytes == null) 250 | { 251 | throw new InvalidOperationException("无法打开IP数据库" + _qqwryOptions.DbPath + "!"); 252 | } 253 | 254 | return true; 255 | } 256 | 257 | /// 258 | /// 259 | /// 获取指定IP所在地理位置 260 | /// 261 | /// 要查询的IP地址 262 | /// 263 | /// 264 | public virtual async Task GetIpLocationAsync(string strIp, CancellationToken token = default) 265 | { 266 | var loc = new IpLocation 267 | { 268 | Ip = strIp 269 | }; 270 | 271 | long ip = IpToLong(strIp); 272 | if (ip == IpToLong("127.0.0.1")) 273 | { 274 | loc.Country = "本机内部环回地址"; 275 | loc.Area = string.Empty; 276 | return loc; 277 | } 278 | if (!await InitAsync(token)) 279 | { 280 | return loc; 281 | } 282 | return ReadLocation(loc, strIp); 283 | } 284 | 285 | /// 286 | /// 287 | /// 检查IP合法性 288 | /// 289 | /// 290 | /// 291 | public bool CheckIp(string ip) 292 | { 293 | return IpAddressRegex.IsMatch(ip); 294 | } 295 | 296 | /// 297 | /// 298 | /// 释放 299 | /// 300 | public void Dispose() 301 | { 302 | _initLock?.Dispose(); 303 | _qqwryDbBytes = null; 304 | _init = null; 305 | } 306 | 307 | /// 308 | /// 将字符串形式的IP转换位long 309 | /// 310 | /// 311 | /// 312 | private static long IpToLong(string strIp) 313 | { 314 | var ipBytes = new byte[8]; 315 | var strArr = strIp.Split(new char[] { '.' }); 316 | for (var i = 0; i < 4; i++) 317 | { 318 | ipBytes[i] = byte.Parse(strArr[3 - i]); 319 | } 320 | return BitConverter.ToInt64(ipBytes, 0); 321 | } 322 | 323 | private static byte[] FileToBytes(string fileName) 324 | { 325 | using (FileStream fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read)) 326 | { 327 | byte[] bytes = new byte[fileStream.Length]; 328 | 329 | fileStream.Read(bytes, 0, bytes.Length); 330 | 331 | fileStream.Close(); 332 | 333 | return bytes; 334 | } 335 | } 336 | 337 | private static void EnsureFileExist(string ipDbPath) 338 | { 339 | var dir = Path.GetDirectoryName(ipDbPath); 340 | if (!Directory.Exists(dir)) 341 | { 342 | Directory.CreateDirectory(dir ?? throw new InvalidOperationException()); 343 | } 344 | if (!File.Exists(ipDbPath)) 345 | { 346 | #if DEBUG 347 | System.Diagnostics.Debug.WriteLine(format: "无法找到IP数据库{0}", ipDbPath); 348 | #endif 349 | throw new Exception($"无法找到IP数据库{ipDbPath}"); 350 | } 351 | 352 | } 353 | 354 | /// 355 | ///搜索IP地址搜索 356 | /// 357 | /// 358 | /// 359 | public IpLocation ReadLocation(IpLocation loc, string ip) 360 | { 361 | //将字符IP转换为字节 362 | string[] ipSp = ip.Split('.'); 363 | if (ipSp.Length != 4) 364 | { 365 | throw new ArgumentOutOfRangeException("不是合法的IP地址!"); 366 | } 367 | byte[] IP = new byte[4]; 368 | for (int i = 0; i < IP.Length; i++) 369 | { 370 | IP[i] = (byte)(int.Parse(ipSp[i]) & 0xFF); 371 | } 372 | var ipFile = new IpDbAccessor(_qqwryDbBytes); 373 | long offset = locateIP(ipFile, IP); 374 | if (offset != -1) 375 | { 376 | loc = getIPLocation(loc, ipFile, offset); 377 | } 378 | return loc; 379 | } 380 | 381 | /// 382 | ///取得具体信息 383 | /// 384 | /// 385 | /// 386 | private IpLocation getIPLocation(IpLocation loc, IpDbAccessor ipFile, long offset) 387 | { 388 | ipFile.Position = offset + 4; 389 | //读取第一个字节判断是否是标志字节 390 | byte one = (byte)ipFile.ReadByte(); 391 | if (one == REDIRECT_MODE_1) 392 | { 393 | //第一种模式 394 | //读取国家偏移 395 | long countryOffset = ReadLongX(ipFile, 3);//readLong3(); 396 | //转至偏移处 397 | ipFile.Position = countryOffset; 398 | //再次检查标志字节 399 | byte b = (byte)ipFile.ReadByte(); 400 | if (b == REDIRECT_MODE_2) 401 | { 402 | loc.Country = readString(ipFile, ReadLongX(ipFile, 3));//readString(readLong3()); 403 | ipFile.Position = countryOffset + 4; 404 | } 405 | else 406 | loc.Country = readString(ipFile, countryOffset); 407 | //读取地区标志 408 | loc.Area = readArea(ipFile, ipFile.Position); 409 | } 410 | else if (one == REDIRECT_MODE_2) 411 | { 412 | //第二种模式 413 | loc.Country = readString(ipFile, ReadLongX(ipFile, 3));//readString(readLong3()); 414 | loc.Area = readArea(ipFile, offset + 8); 415 | } 416 | else 417 | { 418 | //普通模式 419 | loc.Country = readString(ipFile, --ipFile.Position); 420 | loc.Area = readString(ipFile, ipFile.Position); 421 | } 422 | return loc; 423 | } 424 | 425 | /// 426 | ///读取地区名称 427 | /// 428 | /// 429 | /// 430 | private string readArea(IpDbAccessor ipFile, long offset) 431 | { 432 | ipFile.Position = offset; 433 | byte one = (byte)ipFile.ReadByte(); 434 | if (one == REDIRECT_MODE_1 || one == REDIRECT_MODE_2) 435 | { 436 | ipFile.Position = offset + 1; 437 | long areaOffset = ReadLongX(ipFile, 3);//readLong3(offset + 1); 438 | if (areaOffset == 0) 439 | return unArea; 440 | else 441 | { 442 | return readString(ipFile, areaOffset).Replace(" CZ88.NET", ""); 443 | } 444 | } 445 | else 446 | { 447 | return readString(ipFile, offset).Replace(" CZ88.NET", ""); 448 | } 449 | } 450 | 451 | /// 452 | ///读取字符串 453 | /// 454 | /// 455 | /// 456 | private string readString(IpDbAccessor ipFile, long offset) 457 | { 458 | var buf = new byte[100]; 459 | ipFile.Position = offset; 460 | int i = 0; 461 | for (i = 0, buf[i] = (byte)ipFile.ReadByte(); 462 | buf[i] != (byte)(0); buf[++i] = (byte)ipFile.ReadByte()) ; 463 | if (i > 0) 464 | return _encodingGb2312.GetString(buf, 0, i); 465 | else 466 | return ""; 467 | } 468 | 469 | /// 470 | ///查找IP地址所在的绝对偏移量 471 | /// 472 | /// 473 | /// 474 | private long locateIP(IpDbAccessor ipFile, byte[] ip) 475 | { 476 | long m = 0; 477 | int r; 478 | var b4 = new byte[4]; 479 | //比较第一个IP项 480 | readIP(ipFile, _startPosition, b4); 481 | r = compareIP(ip, b4); 482 | if (r == 0) 483 | return _startPosition; 484 | else if (r < 0) 485 | return -1; 486 | //开始二分搜索 487 | for (long i = _startPosition, j = _endPosition; i < j;) 488 | { 489 | m = this.getMiddleOffset(i, j); 490 | readIP(ipFile, m, b4); 491 | r = compareIP(ip, b4); 492 | if (r > 0) 493 | i = m; 494 | else if (r < 0) 495 | { 496 | if (m == j) 497 | { 498 | j -= IP_RECORD_LENGTH; 499 | m = j; 500 | } 501 | else 502 | { 503 | j = m; 504 | } 505 | } 506 | else 507 | { 508 | ipFile.Position = m + 4; 509 | return ReadLongX(ipFile, 3);//readLong3(m + 4); 510 | } 511 | 512 | } 513 | ipFile.Position = m + 4; 514 | m = ReadLongX(ipFile, 3);//readLong3(m + 4); 515 | readIP(ipFile, m, b4); 516 | r = compareIP(ip, b4); 517 | if (r <= 0) 518 | return m; 519 | else 520 | return -1; 521 | } 522 | 523 | /// 524 | ///从当前位置读取四字节,此四字节是IP地址 525 | /// 526 | /// 527 | /// 528 | private void readIP(IpDbAccessor ipFile, long offset, byte[] ip) 529 | { 530 | ipFile.Position = offset; 531 | ipFile.Read(ip, 0, ip.Length); 532 | byte tmp = ip[0]; 533 | ip[0] = ip[3]; 534 | ip[3] = tmp; 535 | tmp = ip[1]; 536 | ip[1] = ip[2]; 537 | ip[2] = tmp; 538 | } 539 | 540 | /// 541 | ///比较IP地址是否相同 542 | /// 543 | /// 544 | /// 545 | ///0:相等,1:ip大于beginIP,-1:小于 546 | private int compareIP(byte[] ip, byte[] beginIP) 547 | { 548 | for (int i = 0; i < 4; i++) 549 | { 550 | int r = compareByte(ip[i], beginIP[i]); 551 | if (r != 0) 552 | return r; 553 | } 554 | return 0; 555 | } 556 | 557 | /// 558 | ///比较两个字节是否相等 559 | /// 560 | /// 561 | /// 562 | /// 563 | private int compareByte(byte bsrc, byte bdst) 564 | { 565 | if ((bsrc & 0xFF) > (bdst & 0xFF)) 566 | return 1; 567 | else if ((bsrc ^ bdst) == 0) 568 | return 0; 569 | else 570 | return -1; 571 | } 572 | 573 | /// 574 | ///从当前位置读取4字节,转换为长整型 575 | /// 576 | /// 577 | /// 578 | private long readLong4(IpDbAccessor ipFile, long offset) 579 | { 580 | long ret = 0; 581 | ipFile.Position = offset; 582 | ret |= Convert.ToInt64(ipFile.ReadByte() & 0xFF); 583 | ret |= Convert.ToInt64((ipFile.ReadByte() << 8) & 0xFF00); 584 | ret |= Convert.ToInt64((ipFile.ReadByte() << 16) & 0xFF0000); 585 | ret |= Convert.ToInt64((ipFile.ReadByte() << 24) & 0xFF000000); 586 | return ret; 587 | } 588 | 589 | /// 590 | ///根据当前位置,读取3字节 591 | /// 592 | /// 593 | /// 594 | private long readLong3(IpDbAccessor ipFile, long offset) 595 | { 596 | long ret = 0; 597 | ipFile.Position = offset; 598 | ret |= Convert.ToInt64(ipFile.ReadByte() & 0xFF); 599 | ret |= Convert.ToInt64((ipFile.ReadByte() << 8) & 0xFF00); 600 | ret |= Convert.ToInt64((ipFile.ReadByte() << 16) & 0xFF0000); 601 | return ret; 602 | } 603 | 604 | /// 605 | ///从当前位置读取3字节 606 | /// 607 | /// 608 | private long readLong3(IpDbAccessor ipFile) 609 | { 610 | long ret = 0; 611 | ret |= Convert.ToInt64(ipFile.ReadByte() & 0xFF); 612 | ret |= Convert.ToInt64((ipFile.ReadByte() << 8) & 0xFF00); 613 | ret |= Convert.ToInt64((ipFile.ReadByte() << 16) & 0xFF0000); 614 | return ret; 615 | } 616 | 617 | /// 618 | /// 从IP文件中读取指定字节并转换位long 619 | /// 620 | /// 621 | /// 需要转换的字节数,主意不要超过8字节 622 | /// 623 | private long ReadLongX(IpDbAccessor stream, int bytesCount) 624 | { 625 | var bytes = new byte[8]; 626 | stream.Read(bytes, 0, bytesCount); 627 | return BitConverter.ToInt64(bytes, 0); 628 | } 629 | 630 | /// 631 | ///取得begin和end中间的偏移 632 | /// 633 | /// 634 | /// 635 | /// 636 | private long getMiddleOffset(long begin, long end) 637 | { 638 | long records = (end - begin) / IP_RECORD_LENGTH; 639 | records >>= 1; 640 | if (records == 0) 641 | records = 1; 642 | return begin + records * IP_RECORD_LENGTH; 643 | } 644 | } 645 | } 646 | -------------------------------------------------------------------------------- /Sample/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using BenchmarkDotNet.Running; 6 | 7 | using Microsoft.Extensions.DependencyInjection; 8 | 9 | using QQWry; 10 | using QQWry.DependencyInjection; 11 | 12 | namespace Sample 13 | { 14 | class Program 15 | { 16 | private static async Task Main(string[] args) 17 | { 18 | Console.WriteLine("QQWry Sample!"); 19 | 20 | var config = new QQWryOptions() 21 | { 22 | DbPath = BenchmarkTest.MapRootPath("qqwry.dat") 23 | }; 24 | #region QQWry 25 | Console.WriteLine(""); 26 | Console.WriteLine("QQWry"); 27 | 28 | var ipSearch = new QQWryIpSearch(config); 29 | var ipSearchMode2 = new QQWryIpSearchMode2(config); 30 | 31 | //可选, 若不调用则在首次使用时, 自动先初始化 32 | await ipSearch.InitAsync(); 33 | await ipSearchMode2.InitAsync(); 34 | 35 | for (var i = 0; i < 10; i++) 36 | { 37 | var ipLocation = await ipSearch.GetIpLocationAsync(BenchmarkTest.GetRandomIp(ipSearch)); 38 | Write(ipLocation); 39 | } 40 | Console.WriteLine("记录总数" + ipSearch.IpCount); 41 | Console.WriteLine("版本" + ipSearch.Version); 42 | 43 | #endregion 44 | 45 | #region QQWry.DependencyInjection 46 | 47 | Console.WriteLine(""); 48 | Console.WriteLine("QQWry.DependencyInjection"); 49 | 50 | var service = new ServiceCollection(); 51 | service.AddQQWry(config); 52 | var serviceProvider = service.BuildServiceProvider(); 53 | 54 | using (var scope = serviceProvider.CreateScope()) 55 | { 56 | var ipSearchInterface = scope.ServiceProvider.GetRequiredService(); 57 | for (var i = 0; i < 10; i++) 58 | { 59 | var ipLocation = await ipSearch.GetIpLocationAsync(BenchmarkTest.GetRandomIp(ipSearch)); 60 | Write(ipLocation); 61 | } 62 | Console.WriteLine("记录总数" + ipSearchInterface.IpCount); 63 | Console.WriteLine("版本" + ipSearchInterface.Version); 64 | } 65 | 66 | 67 | #endregion 68 | 69 | #region Speed 70 | 71 | Console.WriteLine(""); 72 | Console.WriteLine("Speed"); 73 | 74 | await ExecuteBatchAsync(100, ipSearch); 75 | await ExecuteBatchAsync(1_000, ipSearch); 76 | await ExecuteBatchAsync(10_000, ipSearch); 77 | await ExecuteBatchAsync(100_000, ipSearch); 78 | await ExecuteBatchAsync(1_000_000, ipSearch); 79 | 80 | #endregion 81 | 82 | #if RELEASE 83 | var summary = BenchmarkRunner.Run(); 84 | #endif 85 | 86 | Console.ReadKey(); 87 | } 88 | 89 | private static void Write(IpLocation ipLocation) 90 | { 91 | Console.WriteLine($"ip:{ipLocation.Ip},country:{ipLocation.Country},area:{ipLocation.Area}"); 92 | } 93 | 94 | private static void Write(string ip, IPLocation ipLocation) 95 | { 96 | Console.WriteLine($"ip:{ip},country:{ipLocation.country},area:{ipLocation.area}"); 97 | } 98 | 99 | private static async Task ExecuteBatchAsync(int count, IIpSearch ipSearch) 100 | { 101 | // prepare 102 | var ips = Enumerable.Range(0, count).Select(_ => BenchmarkTest.GetRandomIp(ipSearch)).ToArray(); 103 | 104 | var sw = Stopwatch.StartNew(); 105 | 106 | foreach (var ip in ips) 107 | { 108 | var _ = await ipSearch.GetIpLocationAsync(ip); 109 | } 110 | 111 | Console.WriteLine("{0}条耗费{1}ms", count, sw.ElapsedMilliseconds); 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /Sample/Sample.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net6.0 6 | 7 | 8 | 9 | TRACE 10 | 11 | 12 | 13 | DEBUG;TRACE 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | Always 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /qqwry.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JadynWong/IP_qqwry/3fdc0035bfe7973d07b94ffd2cdf4a8b80bae73d/qqwry.dat --------------------------------------------------------------------------------