feat: 增加一个还没写完的监控器, 用于在运行过程中监测当前出站代理是否正常, 且自动重选出站.
This commit is contained in:
@@ -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<OutboundMonitor> _logger;
|
||||
private readonly IOptionsMonitor<ProxdConfig> _configProvider;
|
||||
private readonly SubscribeManager _subscribeManager;
|
||||
private readonly XrayCoreManager _xrayCoreManager;
|
||||
|
||||
private ProxdConfig _config => _configProvider.CurrentValue;
|
||||
|
||||
public OutboundMonitor(ILogger<OutboundMonitor> logger, IOptionsMonitor<ProxdConfig> 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<SubscribeSource, List<BaseOutboundSetting>> 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<long[]> 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<long[]> 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user