Browse Source

实现域名下的ip测速功能

陈国伟 3 years ago
parent
commit
f29dca6b43

+ 0 - 4
FastGithub.DomainResolve/DnsClient.cs

@@ -81,7 +81,6 @@ namespace FastGithub.DomainResolve
             if (cryptDns != null)
             if (cryptDns != null)
             {
             {
                 yield return cryptDns;
                 yield return cryptDns;
-                yield return cryptDns;
             }
             }
 
 
             foreach (var fallbackDns in this.fastGithubConfig.FallbackDns)
             foreach (var fallbackDns in this.fastGithubConfig.FallbackDns)
@@ -109,9 +108,6 @@ namespace FastGithub.DomainResolve
                 {
                 {
                     value = await this.LookupCoreAsync(dns, domain, cancellationToken);
                     value = await this.LookupCoreAsync(dns, domain, cancellationToken);
                     this.dnsCache.Set(key, value, this.dnsExpiration);
                     this.dnsCache.Set(key, value, this.dnsExpiration);
-
-                    var items = string.Join(", ", value.Select(item => item.ToString()));
-                    this.logger.LogInformation($"dns://{dns}:{domain}->[{items}]");
                 }
                 }
                 return value;
                 return value;
             }
             }

+ 25 - 4
FastGithub.DomainResolve/DomainResolver.cs

@@ -1,6 +1,7 @@
 using FastGithub.Configuration;
 using FastGithub.Configuration;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Net;
 using System.Net;
+using System.Runtime.CompilerServices;
 using System.Threading;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 
 
@@ -12,14 +13,19 @@ namespace FastGithub.DomainResolve
     sealed class DomainResolver : IDomainResolver
     sealed class DomainResolver : IDomainResolver
     {
     {
         private readonly DnsClient dnsClient;
         private readonly DnsClient dnsClient;
+        private readonly DomainSpeedTestService speedTestService;
 
 
         /// <summary>
         /// <summary>
         /// 域名解析器
         /// 域名解析器
-        /// </summary> 
+        /// </summary>
         /// <param name="dnsClient"></param>
         /// <param name="dnsClient"></param>
-        public DomainResolver(DnsClient dnsClient)
+        /// <param name="speedTestService"></param>
+        public DomainResolver(
+            DnsClient dnsClient,
+            DomainSpeedTestService speedTestService)
         {
         {
             this.dnsClient = dnsClient;
             this.dnsClient = dnsClient;
+            this.speedTestService = speedTestService;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -43,9 +49,24 @@ namespace FastGithub.DomainResolve
         /// <param name="domain">域名</param>
         /// <param name="domain">域名</param>
         /// <param name="cancellationToken"></param>
         /// <param name="cancellationToken"></param>
         /// <returns></returns>
         /// <returns></returns>
-        public IAsyncEnumerable<IPAddress> ResolveAllAsync(string domain, CancellationToken cancellationToken)
+        public async IAsyncEnumerable<IPAddress> ResolveAllAsync(string domain, [EnumeratorCancellation] CancellationToken cancellationToken)
         {
         {
-            return this.dnsClient.ResolveAsync(domain, cancellationToken);
+            var addresses = this.speedTestService.GetIPAddresses(domain);
+            if (addresses.Length > 0)
+            {
+                foreach (var address in addresses)
+                {
+                    yield return address;
+                }
+            }
+            else
+            {
+                this.speedTestService.Add(domain);
+                await foreach (var address in this.dnsClient.ResolveAsync(domain, cancellationToken))
+                {
+                    yield return address;
+                }
+            }
         }
         }
     }
     }
 }
 }

+ 61 - 0
FastGithub.DomainResolve/DomainSpeedTestHostedService.cs

@@ -0,0 +1,61 @@
+using Microsoft.Extensions.Hosting;
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace FastGithub.DomainResolve
+{
+    /// <summary>
+    /// 域名的IP测速后台服务
+    /// </summary>
+    sealed class DomainSpeedTestHostedService : BackgroundService
+    {
+        private readonly DomainSpeedTestService speedTestService;
+        private readonly TimeSpan testDueTime = TimeSpan.FromMinutes(1d);
+
+        /// <summary>
+        /// 域名的IP测速后台服务
+        /// </summary>
+        /// <param name="speedTestService"></param>
+        public DomainSpeedTestHostedService(DomainSpeedTestService speedTestService)
+        {
+            this.speedTestService = speedTestService;
+        }
+
+        /// <summary>
+        /// 启动时
+        /// </summary>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public override async Task StartAsync(CancellationToken cancellationToken)
+        {
+            await this.speedTestService.LoadDataAsync(cancellationToken);
+            await base.StartAsync(cancellationToken);
+        }
+
+        /// <summary>
+        /// 停止时
+        /// </summary>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public override async Task StopAsync(CancellationToken cancellationToken)
+        {
+            await this.speedTestService.SaveDataAsync();
+            await base.StopAsync(cancellationToken);
+        }
+
+        /// <summary>
+        /// 后台测速
+        /// </summary>
+        /// <param name="stoppingToken"></param>
+        /// <returns></returns>
+        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
+        {
+            while (stoppingToken.IsCancellationRequested == false)
+            {
+                await this.speedTestService.TestSpeedAsync(stoppingToken);
+                await Task.Delay(this.testDueTime, stoppingToken);
+            }
+        }
+    }
+}

