纯真IP数据库查询,C#.NET实现。

参看了,LumaQQ 作者对数据库文件分析的文章,和实现代码,改写的C#.NET版本,感谢原作者。


/*******************************************
 * 说明:查询纯真IP数据库,数据库来自CZ88.NET(纯真网络),感谢 LumaQQ 作者:http://lumaqq.linuxsir.org/article/qqwry_format_detail.html
 * 作者:孙宇
 * 日期:2011/11/08
/*******************************************/

using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;

namespace CZIPDemo
{
    /// <summary>
    /// 存储地区的结构
    /// </summary>
    public struct stLocation
    {
        /// <summary>
        /// 未使用
        /// </summary>
        public string Ip;

        /// <summary>
        /// 国家名
        /// </summary>
        public string Contry;

        /// <summary>
        /// 城市名
        /// </summary>
        public string City;
    }


    /// <summary>
    /// 纯真IP数据库查询辅助类
    /// </summary>
    public static class QqwryHelper
    {
        #region 成员变量

        private const byte REDIRECT_MODE_1 = 0x01;//名称存储模式一
        private const byte REDIRECT_MODE_2 = 0x02;//名称存储模式二
        private const int IP_RECORD_LENGTH = 7; //每条索引的长度

        private static long beginIndex = 0;//索引开始
        private static long endIndex = 0;//索引结束

        private static stLocation loc = new stLocation() { City = "未知城市", Contry = "未知国家" };

        private static Stream fs;

        #endregion

        #region 私有成员函数

        /// <summary>
        /// 在索引区查找指定IP对应的记录区地址
        /// </summary>
        /// <param name="_ip">字节型IP</param>
        /// <returns></returns>
        private static long SearchIpIndex(byte[] _ip)
        {
            long index = 0;

            byte[] nextIp = new byte[4];

            ReadIp(beginIndex, ref nextIp);

            int flag = CompareIp(_ip, nextIp);
            if (flag == 0) return beginIndex;
            else if (flag < 0) return -1;

            for (long i = beginIndex, j = endIndex; i < j; )
            {
                index = GetMiddleOffset(i, j);

                ReadIp(index, ref nextIp);
                flag = CompareIp(_ip, nextIp);

                if (flag == 0) return ReadLong(index + 4, 3);
                else if (flag > 0) i = index;
                else if (flag < 0)
                {
                    if (index == j)
                    {
                        j -= IP_RECORD_LENGTH;
                        index = j;
                    }
                    else
                    {
                        j = index;
                    }
                }
            }

            index = ReadLong(index + 4, 3);
            ReadIp(index, ref nextIp);

            flag = CompareIp(_ip, nextIp);
            if (flag <= 0) return index;
            else return -1;
        }

        /// <summary>
        /// 获取两个索引的中间位置
        /// </summary>
        /// <param name="begin">索引1</param>
        /// <param name="end">索引2</param>
        /// <returns></returns>
        private static long GetMiddleOffset(long begin, long end)
        {
            long records = (end - begin) / IP_RECORD_LENGTH;
            records >>= 1;
            if (records == 0) records = 1;
            return begin + records * IP_RECORD_LENGTH;
        }
        
        /// <summary>
        /// 读取记录区的地区名称
        /// </summary>
        /// <param name="offset">位置</param>
        /// <returns></returns>
        private static string ReadString(long offset)
        {
            fs.Position = offset;

            byte b = (byte)fs.ReadByte();
            if (b == REDIRECT_MODE_1 || b == REDIRECT_MODE_2)
            {
                long areaOffset = ReadLong(offset + 1, 3);
                if (areaOffset == 0)
                    return "未知地区";

                else fs.Position = areaOffset;
            }
            else
            {
                fs.Position = offset;
            }

            List<byte> buf = new List<byte>();

            int i = 0;
            for (i = 0, buf.Add((byte)fs.ReadByte()); buf[i] != (byte)(0); ++i, buf.Add((byte)fs.ReadByte())) ;

            if (i > 0) return Encoding.Default.GetString(buf.ToArray(), 0, i);
            else return "";
        }

