VB.NET 调用纯真IP库进行IP查询

VB.NET · 2023-12-15 · 297 人浏览

类文件[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