+ 126 - 0
FastGithub.DomainResolve/DomainSpeedTestService.cs

@@ -0,0 +1,126 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Text.Json;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace FastGithub.DomainResolve
+{
+    /// <summary>
+    /// 域名的IP测速服务
+    /// </summary>
+    sealed class DomainSpeedTestService
+    {
+        private const string DATA_FILE = "domains.json";
+        private readonly DnsClient dnsClient;
+
+        private readonly object syncRoot = new();
+        private readonly Dictionary<string, IPAddressItemHashSet> domainIPAddressHashSet = new();
+
+        /// <summary>
+        /// 域名的IP测速服务
+        /// </summary>
+        /// <param name="dnsClient"></param>
+        public DomainSpeedTestService(DnsClient dnsClient)
+        {
+            this.dnsClient = dnsClient;
+        }
+
+        /// <summary>
+        /// 添加要测速的域名
+        /// </summary>
+        /// <param name="domain"></param>
+        /// <returns></returns>
+        public bool Add(string domain)
+        {
+            lock (this.syncRoot)
+            {
+                return this.domainIPAddressHashSet.TryAdd(domain, new IPAddressItemHashSet());
+            }
+        }
+
+        /// <summary>
+        /// 获取测试后排序的IP
+        /// </summary>
+        /// <param name="domain"></param>
+        /// <returns></returns>
+        public IPAddress[] GetIPAddresses(string domain)
+        {
+            lock (this.syncRoot)
+            {
+                if (this.domainIPAddressHashSet.TryGetValue(domain, out var hashSet) && hashSet.Count > 0)
+                {
+                    return hashSet.ToArray().OrderBy(item => item.PingElapsed).Select(item => item.Address).ToArray();
+                }
+                return Array.Empty<IPAddress>();
+            }
+        }
+
+        /// <summary>
+        /// 加载数据
+        /// </summary>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public async Task LoadDataAsync(CancellationToken cancellationToken)
+        {
+            if (File.Exists(DATA_FILE) == false)
+            {
+                return;
+            }
+
+            var fileStream = File.OpenRead(DATA_FILE);
+            var domains = await JsonSerializer.DeserializeAsync<string[]>(fileStream, cancellationToken: cancellationToken);
+            if (domains == null)
+            {
+                return;
+            }
+
+            lock (this.syncRoot)
+            {
+                foreach (var domain in domains)
+                {
+                    this.domainIPAddressHashSet.TryAdd(domain, new IPAddressItemHashSet());
+                }
+            }
+        }
+
+        /// <summary>
+        /// 保存数据
+        /// </summary>
+        /// <returns></returns>
+        public async Task SaveDataAsync()
+        {
+            var domains = this.domainIPAddressHashSet.Keys.ToArray();
+            using var fileStream = File.OpenWrite(DATA_FILE);
+            await JsonSerializer.SerializeAsync(fileStream, domains);
+        }
+
+        /// <summary>
+        /// 进行一轮IP测速
+        /// </summary>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public async Task TestSpeedAsync(CancellationToken cancellationToken)
+        {
+            KeyValuePair<string, IPAddressItemHashSet>[] keyValues;
+            lock (this.syncRoot)
+            {
+                keyValues = this.domainIPAddressHashSet.ToArray();
+            }
+
+            foreach (var keyValue in keyValues)
+            {
+                var domain = keyValue.Key;
+                var hashSet = keyValue.Value;
+                await foreach (var address in this.dnsClient.ResolveAsync(domain, cancellationToken))
+                {
+                    hashSet.Add(new IPAddressItem(address));
+                }
+                await hashSet.PingAllAsync();
+            }
+        }
+    }
+}

+ 26 - 6
FastGithub.DomainResolve/IPAddressItem.cs

@@ -1,34 +1,54 @@
 using System;
 using System;