        /// <summary>
        /// 从自定位置读取指定长度的字节,并转换为big-endian字节序(数据源文件为little-endian字节序)
        /// </summary>
        /// <param name="offset">开始读取位置</param>
        /// <param name="length">读取长度</param>
        /// <returns></returns>
        private static long ReadLong(long offset, int length)
        {
            long ret = 0;
            fs.Position = offset;
            for (int i = 0; i < length; i++)
            {
                ret |= ((fs.ReadByte() << (i * 8)) & (0xFF * ((int)Math.Pow(16, i * 2))));
            }

            return ret;
        }

        /// <summary>
        /// 从指定位置处读取一个IP
        /// </summary>
        /// <param name="offset">指定的位置</param>
        /// <param name="_buffIp">保存IP的缓存区</param>
        private static void ReadIp(long offset, ref byte[] _buffIp)
        {
            fs.Position = offset;
            fs.Read(_buffIp, 0, _buffIp.Length);

            for (int i = 0; i < _buffIp.Length / 2; i++)
            {
                byte temp = _buffIp[i];
                _buffIp[i] = _buffIp[_buffIp.Length - i - 1];
                _buffIp[_buffIp.Length - i - 1] = temp;
            }
        }

        /// <summary>
        /// 比较两个IP是否相等,1:IP1大于IP2,-1:IP1小于IP2,0:IP1=IP2
        /// </summary>
        /// <param name="_buffIp1">IP1</param>
        /// <param name="_buffIp2">IP2</param>
        /// <returns></returns>
        private static int CompareIp(byte[] _buffIp1, byte[] _buffIp2)
        {
            if (_buffIp1.Length > 4 || _buffIp2.Length > 4) throw new Exception("指定的IP无效。");

            for (int i = 0; i < 4; i++)
            {
                if ((_buffIp1[i] & 0xFF) > (_buffIp2[i] & 0xFF)) return 1;
                else if ((_buffIp1[i] & 0xFF) < (_buffIp2[i] & 0xFF)) return -1;
            }

            return 0;
        }

        /// <summary>
        /// 从指定的地址获取区域名称
        /// </summary>
        /// <param name="offset"></param>
        private static void GetAreaName(long offset)
        {
            fs.Position = offset + 4;
            long flag = fs.ReadByte();
            long contryIndex = 0;
            if (flag == REDIRECT_MODE_1)
            {
                contryIndex = ReadLong(fs.Position, 3);
                fs.Position = contryIndex;

                flag = fs.ReadByte();

                if (flag == REDIRECT_MODE_2)    //是否仍然为重定向
                {
                    loc.Contry = ReadString(ReadLong(fs.Position, 3));
                    fs.Position = contryIndex + 4;
                }
                else
                {
                    loc.Contry = ReadString(contryIndex);
                }
                loc.City = ReadString(fs.Position);
            }
            else if (flag == REDIRECT_MODE_2)
            {
                contryIndex = ReadLong(fs.Position, 3);
                loc.Contry = ReadString(contryIndex);
                loc.City = ReadString(contryIndex + 3);
            }
            else
            {
                loc.Contry = ReadString(offset + 4);
                loc.City = ReadString(fs.Position);
            }
        }

        #endregion

        #region 公有成员函数

        /// <summary>
        /// 加载数据库文件到缓存
        /// </summary>
        /// <param name="path">数据库文件地址</param>
        /// <returns></returns>
        public static void Init(string path)
        {
            fs = new FileStream(path, FileMode.Open);
        }

