412 lines
13 KiB
C#
412 lines
13 KiB
C#
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大于ip2;2代表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
|
||
}
|
||
}
|