Files
2026-07-10 15:25:05 +08:00

412 lines
13 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.IO;
using System.Text;
using System.Web;
namespace Zhaizj.Framework.Utils
{
/// <summary>
/// 从IP数据库中读取IP与实际地址
/// </summary>
public class IPQuery : IDisposable
{
/// <summary>
/// 地理位置结构体
/// </summary>
public struct Location
{
public string Region;
public string City;
}
#region 成员变量
private string m_ipFileAdrress; //IPQWR.dat文件地址
private string IPFILEADDR = AppDomain.CurrentDomain.BaseDirectory + "IPQuery.Dat"; //默认IP查询数据库文件地址
private FileStream m_fs;
private Int32 m_indexOffsetFirst; //第一个索引绝对偏移地址
private Int32 m_indexOffsetLast; //最后一个索引绝对偏移地址
private const int INDEX_SIZE = 7; //一个索引占用的字节数
private const byte REDIRECT_MODE_1 = 0x01; //重定向模式1
private const byte REDIRECT_MODE_2 = 0x02; //重定向模式2
#endregion
#region 构造函数
/// <summary>
/// IP地址查询对象的构造函数
/// </summary>
/// <param name="ipFileAddress">IP数据库文件地址</param>
public IPQuery(string ipFileAddress)
{
this.m_ipFileAdrress = ipFileAddress;
try
{
m_fs = new FileStream(m_ipFileAdrress, FileMode.Open);
//m_fs = new FileStream(m_ipFileAdrress, FileMode.Open, FileAccess.Read);
}
catch (FileNotFoundException e)
{
throw e;
}
finally
{
Initialize();
}
}
/// <summary>
/// IP地址查询对象的构造函数
/// </summary>
public IPQuery()
{
//获取项目根目录后寻找文件位置
this.m_ipFileAdrress = IPFILEADDR;
try
{
m_fs = new FileStream(m_ipFileAdrress, FileMode.Open, FileAccess.Read);
}
catch (FileNotFoundException e)
{
throw e;
}
finally
{
Initialize();
}
}
#endregion
#region IDisposable 成员
/// <summary>
/// 释放非托管资源
/// </summary>
public void Dispose()
{
m_fs.Dispose();
m_fs.Close();//关闭文件流
}
#endregion
/// <summary>
/// 根据IP查找IP段所在的地理位置
/// </summary>
/// <param name="ip">IP地址数组</param>
/// <returns>地理位置</returns>
public Location GetIPLocation(byte[] ip)
{
if (!ValidateIP(ip))
{
throw new ArgumentException("IP数组有误");
}
Int32 offset = FindIpRecdOffset(ip);
Location location = ReadLocation(offset);
return location;
}
/// <summary>
/// 根据IP查找IP段所在的地理位置
/// </summary>
/// <param name="ip">IP地址数组</param>
/// <returns>地理位置</returns>
public Location GetIPLocation(string ip)
{
byte[] bIp = new byte[4];
int i = 0;
foreach (string secIp in ip.Split(new char[] { '.' }))
{
bIp[i++] = (byte)(Convert.ToInt16(secIp));
}
if (!ValidateIP(bIp))
{
throw new ArgumentException("IP数组有误");
}
Int32 offset = FindIpRecdOffset(bIp);
Location location = ReadLocation(offset);
return location;
}
#region 私有成员方法
/// <summary>
/// 初始化类数据
/// </summary>
private void Initialize()
{
this.ReadIndexOffset();
}
/// <summary>
/// 指定字节序列编码成Int32类型数据
/// </summary>
/// <param name="buf">字节数组</param>
/// <param name="start">要编码序列在数组中的起始位置</param>
/// <param name="count">要编码序列的长度大小</param>
/// <returns>编码出的Int32类型值</returns>
private Int32 EncodeToInt32(byte[] buf, int start, int count)
{
//检查边界条件
if (count > 4 ||
count < 1 ||
start < 0 ||
start > buf.GetLength(0) - 1)
{
throw new ArgumentOutOfRangeException();
}
else
{
//编码Little-Endian字节序列
Int32 offset = 0;
for (int i = 0; i < count; i++)
{
offset |= (buf[start + i] << (i * 8));
}
return offset;
}
}
/// <summary>
/// 从IP数据库文件中读取索引绝对偏移地址
/// </summary>
private void ReadIndexOffset()
{
//读取文件头数据
byte[] head = new byte[8];
m_fs.Seek(0, SeekOrigin.Begin);
m_fs.Read(head, 0, 8);
//解析第一个索引绝对偏移
m_indexOffsetFirst = EncodeToInt32(head, 0, 4);
//解析第一个索引绝对偏移
m_indexOffsetLast = EncodeToInt32(head, 4, 4);
}
/// <summary>
/// 检验IP的合法性,IP数组元素必须为4且字段值在[0,255]内
/// </summary>
/// <param name="ip">IP数组,有且只有4个元素</param>
/// <returns>IP合法返回true,否则返回false</returns>
private bool ValidateIP(byte[] ip)
{
if (ip.GetLength(0) != 4)
{
return false;
//throw new ArgumentException("ip数组元素必须为4");
}
else
{
for (int i = 0; i < 4; i++)
{
if (ip[i] > 255 || ip[i] < 0)
{
return false;
//throw new ArgumentException("IP字段值非法,必须在[0,255]内。");
}
}
}
return true;
}
/// <summary>
/// 按照字典排序法,比较两个IP的大小
/// </summary>
/// <param name="ip1">第一个IP</param>
/// <param name="ip2">第二个IP</param>
/// <returns>0代表相等;1代表ip1大于ip22代表ip1小于ip2</returns>
private int CompareIP(byte[] ip1, byte[] ip2)
{
for (int i = 0; i < 4; i++)
{
if (ip1[i] < ip2[i])
{
return -1;
}
else if (ip1[i] > ip2[i])
{
return 1;
}
}
return 0;
}
/// <summary>
/// 使用二分搜索法,在索引中检索到IP记录的绝对偏移
/// </summary>
/// <returns>IP记录的绝对偏移地址</returns>
private Int32 FindIpRecdOffset(byte[] ip)
{
Int32 first = m_indexOffsetFirst;
Int32 last = m_indexOffsetLast;
byte[] indexBuf = new byte[INDEX_SIZE];
while (last - first >= INDEX_SIZE)
{
Int32 mid = first + (last - first) / 2 / INDEX_SIZE * INDEX_SIZE;
m_fs.Seek(mid, SeekOrigin.Begin);
m_fs.Read(indexBuf, 0, INDEX_SIZE);
if (mid == first)
{
break;
}
//复制索引中的IP至临时ip数组中
byte[] midIp = new byte[4];
for (int i = 0; i < 4; i++)
{
//索引中的ip顺序是从低字段到高字段,转换成从高到底
midIp[i] = indexBuf[3 - i];
}
if (CompareIP(ip, midIp) < 0)
{
last = mid;
}
else if (CompareIP(ip, midIp) > 0)
{
first = mid;
}
else
{
break;
}
}
//获取IP地理位置记录的绝对偏移记录
Int32 offset = EncodeToInt32(indexBuf, 4, INDEX_SIZE - 4);
return offset;
}
/// <summary>
/// 从一条IP信息记录中读取出地理位置数据
/// </summary>
/// <param name="offset">IP信息绝对偏移地址</param>
/// <returns>IP所在的地理位置</returns>
private Location ReadLocation(Int32 offset)
{
if (offset < 8 || offset >= m_indexOffsetFirst)
{
throw new ArgumentOutOfRangeException("offset", "offset值不在IP信息记录范围内");
}
m_fs.Seek(offset + 4, SeekOrigin.Begin);
Location location = new Location { Region = "", City = "" };
byte mode = (byte)(m_fs.ReadByte());
if (mode == REDIRECT_MODE_1)
{
byte[] offsetBuf = new byte[3];
m_fs.Read(offsetBuf, 0, 3);
Int32 contentOffset = EncodeToInt32(offsetBuf, 0, 3);
m_fs.Seek(contentOffset, SeekOrigin.Begin);
mode = (byte)(m_fs.ReadByte());
if (mode == REDIRECT_MODE_2)
{
m_fs.Read(offsetBuf, 0, 3);
Int32 regionOffset = EncodeToInt32(offsetBuf, 0, 3);
location.Region = ReadString(regionOffset);
Int32 cityOffset = contentOffset + 4;
m_fs.Seek(cityOffset, SeekOrigin.Begin);
mode = (byte)(m_fs.ReadByte());
//第三种情况的结构
if (mode == REDIRECT_MODE_1 || mode == REDIRECT_MODE_2)
{
m_fs.Seek(cityOffset + 1, SeekOrigin.Begin);
m_fs.Read(offsetBuf, 0, 3);
cityOffset = EncodeToInt32(offsetBuf, 0, 3);
location.City = ReadString(cityOffset);
}
//第二种情况的结构
else
{
m_fs.Seek(cityOffset, SeekOrigin.Begin);
location.City = ReadString(cityOffset);
}
}
//第一种情况的结构
else
{
//读取区域和城市字符串
Int32 endOffset;
location.Region = ReadString(contentOffset, out endOffset);
location.City = ReadString(endOffset);
}
}
//第四种情况
else if (mode == REDIRECT_MODE_2)
{
Int32 regionOffset = offset + 5;
byte[] offsetBuf = new byte[3];
m_fs.Seek(regionOffset, SeekOrigin.Begin);
m_fs.Read(offsetBuf, 0, 3);
regionOffset = EncodeToInt32(offsetBuf, 0, 3);
location.Region = ReadString(regionOffset);
Int32 cityOffset = offset + 8;
location.City = ReadString(cityOffset);
}
//第零种情况
else
{
Int32 endOffset;
location.Region = ReadString(offset + 4, out endOffset);
location.City = ReadString(endOffset);
}
return location;
}
/// <summary>
/// 从指定位置读取一个字符串
/// </summary>
/// <param name="offset">字符串首的绝对偏移地址</param>
/// <returns>读取出的字符串</returns>
private string ReadString(Int32 offset)
{
m_fs.Seek(offset, SeekOrigin.Begin);
byte[] regionBuf = new byte[1024];
byte bt = (byte)(m_fs.ReadByte());
int i = 0;
while (bt != 0)
{
regionBuf[i++] = bt;
bt = (byte)(m_fs.ReadByte());
}
return Encoding.Default.GetString(regionBuf, 0, i);
}
/// <summary>
/// 从指定位置读取一个字符串
/// </summary>
/// <param name="offset">字符串首的绝对偏移地址</param>
/// <param name="endOffset">输出字符串尾部的绝对偏移地址</param>
/// <returns>读取的字符串</returns>
private string ReadString(Int32 offset, out Int32 endOffset)
{
m_fs.Seek(offset, SeekOrigin.Begin);
byte[] regionBuf = new byte[1024];
byte bt = (byte)(m_fs.ReadByte());
int i = 0;
while (bt != 0)
{
regionBuf[i++] = bt;
bt = (byte)(m_fs.ReadByte());
}
endOffset = offset + i;
return Encoding.Default.GetString(regionBuf, 0, i);
}
#endregion
}
}