        /// <summary>
        /// 根据IP获取区域名
        /// </summary>
        /// <param name="ip">指定的IP</param>
        /// <returns></returns>
        public static stLocation GetLocation(string ip)
        {
            IPAddress ipAddress = null;
            if (!IPAddress.TryParse(ip, out ipAddress)) throw new Exception("无效的IP地址。");

            byte[] buff_local_ip = ipAddress.GetAddressBytes();

            beginIndex = ReadLong(0, 4);
            endIndex = ReadLong(4, 4);

            long offset = SearchIpIndex(buff_local_ip);
            if (offset != -1)
            {
                GetAreaName(offset);
            }

            loc.Contry = loc.Contry.Trim();
            loc.City = loc.City.Trim().Replace("CZ88.NET", "");

            return loc;
        }

        /// <summary>
        /// 释放资源
        /// </summary>
        public static void Dispose()
        {
            fs.Dispose();
        }

        #endregion
    }
}


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
本模块代码是针对在 2011 年在 CSDN 论坛个发布的“最新 NET 读取纯真IP数据库代码(C#)”源码,做了一次升级,这次升级不是简单的修补,是本人精心的重写,现在只需要 5 分哦,您值得拥有!该源代码不同于网上的代码,网上代码基本可分为两大类,第一类直接使用文件流,通过移动文件流指针(即更改 Stream.Position 属性值)搜索 IP 地址对应的信息,此类代码问题是:其一移动文件指针效率是比较低的(给 Position 赋值),多线程并发时,会重复打开多个文件效率更加底下;第二类是把文件直接加载内存中,通过这种缓冲,速度是提升了,但并没有为多线程环境优化,多线程并发时(如:Web 中每位访客,都是一根线程),意味会重复的读取文件,重复的创建缓存,浪费内存空间。 该源代码特点是考虑到了多线程应用环境(如:Web 每个会话,都是一根线程),设计了缓存对象 QQWryCache 用于管理缓存,用 QQCacheStream 流读取缓存数据。在多线程应用环境中,假设 10 根线程访问同一个纯真 IP 数据库时,只会开辟 1 份缓存,给多根线程共享,避免了不必要的内存浪费。 注1:本模块代码,保证所有静态方法都是线程安全的,但不保证所有实例方法都是线程安全的。 注2:每根线程访问缓存时,请通过 QQWryCache.GetCache 静态方法获取缓存对象。 注3:多根线程获取到的缓存对象,通常都是同一对象,该对象已经考虑了线程同步,不必担心线程安全问题。 /* >>> 使用完全缓存(缓存整个文件,约 8.8MB),调用方法如下: */ QQWryCache cache = QQWryCache.GetCache("qqwry.dat", true); Stream stream = cache.GetCacheStream(); QQWrySearcher searcher = new QQwryScanner(stream); QQWryLocation location = searcher.Query("IP 地址"); Console.WritleLine("Country = {0}, Location = {1}", location.Country, location.Location); /* 完全缓冲, * 缓存一旦初始化完毕,就会自动关闭文件, * 所以不再依赖于文件,因此可以不用关闭缓冲流, * 下面调用 Close 方法,其实没有实际意义,但也不会引发异常。 */ stream.Close(); /* >>> 使用索引缓存(仅缓存索引部分,约 3MB),调用方法如下: <<>> 直接使用文件流(不使用缓存),调用方法如下: <<>> 遍历 IP 数据库。 <<< */ QQWryCache cache = QQWryCache.GetCache("qqwry.dat", true); Stream stream = cache.GetCacheStream(); QQWrySearcher searcher = new QQWrySearcher(stream); // 用 for 循环遍历 for(int i = 0; i < searcher.Count; i++) { QQWryIpLocation item = searcher[i]; Console.WritleLine("Country = {0}, Location = {1}", location.Country, location.Location); } // 用 foreach 循环遍历 foreach(QQWryIpLocation item in searcher) { QQWryIpLocation item = searcher[i]; Console.WritleLine("Country = {0}, Location = {1}", location.Country, location.Location); }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值