QQWrySearcher.cs 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. using Masuit.Tools;
  2. using System.Net;
  3. using System.Text;
  4. namespace Masuit.MyBlogs.Core.Common;
  5. /// <summary>
  6. /// QQWryIpSearch 请作为单例使用 数据库缓存在内存
  7. /// </summary>
  8. public sealed class QQWrySearcher : IDisposable
  9. {
  10. private readonly SemaphoreSlim _initLock = new SemaphoreSlim(initialCount: 1, maxCount: 1);
  11. private object _versionLock = new object();
  12. private static readonly Encoding Gb2312Encoding;
  13. /// <summary>
  14. /// 数据库 缓存
  15. /// </summary>
  16. private byte[] _qqwryDbBytes;
  17. /// <summary>
  18. /// Ip索引 缓存
  19. /// </summary>
  20. private long[] _ipIndexCache;
  21. /// <summary>
  22. /// 起始定位
  23. /// </summary>
  24. private long _startPosition;
  25. /// <summary>
  26. /// 是否初始化
  27. /// </summary>
  28. private bool? _init;
  29. private readonly string _dbPath;
  30. private int? _ipCount;
  31. private string _version;
  32. /// <summary>
  33. /// 记录总数
  34. /// </summary>
  35. public int IpCount
  36. {
  37. get
  38. {
  39. _ipCount ??= _ipIndexCache.Length;
  40. return _ipCount.Value;
  41. }
  42. }
  43. /// <summary>
  44. /// 版本信息
  45. /// </summary>
  46. public string Version
  47. {
  48. get
  49. {
  50. if (!string.IsNullOrWhiteSpace(_version))
  51. {
  52. return _version;
  53. }
  54. lock (_versionLock)
  55. {
  56. if (!string.IsNullOrWhiteSpace(_version))
  57. {
  58. return _version;
  59. }
  60. _version = GetIpLocation(IPAddress.Parse("255.255.255.255")).Network;
  61. return _version;
  62. }
  63. }
  64. }
  65. static QQWrySearcher()
  66. {
  67. Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
  68. Gb2312Encoding = Encoding.GetEncoding("gb2312");
  69. }
  70. public QQWrySearcher(string dbPath)
  71. {
  72. _dbPath = dbPath;
  73. Init();
  74. }
  75. /// <summary>
  76. /// 初始化
  77. /// </summary>
  78. /// <returns></returns>
  79. public bool Init(bool getNewDb = false)
  80. {
  81. if (_init != null && !getNewDb)
  82. {
  83. return _init.Value;
  84. }
  85. _initLock.Wait();
  86. try
  87. {
  88. if (_init != null && !getNewDb)
  89. {
  90. return _init.Value;
  91. }
  92. _qqwryDbBytes = File.ReadAllBytes(_dbPath);
  93. _ipIndexCache = BlockToArray(ReadIpBlock(_qqwryDbBytes, out _startPosition));
  94. _ipCount = null;
  95. _version = null;
  96. _init = true;
  97. }
  98. finally
  99. {
  100. _initLock.Release();
  101. }
  102. if (_qqwryDbBytes == null)
  103. {
  104. throw new InvalidOperationException("无法打开IP数据库" + _dbPath + "!");
  105. }
  106. return true;
  107. }
  108. /// <summary>
  109. /// 获取指定IP所在地理位置
  110. /// </summary>
  111. /// <param name="ip">要查询的IP地址</param>
  112. /// <returns></returns>
  113. public (string City, string Network) GetIpLocation(IPAddress ip)
  114. {
  115. if (ip.IsPrivateIP())
  116. {
  117. return ("内网", "内网");
  118. }
  119. var ipnum = IpToLong(ip);
  120. return ReadLocation(ipnum, _startPosition, _ipIndexCache, _qqwryDbBytes);
  121. }
  122. /// <inheritdoc />
  123. /// <summary>
  124. /// 释放
  125. /// </summary>
  126. public void Dispose()
  127. {
  128. _initLock?.Dispose();
  129. _versionLock = null;
  130. _qqwryDbBytes = null;
  131. _qqwryDbBytes = null;
  132. _ipIndexCache = null;
  133. _init = null;
  134. }
  135. ///<summary>
  136. /// 将字符串形式的IP转换位long
  137. ///</summary>
  138. ///<param name="ip"></param>
  139. ///<returns></returns>
  140. private static long IpToLong(IPAddress ip)
  141. {
  142. var bytes = ip.GetAddressBytes();
  143. var ipBytes = new byte[8];
  144. for (var i = 0; i < 4; i++)
  145. {
  146. ipBytes[i] = bytes[3 - i];
  147. }
  148. return BitConverter.ToInt64(ipBytes);
  149. }
  150. ///<summary>
  151. /// 将索引区字节块中的起始IP转换成Long数组
  152. ///</summary>
  153. ///<param name="ipBlock"></param>
  154. private static long[] BlockToArray(byte[] ipBlock)
  155. {
  156. var ipArray = new long[ipBlock.Length / 7];
  157. var ipIndex = 0;
  158. var temp = new byte[8];
  159. for (var i = 0; i < ipBlock.Length; i += 7)
  160. {
  161. Array.Copy(ipBlock, i, temp, 0, 4);
  162. ipArray[ipIndex] = BitConverter.ToInt64(temp, 0);
  163. ipIndex++;
  164. }
  165. return ipArray;
  166. }
  167. /// <summary>
  168. /// 从IP数组中搜索指定IP并返回其索引
  169. /// </summary>
  170. /// <param name="ip"></param>
  171. /// <param name="ipArray">IP数组</param>
  172. /// <param name="start">指定搜索的起始位置</param>
  173. /// <param name="end">指定搜索的结束位置</param>
  174. /// <returns></returns>
  175. private static int SearchIp(long ip, long[] ipArray, int start, int end)
  176. {
  177. while (true)
  178. {
  179. //计算中间索引
  180. var middle = (start + end) / 2;
  181. if (middle == start)
  182. {
  183. return middle;
  184. }
  185. if (ip < ipArray[middle])
  186. {
  187. end = middle;
  188. }
  189. else
  190. {
  191. start = middle;
  192. }
  193. }
  194. }
  195. ///<summary>
  196. /// 读取IP文件中索引区块
  197. ///</summary>
  198. ///<returns></returns>
  199. private static byte[] ReadIpBlock(byte[] bytes, out long startPosition)
  200. {
  201. long offset = 0;
  202. startPosition = ReadLongX(bytes, offset, 4);
  203. offset += 4;
  204. var endPosition = ReadLongX(bytes, offset, 4);
  205. offset = startPosition;
  206. var count = (endPosition - startPosition) / 7 + 1;//总记录数
  207. var ipBlock = new byte[count * 7];
  208. for (var i = 0; i < ipBlock.Length; i++)
  209. {
  210. ipBlock[i] = bytes[offset + i];
  211. }
  212. return ipBlock;
  213. }
  214. /// <summary>
  215. /// 从IP文件中读取指定字节并转换位long
  216. /// </summary>
  217. /// <param name="bytes"></param>
  218. /// <param name="offset"></param>
  219. /// <param name="bytesCount">需要转换的字节数,主意不要超过8字节</param>
  220. /// <returns></returns>
  221. private static long ReadLongX(byte[] bytes, long offset, int bytesCount)
  222. {
  223. var cBytes = new byte[8];
  224. for (var i = 0; i < bytesCount; i++)
  225. {
  226. cBytes[i] = bytes[offset + i];
  227. }
  228. return BitConverter.ToInt64(cBytes, 0);
  229. }
  230. /// <summary>
  231. /// 从IP文件中读取字符串
  232. /// </summary>
  233. /// <param name="bytes"></param>
  234. /// <param name="flag">转向标志</param>
  235. /// <param name="offset"></param>
  236. /// <returns></returns>
  237. private static string ReadString(byte[] bytes, int flag, ref long offset)
  238. {
  239. if (flag == 1 || flag == 2)//转向标志
  240. {
  241. offset = ReadLongX(bytes, offset, 3);
  242. }
  243. else
  244. {
  245. offset -= 1;
  246. }
  247. var list = new List<byte>();
  248. var b = bytes[offset];
  249. offset += 1;
  250. while (b > 0)
  251. {
  252. list.Add(b);
  253. b = bytes[offset];
  254. offset += 1;
  255. }
  256. return Gb2312Encoding.GetString(list.ToArray());
  257. }
  258. private static (string City, string Network) ReadLocation(long ip, long startPosition, long[] ipIndex, byte[] qqwryDbBytes)
  259. {
  260. long offset = SearchIp(ip, ipIndex, 0, ipIndex.Length) * 7 + 4;
  261. //偏移
  262. var arrayOffset = startPosition + offset;
  263. //跳过结束IP
  264. arrayOffset = ReadLongX(qqwryDbBytes, arrayOffset, 3) + 4;
  265. //读取标志
  266. var flag = qqwryDbBytes[arrayOffset];
  267. arrayOffset += 1;
  268. //表示国家和地区被转向
  269. if (flag == 1)
  270. {
  271. arrayOffset = ReadLongX(qqwryDbBytes, arrayOffset, 3);
  272. //再读标志
  273. flag = qqwryDbBytes[arrayOffset];
  274. arrayOffset += 1;
  275. }
  276. var countryOffset = arrayOffset;
  277. var city = ReadString(qqwryDbBytes, flag, ref arrayOffset);
  278. if (flag == 2)
  279. {
  280. arrayOffset = countryOffset + 3;
  281. }
  282. flag = qqwryDbBytes[arrayOffset];
  283. arrayOffset += 1;
  284. var network = ReadString(qqwryDbBytes, flag, ref arrayOffset);
  285. return (city, network);
  286. }
  287. }