+using System.Diagnostics;
 using System.Net;
 using System.Net;
 using System.Net.NetworkInformation;
 using System.Net.NetworkInformation;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 
 
 namespace FastGithub.DomainResolve
 namespace FastGithub.DomainResolve
 {
 {
+    /// <summary>
+    /// IP地址项
+    /// </summary>
+    [DebuggerDisplay("Address = {Address}, PingElapsed = {PingElapsed}")]
     sealed class IPAddressItem : IEquatable<IPAddressItem>
     sealed class IPAddressItem : IEquatable<IPAddressItem>
     {
     {
+        private readonly Ping ping = new();
+
+        /// <summary>
+        /// 地址
+        /// </summary>
         public IPAddress Address { get; }
         public IPAddress Address { get; }
 
 
-        public TimeSpan Elapsed { get; private set; } = TimeSpan.MaxValue;
+        /// <summary>
+        /// Ping耗时
+        /// </summary>
+        public TimeSpan PingElapsed { get; private set; } = TimeSpan.MaxValue;
 
 
+        /// <summary>
+        /// IP地址项
+        /// </summary>
+        /// <param name="address"></param>
         public IPAddressItem(IPAddress address)
         public IPAddressItem(IPAddress address)
         {
         {
             this.Address = address;
             this.Address = address;
         }
         }
 
 
-        public async Task TestSpeedAsync()
+        /// <summary>
+        /// 发起ping请求
+        /// </summary>
+        /// <returns></returns>
+        public async Task PingAsync()
         {
         {
             try
             try
             {
             {
-                using var ping = new Ping();
-                var reply = await ping.SendPingAsync(this.Address);
-                this.Elapsed = reply.Status == IPStatus.Success
+                var reply = await this.ping.SendPingAsync(this.Address);
+                this.PingElapsed = reply.Status == IPStatus.Success
                     ? TimeSpan.FromMilliseconds(reply.RoundtripTime)
                     ? TimeSpan.FromMilliseconds(reply.RoundtripTime)
                     : TimeSpan.MaxValue;
                     : TimeSpan.MaxValue;
             }
             }
             catch (Exception)
             catch (Exception)
             {
             {
-                this.Elapsed = TimeSpan.MaxValue;
+                this.PingElapsed = TimeSpan.MaxValue;
             }
             }
         }
         }
 
 

+ 30 - 14
FastGithub.DomainResolve/IPAddressItemHashSet.cs

@@ -4,14 +4,24 @@ using System.Threading.Tasks;
 
 
 namespace FastGithub.DomainResolve
 namespace FastGithub.DomainResolve
 {
 {
+    /// <summary>
+    /// IPAddressItem集合
+    /// </summary>
     sealed class IPAddressItemHashSet
     sealed class IPAddressItemHashSet
     {
     {
         private readonly object syncRoot = new();
         private readonly object syncRoot = new();
-
         private readonly HashSet<IPAddressItem> hashSet = new();
         private readonly HashSet<IPAddressItem> hashSet = new();
 
 
+        /// <summary>
+        /// 获取元素数量
+        /// </summary>
         public int Count => this.hashSet.Count;
         public int Count => this.hashSet.Count;
 
 
+        /// <summary>
+        /// 添加元素
+        /// </summary>
+        /// <param name="item"></param>
+        /// <returns></returns>
         public bool Add(IPAddressItem item)
         public bool Add(IPAddressItem item)
         {
         {
             lock (this.syncRoot)
             lock (this.syncRoot)
@@ -20,17 +30,10 @@ namespace FastGithub.DomainResolve
             }
             }
         }
         }
 
 
-        public void AddRange(IEnumerable<IPAddressItem> items)
-        {
-            lock (this.syncRoot)
-            {
-                foreach (var item in items)
-                {
-                    this.hashSet.Add(item);
-                }
-            }
-        }
-
+        /// <summary>
+        /// 转换为数组
+        /// </summary>
+        /// <returns></returns>
         public IPAddressItem[] ToArray()
         public IPAddressItem[] ToArray()
         {
         {
             lock (this.syncRoot)
             lock (this.syncRoot)
@@ -39,9 +42,22 @@ namespace FastGithub.DomainResolve
             }
             }
         }
         }
 
 
-        public Task TestSpeedAsync()
+        /// <summary>
+        /// Ping所有IP
+        /// </summary>
+        /// <returns></returns>
+        public Task PingAllAsync()
         {
         {
-            var tasks = this.ToArray().Select(item => item.TestSpeedAsync());
+            var items = this.ToArray();
+            if (items.Length == 0)
+            {
+                return Task.CompletedTask;
+            }
+            if (items.Length == 1)
+            {
+                return items[0].PingAsync();
+            }
+            var tasks = items.Select(item => item.PingAsync());
             return Task.WhenAll(tasks);
             return Task.WhenAll(tasks);
         }
         }
     }
     }

+ 4 - 1
FastGithub.DomainResolve/ServiceCollectionExtensions.cs

@@ -18,8 +18,11 @@ namespace FastGithub
         {
         {
             services.TryAddSingleton<DnsClient>();
             services.TryAddSingleton<DnsClient>();
             services.TryAddSingleton<DnscryptProxy>();
             services.TryAddSingleton<DnscryptProxy>();
+            services.TryAddSingleton<DomainSpeedTestService>();
             services.TryAddSingleton<IDomainResolver, DomainResolver>();
             services.TryAddSingleton<IDomainResolver, DomainResolver>();
-            return services.AddHostedService<DnscryptProxyHostedService>();
+            services.AddHostedService<DnscryptProxyHostedService>();
+            services.AddHostedService<DomainSpeedTestHostedService>();
+            return services;
         }
         }
     }
     }
 }
 }