高并发下查询纯真IP库的ip数据
刚开始参考了 HeDaode 写的读取ip库的方法,由于他的方法是通过文件流的方式来读取库,这种方式在稍微大点的并发下有些问题:
每个线程都读取一次文件会造成内存浪费,如果只读取一次会出现文件流指针未复位就被其他线程挪移出现索引错误
于是自己修改了一下使用单例模式,并且只读一次库文件,在查询过程中均使用相关对字节复制,而不是使用指针的方式来避免“高并发”下的指针和内存问题
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; namespace PureIP { ///<summary> /// 提供从纯真IP数据库搜索IP信息的方法; /// 感谢LumaQQ提供纯真IP数据库格式文档; /// ----HeDaode 2007-12-28 四川教育学院 ///</summary> public class IPSearch { static byte[] ipstream = null; static IPSearch IPsearch = null; string ipfilePath; static object o = new object(); long startPosition = 0; //文件头实际上是两个指针,分别指向了第一条索引和最后一条索引的绝对偏移 long endPosition = 0; //文件最后一条索引的位置 long count = 0; //总记录数 long[] ipArray = null; //将索引区域的ip转为long型数组存储 ///<summary> /// 构造函数 ///</summary> ///<param name="ipfilePath">纯真IP数据库路径</param> private IPSearch(string ipfilePath) { this.ipfilePath = ipfilePath; ipstream = File.ReadAllBytes(ipfilePath); if (ipstream == null) throw new Exception("ip库文件不存在"); ipArray = BlockToArray(ReadIPBlock()); //将索引区域的ip转为long型数组存储,方便后续根据ip查找其索引,使用完毕后指针会被放置到第一条索引的位置 } public static IPSearch Init(string ipfilePath) { if (IPsearch == null) { lock (o) { if (IPsearch == null) { IPsearch = new IPSearch(ipfilePath); } } } return IPsearch; } ///<summary> /// 地理位置,包括国家和地区 ///</summary> public struct IPLocation { public string Ip { get; set; } /// <summary> /// 国家 /// </summary> public string Country { get; set; } /// <summary> /// 地区(运营商) /// </summary> public string Area { get; set; } /// <summary> /// 省份 /// </summary> public string Province { get { string[] s = Country.Split('省'); if (s != null && s.Length > 1) { return s[0]+"省"; } else { if (Country.StartsWith("北京市")) { return "北京市"; } else if (Country.StartsWith("上海市")) { return "上海市"; } else if (Country.StartsWith("天津市")) { return "天津市"; } else if (Country.StartsWith("重庆市")) { return "重庆市"; } //判断含有内蒙古 else if (Country.StartsWith("内蒙")) { return "内蒙古自治区"; } else if (Country.StartsWith("新疆")) { return "新疆"; } else if (Country.StartsWith("西藏")) { return "西藏自治区"; } else if (Country.StartsWith("宁夏")) { return "宁夏回族自治区"; } else if (Country.StartsWith("广西")) { return "广西壮族自治区"; } else if (Country.StartsWith("香港")) { return "香港特别行政区"; } else if (Country.StartsWith("澳门")) { return "澳门特别行政区"; } } return Country; } } /// <summary> /// 城市 /// </summary> public string City { get { string[] s = Country.Split('省'); if (s != null && s.Length > 1) { return s[1]; } else { return Country.Replace("行政区", "").Replace("自治区", ""); } } } /// <summary> /// 行政区编码(按县,市,省依次获取,获取即止) /// </summary> public string Code { get; set; } } ///<summary> /// 获取指定IP所在地理位置 ///</summary> ///<param name="strIP">要查询的IP地址</param> ///<returns></returns> public IPLocation GetIPLocation(string strIP) { long ip = IPToLong(strIP); //ip转为long类型 long beginPointer = startPosition; //指向第一条索引的位置 long offset = SearchIP(ipArray, 0, ipArray.Length - 1,ip) * 7 + 4; //根据ip得到索引,计算出其指针位置,二分法,在索引区每个ip记录占7个字节,ip本身占4个字节,这里得到ip对应的记录指针地址 //ipFile.Position += offset;//跳过起始IP long startIndex= ReadLongX(beginPointer+offset,3) + 4;//跳过结束IP,读取国家偏移位置,并放置指针位置到 ?? IPLocation loc = new IPLocation(); loc.Ip = strIP; int flag = ipstream[startIndex];//读取标志 startIndex++; if (flag == 1)//表示国家和地区被转向 { startIndex = ReadLongX(startIndex,3); flag = ipstream[startIndex];//再读标志 startIndex++; } long countryOffset = startIndex; loc.Country = ReadString(countryOffset,flag).Trim(); //国家 if (flag == 2) { startIndex = countryOffset + 3; } flag = ipstream[startIndex]; startIndex++; loc.Area = ReadString(startIndex,flag).Trim(); //地区 //ipFile.Close(); //ipFile = null; //ipFile.Position = beginPointer; //还原文件流指针位置 return loc; } ///<summary> /// 将字符串形式的IP转换位long ///</summary> ///<param name="strIP"></param> ///<returns></returns> public long IPToLong(string strIP) { byte[] ip_bytes = new byte[8]; string[] strArr = strIP.Split('.'); for (int i = 0; i < 4; i++) { ip_bytes[i] = byte.Parse(strArr[3 - i]); } return BitConverter.ToInt64(ip_bytes, 0); } ///<summary> /// 将索引区字节块中的起始IP转换成Long数组 ///</summary> ///<param name="ipBlock"></param> long[] BlockToArray(byte[] ipBlock) { long[] ipArray = new long[ipBlock.Length / 7]; //ip记录个数 int ipIndex = 0; byte[] temp = new byte[8]; //每个ip长度 for (int i = 0; i < ipBlock.Length; i += 7) { Array.Copy(ipBlock, i, temp, 0, 4); ipArray[ipIndex] = BitConverter.ToInt64(temp, 0); ipIndex++; } return ipArray; } ///<summary> /// 从IP数组中搜索指定IP并返回其索引 ///</summary> ///<param name="ipArray">IP数组</param> ///<param name="start">指定搜索的起始位置</param> ///<param name="end">指定搜索的结束位置</param> ///<returns></returns> int SearchIP(long[] ipArray, int start, int end ,long ip) //(二分法) { int middle = (start + end) / 2; if (middle == start) return middle; else if (ip < ipArray[middle]) return SearchIP(ipArray, start, middle, ip); else return SearchIP(ipArray, middle, end, ip); } ///<summary> /// 读取IP文件中索引区块 ///</summary> ///<returns>索引区块</returns> byte[] ReadIPBlock() { startPosition = ReadLongX(0,4); //文件头实际上是两个指针,分别指向了第一条索引和最后一条索引的绝对偏移 endPosition = ReadLongX(4,4); count = (endPosition - startPosition) / 7 + 1;//总记录数,每条索引长度为7个字节,前4个字节是起始IP地址,后三个字节就指向了IP记录 //ipFile.Position = startPosition; //指针定位到第一条索引的偏移位置 byte[] ipBlock = new byte[count * 7]; //ip索引区域所占的自己块 Array.Copy(ipstream, startPosition, ipBlock, 0, ipBlock.Length); //ipstream.CopyTo(ipBlock,0,ipBlock.Length); //索引区域的记录值 //ipFile.Position = startPosition; //重置指针索引 return ipBlock; } ///<summary> /// 从IP文件中读取指定字节并转换位long ///</summary> ///<param name="index">读取索引位置</param> ///<param name="bytesCount">需要转换的字节数,主意不要超过8字节</param> /// <returns></returns> long ReadLongX(long index, int bytesCount) { byte[] _bytes = new byte[8]; //ipFile.Read(_bytes, 0, bytesCount); Array.Copy(ipstream, index, _bytes, 0, bytesCount); return BitConverter.ToInt64(_bytes, 0); } /// <summary> /// 从IP文件中读取字符串 /// </summary> /// <param name="index">读取开始位置</param> /// <param name="flag">反向标志</param> /// <returns></returns> string ReadString(long index ,int flag) { long statrIndex = 0; if (flag == 1 || flag == 2)//转向标志 statrIndex = ReadLongX(index,3); else statrIndex = index-1; List<byte> list = new List<byte>(); byte b = (byte)ipstream[statrIndex]; statrIndex++; while (b > 0) { list.Add(b); b = (byte)ipstream[statrIndex]; statrIndex++; } return Encoding.Default.GetString(list.ToArray()); } } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构