类文件[IPHelper.vb]:
Imports System
Imports System.Collections.Generic
Imports System.IO
Imports System.Linq
Imports System.Net
Imports System.Text
Imports System.Threading.Tasks
Namespace IPLibrary
''' <summary>
''' IP归属地查询
''' qqwry.dat文件的结构原理参考:[qqwry.dat的数据结构图文解释](https://www.jb51.net/article/17197_all.htm)
''' </summary>
Public Class IPHelper
' IP库文件地址
Private ReadOnly mLibraryFilePath As String
' 第一条索引的绝对地址
Private ReadOnly mFirstIndex As UInteger
' 最后一条索引的绝对地址
Private ReadOnly mLastIndex As UInteger
Public Sub New()
mLibraryFilePath = AppDomain.CurrentDomain.BaseDirectory & "qqwry.dat"
' 定位索引区
Using fs = New FileStream(mLibraryFilePath, FileMode.Open, FileAccess.Read)
Dim reader = New BinaryReader(fs)
' 文件头
Dim header = reader.ReadBytes(IPFormat.HeaderLength)
mFirstIndex = BitConverter.ToUInt32(header, 0)
mLastIndex = BitConverter.ToUInt32(header, 4)
End Using
End Sub
''' <summary>
''' 获取IP的归属地
''' </summary>
''' <param name="ip">IP地址</param>
''' <returns></returns>
Public Function GetIpLocation(ip As IPAddress) As IPLocation
Using fs = New FileStream(mLibraryFilePath, FileMode.Open, FileAccess.Read)
Dim reader = New BinaryReader(fs)
' 从大端顺序转为小端顺序
Dim ipBytes = BitConverter.GetBytes(IPAddress.NetworkToHostOrder(BitConverter.ToInt32(ip.GetAddressBytes(), 0)))
Dim offset = FindIpStartPos(fs, reader, mFirstIndex, mLastIndex, ipBytes)
Return GetIPInfo(fs, reader, offset, ip, ipBytes)
End Using
End Function
''' <summary>
''' 在索引区中查找目标IP的索引的起始位置
''' </summary>
''' <param name="fs"></param>
''' <param name="reader"></param>
''' <param name="startIndex"></param>
''' <param name="endIndex"></param>
''' <param name="ip"></param>
''' <returns></returns>
Private Function FindIpStartPos(fs As FileStream, reader As BinaryReader, startIndex As UInteger, endIndex As UInteger, ip As Byte()) As UInteger
Dim ipVal = BitConverter.ToUInt32(ip, 0)
fs.Position = startIndex
While fs.Position <= endIndex
Dim bytes = reader.ReadBytes(IPFormat.IndexRecLength)
Dim curVal = BitConverter.ToUInt32(bytes, 0)
If curVal > ipVal Then
fs.Position = fs.Position - 2 * IPFormat.IndexRecLength
bytes = reader.ReadBytes(IPFormat.IndexRecLength)
Dim offsetByte = New Byte(3) {}
Array.Copy(bytes, 4, offsetByte, 0, 3)
Return BitConverter.ToUInt32(offsetByte, 0)
End If
End While
Return 0
End Function
''' <summary>
''' 读取目标IP的信息
''' </summary>
''' <param name="fs"></param>
''' <param name="reader"></param>
''' <param name="offset"></param>
''' <param name="ipToLoc"></param>
''' <param name="ipBytes"></param>
''' <returns></returns>
Private Function GetIPInfo(fs As FileStream, reader As BinaryReader, offset As Long, ipToLoc As IPAddress, ipBytes As Byte()) As IPLocation
fs.Position = offset
' 确认目标IP在记录的IP范围内
Dim endIP = reader.ReadBytes(4)
Dim endIpVal = BitConverter.ToUInt32(endIP, 0)
Dim ipVal = BitConverter.ToUInt32(ipBytes, 0)
If endIpVal < ipVal Then Return Nothing
Dim country As String
Dim zone As String
' 读取重定向模式字节
Dim pattern = reader.ReadByte()
If pattern = RedirectMode.Mode_1 Then
Dim countryOffsetBytes = reader.ReadBytes(IPFormat.RecOffsetLength)
Dim countryOffset = IPFormat.ToUint(countryOffsetBytes)
If countryOffset = 0 Then Return GetUnknownLocation(ipToLoc)
fs.Position = countryOffset
If fs.ReadByte() = RedirectMode.Mode_2 Then
Return ReadMode2Record(fs, reader, ipToLoc)
End If
fs.Position -= 1
country = ReadString(reader)
zone = ReadZone(fs, reader, Convert.ToUInt32(fs.Position))
ElseIf pattern = RedirectMode.Mode_2 Then
Return ReadMode2Record(fs, reader, ipToLoc)
Else
fs.Position -= 1
country = ReadString(reader)
zone = ReadZone(fs, reader, Convert.ToUInt32(fs.Position))
End If
Return New IPLocation(ipToLoc, country, zone)
End Function
''' <summary>
''' 获取识别失败的结果
''' </summary>
''' <param name="ip"></param>
''' <returns></returns>
Private Function GetUnknownLocation(ip As IPAddress) As IPLocation
Dim country = IPFormat.UnknownCountry
Dim zone = IPFormat.UnknownZone
Return New IPLocation(ip, country, zone)
End Function
''' <summary>
''' 按模式2读取记录
''' </summary>
''' <param name="fs"></param>
''' <param name="reader"></param>
''' <param name="ip"></param>
''' <returns></returns>
Private Function ReadMode2Record(fs As FileStream, reader As BinaryReader, ip As IPAddress) As IPLocation
Dim countryOffset = IPFormat.ToUint(reader.ReadBytes(IPFormat.RecOffsetLength))
Dim curOffset = Convert.ToUInt32(fs.Position)
If countryOffset = 0 Then Return GetUnknownLocation(ip)
fs.Position = countryOffset
Dim country = ReadString(reader)
Dim zone = ReadZone(fs, reader, curOffset)
Return New IPLocation(ip, country, zone)
End Function
''' <summary>
''' 从二进制文件中读取字符串
''' </summary>
''' <param name="reader"></param>
''' <returns></returns>
Private Function ReadString(reader As BinaryReader) As String
Dim stringLst = New List(Of Byte)()
Dim byteRead As Byte = 0
Do
byteRead = reader.ReadByte()
If byteRead <> 0 Then
stringLst.Add(byteRead)
End If
Loop While byteRead <> 0
Return Encoding.GetEncoding("gb2312").GetString(stringLst.ToArray())
End Function
''' <summary>
''' 读取区域信息
''' </summary>
''' <param name="fs"></param>
''' <param name="reader"></param>
''' <param name="offset"></param>
''' <returns></returns>
Private Function ReadZone(fs As FileStream, reader As BinaryReader, offset As UInteger) As String
fs.Position = offset
Dim b = reader.ReadByte()
If b = RedirectMode.Mode_1 OrElse b = RedirectMode.Mode_2 Then
Dim zoneOffset = IPFormat.ToUint(reader.ReadBytes(3))
If zoneOffset = 0 Then Return IPFormat.UnknownZone
Return ReadZone(fs, reader, zoneOffset)
End If
fs.Position -= 1
Return ReadString(reader)
End Function
End Class
Public Class IPLocation
Public Sub New(ip As IPAddress, country As String, loc As String)
ip = ip
Me.Country = country
Zone = loc
End Sub
Public ReadOnly Property IP As IPAddress
Public ReadOnly Property Country As String
Public ReadOnly Property Zone As String
End Class
Public Class IPFormat
' 文件头为8个字节
Public Shared ReadOnly HeaderLength As Integer = 8
' 一条索引的长度
Public Shared ReadOnly IndexRecLength As Integer = 7
Public Shared ReadOnly IndexOffset As Integer = 3
Public Shared ReadOnly RecOffsetLength As Integer = 3
Public Shared ReadOnly UnknownCountry As String = "未知的国家"
Public Shared ReadOnly UnknownZone As String = "未知的地区"
Public Shared Function ToUint(val As Byte()) As UInteger
If val.Length > 4 Then
Throw New ArgumentException()
End If
If val.Length < 4 Then
Dim copyBytes = New Byte(3) {}
Array.Copy(val, 0, copyBytes, 0, val.Length)
Return BitConverter.ToUInt32(copyBytes, 0)
End If
Return BitConverter.ToUInt32(val, 0)
End Function
End Class
''' <summary>
''' 重定向模式
''' </summary>
Public Class RedirectMode
Public Shared ReadOnly Mode_1 As Integer = 1
Public Shared ReadOnly Mode_2 As Integer = 2
End Class
End Namespace
窗体1:
Dim strIp As String = textBox1.Text.Trim()
Dim seeker As New IPLibrary.IPHelper()
Dim location As IPLibrary.IPLocation = seeker.GetIpLocation(IPAddress.Parse(strIp))
textBox2.Text = location.Country & location.Zone