From 66a0a2de086240ae8ffa52c6d0b05e3d6330ac96 Mon Sep 17 00:00:00 2001 From: LamGC Date: Fri, 10 Jan 2025 10:02:21 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E4=B8=80=E4=B8=AA?= =?UTF-8?q?=E8=BF=98=E6=B2=A1=E5=86=99=E5=AE=8C=E7=9A=84=E7=9B=91=E6=8E=A7?= =?UTF-8?q?=E5=99=A8,=20=E7=94=A8=E4=BA=8E=E5=9C=A8=E8=BF=90=E8=A1=8C?= =?UTF-8?q?=E8=BF=87=E7=A8=8B=E4=B8=AD=E7=9B=91=E6=B5=8B=E5=BD=93=E5=89=8D?= =?UTF-8?q?=E5=87=BA=E7=AB=99=E4=BB=A3=E7=90=86=E6=98=AF=E5=90=A6=E6=AD=A3?= =?UTF-8?q?=E5=B8=B8,=20=E4=B8=94=E8=87=AA=E5=8A=A8=E9=87=8D=E9=80=89?= =?UTF-8?q?=E5=87=BA=E7=AB=99.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Proxd-man/Monitor/OutboundMonitor.cs | 189 +++++++++++++++++++++++++++ Proxd-man/Program.cs | 2 + 2 files changed, 191 insertions(+) create mode 100644 Proxd-man/Monitor/OutboundMonitor.cs diff --git a/Proxd-man/Monitor/OutboundMonitor.cs b/Proxd-man/Monitor/OutboundMonitor.cs new file mode 100644 index 0000000..f913128 --- /dev/null +++ b/Proxd-man/Monitor/OutboundMonitor.cs @@ -0,0 +1,189 @@ +using System.Diagnostics; +using System.Net; +using System.Net.NetworkInformation; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Proxd_man.Config; +using Proxd_man.Config.Outbound; +using Proxd_man.Subscribe; + +namespace Proxd_man.Monitor; + +public class OutboundMonitor +{ + + private static readonly Uri HttpLatencyTestUrl = new("https://cp.cloudflare.com/generate_204"); + private static readonly Uri HttpBandwidthTestUrl = new("https://speed.cloudflare.com/__down?bytes=51200000"); + + private readonly ILogger _logger; + private readonly IOptionsMonitor _configProvider; + private readonly SubscribeManager _subscribeManager; + private readonly XrayCoreManager _xrayCoreManager; + + private ProxdConfig _config => _configProvider.CurrentValue; + + public OutboundMonitor(ILogger logger, IOptionsMonitor configProvider, SubscribeManager subscribeManager, XrayCoreManager xrayCoreManager) + { + _logger = logger; + _configProvider = configProvider; + _subscribeManager = subscribeManager; + _xrayCoreManager = xrayCoreManager; + subscribeManager.SubscribeUpdated += async subscribeOutbounds => + { + await ReselectOutbound(subscribeOutbounds); + }; + } + + private async Task ReselectOutbound(IDictionary> subscribeOutbounds) + { + + _logger.LogInformation("开始重新选择代理..."); + foreach (var (source, outboundSettings) in subscribeOutbounds) + { + _logger.LogDebug("开始测试订阅 {SubscribeName} 的代理...", source.Name); + foreach (var outboundSetting in outboundSettings) + { + if (outboundSetting.Address == null) + { + _logger.LogDebug("跳过代理 {OutboundSettingName} ({OutboundSettingTag}), 因为 Address 为空", + outboundSetting.Name, outboundSetting.GetTag()); + continue; + } + + using var ping = new Ping(); + var pingReply = ping.Send(outboundSetting.Address, 5000); + + if (pingReply.Status != IPStatus.Success) + { + _logger.LogWarning("无法 Ping 到代理 {OutboundSettingName} ({OutboundSettingTag}), 地址: {OutboundSettingAddress}, 错误: {PingReplyStatus}", + outboundSetting.Name, outboundSetting.GetTag(), outboundSetting.Address, pingReply.Status); + continue; + } + + // 如果 Ping 成功, 则开始测试代理的延迟和带宽 + _logger.LogDebug("开始测试代理 {OutboundSettingName} ({OutboundSettingTag}), 地址: {OutboundSettingAddress}", + outboundSetting.Name, outboundSetting.GetTag(), outboundSetting.Address); + + // 随机选择一个端口进行测试 + var port = (ushort) new Random().Next(50000, 60000); + var outboundTestProcess = _xrayCoreManager.CreateOutboundTestProcess(port, outboundSetting); + outboundTestProcess.Start(); + + // 等 Xray-Core 就绪. + await Task.Delay(500); + + // 先测试代理的延迟. + var (latencies, bandwidths) = await MeasureOutbound(port); + if (latencies.All(l => l == -1)) + { + _logger.LogWarning("代理 {OutboundSettingName} ({OutboundSettingTag}) 的延迟测试失败, 地址: {OutboundSettingAddress}", + outboundSetting.Name, outboundSetting.GetTag(), outboundSetting.Address); + continue; + } + + // 计算平均延迟. + var avgLatency = latencies.Where(l => l != -1).Average(); + _logger.LogDebug("代理 {OutboundSettingName} ({OutboundSettingTag}) 的平均延迟为 {AvgLatency}ms, 地址: {OutboundSettingAddress}", + outboundSetting.Name, outboundSetting.GetTag(), avgLatency, outboundSetting.Address); + } + } + } + + public void StartMonitor() + { + Task.Run(async () => + { + + }); + } + + private static async Task<(long[] latencies, long[] bandwidths)> MeasureOutbound(ushort port, bool withBandwidth = false, CancellationToken cancellationToken = default) + { + var latencies = await MeasureHttpLatency(port, cancellationToken); + var bandwidths = withBandwidth ? await MeasureHttpBandwidth(port, cancellationToken) : []; + return (latencies, bandwidths); + } + + private static async Task MeasureHttpLatency(ushort port, CancellationToken cancellationToken = default) + { + var httpClient = new HttpClient(new HttpClientHandler + { + Proxy = new WebProxy + { + Address = new Uri($"http://127.0.0.1:{port}") + }, + UseProxy = true, + }) + { + Timeout = TimeSpan.FromSeconds(10) + }; + + // 发起 8 次请求,取平均值 + var latencies = new long[8]; + for (var i = 0; i < 9; i++) + { + // 第一次请求不计入统计, 因为此时代理可能还未完全启动 + if (i == 0) + { + await httpClient.GetAsync(HttpLatencyTestUrl, cancellationToken); + continue; + } + + var sw = Stopwatch.StartNew(); + try + { + using var response = await httpClient.GetAsync(HttpLatencyTestUrl, cancellationToken); + sw.Stop(); + response.EnsureSuccessStatusCode(); + latencies[i - 1] = sw.ElapsedMilliseconds; + } + catch (Exception) + { + sw.Stop(); + latencies[i - 1] = -1; + } + + await Task.Delay(400, cancellationToken); + } + + return latencies; + } + + private static async Task MeasureHttpBandwidth(ushort port, CancellationToken cancellationToken = default) + { + var httpClient = new HttpClient(new HttpClientHandler + { + Proxy = new WebProxy + { + Address = new Uri($"http://127.0.0.1:{port}") + }, + UseProxy = true, + }) + { + Timeout = TimeSpan.FromSeconds(10) + }; + + // 进行 3 次下载,取平均值 + var bandwidths = new long[3]; + for (var i = 0; i < 3; i++) + { + var sw = Stopwatch.StartNew(); + try + { + using var response = await httpClient.GetAsync(HttpBandwidthTestUrl, cancellationToken); + sw.Stop(); + response.EnsureSuccessStatusCode(); + bandwidths[i] = 51200000 / (sw.ElapsedMilliseconds / 1000); + } + catch (Exception) + { + sw.Stop(); + bandwidths[i] = -1; + } + + await Task.Delay(1000, cancellationToken); + } + + return bandwidths; + } +} \ No newline at end of file diff --git a/Proxd-man/Program.cs b/Proxd-man/Program.cs index c5f5869..ca7f864 100644 --- a/Proxd-man/Program.cs +++ b/Proxd-man/Program.cs @@ -3,6 +3,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Proxd_man.Config; +using Proxd_man.Monitor; using Proxd_man.Subscribe; namespace Proxd_man; @@ -33,6 +34,7 @@ internal class Program .Configure(configuration.GetSection("Proxd")) .AddSingleton() .AddSingleton() + .AddSingleton() .BuildServiceProvider(); var logger = serviceProvider.GetRequiredService>();