using Microsoft.Extensions.Caching.Memory;using Microsoft.Extensions.Options;using System;using System.Collections.Generic;using System.Diagnostics;using System.Linq;using System.Net;using System.Net.Sockets;using System.Threading;using System.Threading.Tasks;namespace FastGithub.DomainResolve{/// <summary>/// IP服务/// 域名IP关系缓存10分钟/// IPEndPoint时延缓存5分钟/// IPEndPoint连接超时5秒/// </summary>sealed class IPAddressService{private record DomainAddress(string Domain, IPAddress Address);private readonly TimeSpan domainAddressExpiration = TimeSpan.FromMinutes(10d);private readonly IMemoryCache domainAddressCache = new MemoryCache(Options.Create(new MemoryCacheOptions()));private record AddressElapsed(IPAddress Address, TimeSpan Elapsed);private readonly TimeSpan problemElapsedExpiration = TimeSpan.FromMinutes(1d);private readonly TimeSpan normalElapsedExpiration = TimeSpan.FromMinutes(5d);private readonly TimeSpan connectTimeout = TimeSpan.FromSeconds(5d);private readonly IMemoryCache addressElapsedCache = new MemoryCache(Options.Create(new MemoryCacheOptions()));private readonly DnsClient dnsClient;/// <summary>/// IP服务/// </summary>/// <param name="dnsClient"></param>public IPAddressService(DnsClient dnsClient){this.dnsClient = dnsClient;}/// <summary>/// 并行获取可连接的IP/// </summary>/// <param name="dnsEndPoint"></param>/// <param name="oldAddresses"></param>/// <param name="cancellationToken"></param>/// <returns></returns>public async Task<IPAddress[]> GetAddressesAsync(DnsEndPoint dnsEndPoint, IEnumerable<IPAddress> oldAddresses, CancellationToken cancellationToken){var ipEndPoints = new HashSet<IPEndPoint>();// 历史未过期的IP节点foreach (var address in oldAddresses){var domainAddress = new DomainAddress(dnsEndPoint.Host, address);if (this.domainAddressCache.TryGetValue(domainAddress, out _)){ipEndPoints.Add(new IPEndPoint(address, dnsEndPoint.Port));}}// 新解析出的IP节点await foreach (var address in this.dnsClient.ResolveAsync(dnsEndPoint, fastSort: false, cancellationToken)){ipEndPoints.Add(new IPEndPoint(address, dnsEndPoint.Port));var domainAddress = new DomainAddress(dnsEndPoint.Host, address);this.domainAddressCache.Set(domainAddress, default(object), this.domainAddressExpiration);}if (ipEndPoints.Count == 0){return Array.Empty<IPAddress>();}var addressElapsedTasks = ipEndPoints.Select(item => this.GetAddressElapsedAsync(item, cancellationToken));var addressElapseds = await Task.WhenAll(addressElapsedTasks);return addressElapseds.Where(item => item.Elapsed < TimeSpan.MaxValue).OrderBy(item => item.Elapsed).Select(item => item.Address).ToArray();}/// <summary>/// 获取IP节点的时延/// </summary>/// <param name="endPoint"></param>/// <param name="cancellationToken"></param>/// <returns></returns>private async Task<AddressElapsed> GetAddressElapsedAsync(IPEndPoint endPoint, CancellationToken cancellationToken){if (this.addressElapsedCache.TryGetValue<AddressElapsed>(endPoint, out var addressElapsed)){return addressElapsed;}var stopWatch = Stopwatch.StartNew();try{using var timeoutTokenSource = new CancellationTokenSource(this.connectTimeout);using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutTokenSource.Token);using var socket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);await socket.ConnectAsync(endPoint, linkedTokenSource.Token);addressElapsed = new AddressElapsed(endPoint.Address, stopWatch.Elapsed);return this.addressElapsedCache.Set(endPoint, addressElapsed, this.normalElapsedExpiration);}catch (Exception ex){cancellationToken.ThrowIfCancellationRequested();addressElapsed = new AddressElapsed(endPoint.Address, TimeSpan.MaxValue);var expiration = IsLocalNetworkProblem(ex) ? this.problemElapsedExpiration : this.normalElapsedExpiration;return this.addressElapsedCache.Set(endPoint, addressElapsed, expiration);}finally{stopWatch.Stop();}}/// <summary>/// 是否为本机网络问题/// </summary>/// <param name="ex"></param>/// <returns></returns>private static bool IsLocalNetworkProblem(Exception ex){if (ex is not SocketException socketException){return false;}var code = socketException.SocketErrorCode;return code == SocketError.NetworkDown || code == SocketError.NetworkUnreachable;}}}
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。