IPAddressStatusService.cs 3.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. using Microsoft.Extensions.Caching.Memory;
  2. using Microsoft.Extensions.Options;
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Diagnostics;
  6. using System.Linq;
  7. using System.Net;
  8. using System.Net.NetworkInformation;
  9. using System.Net.Sockets;
  10. using System.Threading;
  11. using System.Threading.Tasks;
  12. namespace FastGithub.DomainResolve
  13. {
  14. /// <summary>
  15. /// IP状态服务
  16. /// 状态缓存5分钟
  17. /// 连接超时5秒
  18. /// </summary>
  19. sealed class IPAddressStatusService
  20. {
  21. private readonly TimeSpan brokeExpiration = TimeSpan.FromMinutes(1d);
  22. private readonly TimeSpan normalExpiration = TimeSpan.FromMinutes(5d);
  23. private readonly TimeSpan connectTimeout = TimeSpan.FromSeconds(5d);
  24. private readonly IMemoryCache statusCache = new MemoryCache(Options.Create(new MemoryCacheOptions()));
  25. /// <summary>
  26. /// 并行获取可连接的IP
  27. /// </summary>
  28. /// <param name="addresses"></param>
  29. /// <param name="port"></param>
  30. /// <param name="cancellationToken"></param>
  31. /// <returns></returns>
  32. public async Task<IPAddress[]> GetAvailableAddressesAsync(IEnumerable<IPAddress> addresses, int port, CancellationToken cancellationToken)
  33. {
  34. if (addresses.Any() == false)
  35. {
  36. return Array.Empty<IPAddress>();
  37. }
  38. var statusTasks = addresses.Select(item => this.GetStatusAsync(item, port, cancellationToken));
  39. var statusArray = await Task.WhenAll(statusTasks);
  40. return statusArray
  41. .Where(item => item.Elapsed < TimeSpan.MaxValue)
  42. .OrderBy(item => item.Elapsed)
  43. .Select(item => item.Address)
  44. .ToArray();
  45. }
  46. /// <summary>
  47. /// 获取IP状态
  48. /// </summary>
  49. /// <param name="address"></param>
  50. /// <param name="port"></param>
  51. /// <param name="cancellationToken"></param>
  52. /// <returns></returns>
  53. private async Task<IPAddressStatus> GetStatusAsync(IPAddress address, int port, CancellationToken cancellationToken)
  54. {
  55. var endPoint = new IPEndPoint(address, port);
  56. if (this.statusCache.TryGetValue<IPAddressStatus>(endPoint, out var status))
  57. {
  58. return status;
  59. }
  60. var stopWatch = Stopwatch.StartNew();
  61. try
  62. {
  63. using var timeoutTokenSource = new CancellationTokenSource(this.connectTimeout);
  64. using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutTokenSource.Token);
  65. using var socket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
  66. await socket.ConnectAsync(endPoint, linkedTokenSource.Token);
  67. status = new IPAddressStatus(endPoint.Address, stopWatch.Elapsed);
  68. return this.statusCache.Set(endPoint, status, this.normalExpiration);
  69. }
  70. catch (Exception)
  71. {
  72. cancellationToken.ThrowIfCancellationRequested();
  73. status = new IPAddressStatus(endPoint.Address, TimeSpan.MaxValue);
  74. var expiration = NetworkInterface.GetIsNetworkAvailable() ? this.normalExpiration : this.brokeExpiration;
  75. return this.statusCache.Set(endPoint, status, expiration);
  76. }
  77. finally
  78. {
  79. stopWatch.Stop();
  80. }
  81. }
  82. }
  83. }