123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127 |
- 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.NetworkInformation;
- 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 brokeElapsedExpiration = TimeSpan.FromMinutes(1d);
- private readonly TimeSpan normaleElapsedExpiration = 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.normaleElapsedExpiration);
- }
- catch (Exception)
- {
- cancellationToken.ThrowIfCancellationRequested();
- addressElapsed = new AddressElapsed(endPoint.Address, TimeSpan.MaxValue);
- var expiration = NetworkInterface.GetIsNetworkAvailable() ? this.normaleElapsedExpiration : this.brokeElapsedExpiration;
- return this.addressElapsedCache.Set(endPoint, addressElapsed, expiration);
- }
- finally
- {
- stopWatch.Stop();
- }
- }
- }
- }
|