diff --git a/v2rayN/ServiceLib/Models/Configs/ConfigItems.cs b/v2rayN/ServiceLib/Models/Configs/ConfigItems.cs index e9e7f5f8..64902914 100644 --- a/v2rayN/ServiceLib/Models/Configs/ConfigItems.cs +++ b/v2rayN/ServiceLib/Models/Configs/ConfigItems.cs @@ -159,6 +159,8 @@ public class SpeedTestItem public int MixedConcurrencyCount { get; set; } public string IPAPIUrl { get; set; } public string UdpTestTarget { get; set; } + public int BatchTesting { get; set; } + public int DelayInterval { get; set; } } [Serializable] diff --git a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs index c3a00a51..e8a37ce5 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs +++ b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs @@ -4472,7 +4472,47 @@ namespace ServiceLib.Resx { return ResourceManager.GetString("TbSettingsUdpTestUrl", resourceCulture); } } + + /// + /// 查找类似 Batch Testing 的本地化字符串。 + /// + + public static string TbSettingsBatchTesting { + get { + return ResourceManager.GetString("TbSettingsBatchTesting", resourceCulture); + } + } + + /// + /// 查找类似 Batch Testing Tip 的本地化字符串。 + /// + public static string TbSettingsBatchTestingTip { + get { + return ResourceManager.GetString("TbSettingsBatchTestingTip", resourceCulture); + } + } + + /// + /// 查找类似 Delay Interval 的本地化字符串。 + /// + + public static string TbSettingsDelayInterval { + get { + return ResourceManager.GetString("TbSettingsDelayInterval", resourceCulture); + } + } + + /// + /// 查找类似 Delay Interval Tip 的本地化字符串。 + /// + + public static string TbSettingsDelayIntervalTip { + get { + return ResourceManager.GetString("TbSettingsDelayIntervalTip", resourceCulture); + } + } + /// /// 查找类似 Auth user 的本地化字符串。 /// diff --git a/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx b/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx index 246417a6..a517e4c2 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx @@ -949,10 +949,10 @@ اندازه فونت - یمقدار تاخیر تست سرعت منفرد + مقدار تاخیر تست سرعت منفرد - /آدرس اینترنتی SpeedTest + آدرس اینترنتی SpeedTest بالا و پایین حرکت کنید @@ -1261,7 +1261,7 @@ فیلتر هسته - فعال سازی + فعال منبع فایل های جغرافیایی (اختیاری) @@ -1705,10 +1705,22 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if Only for fetching self-signed certificates. This may expose you to MITM risks. - Test Configurations UDP Delay + تست تاخیر کانفیگ ها از طریق UDP - UDP Test Url + آدرس تست تاخیر UDP + + + تعداد تست همزمان در هر دسته + + + تعداد کانفیگ هایی که به صورت همزمان در هر دسته تست می شوند. مقدار 0 یعنی بدون محدودیت + + + تأخیر بین دسته ها بر حسب ثانیه + + + مدت زمان انتظار قبل از شروع دسته بعدی. مقدار 0 یعنی بدون تأخیر Local outbound address (SendThrough) @@ -1746,4 +1758,4 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if New Update - \ No newline at end of file + diff --git a/v2rayN/ServiceLib/Resx/ResUI.resx b/v2rayN/ServiceLib/Resx/ResUI.resx index 6cf545db..8de59863 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.resx @@ -1710,6 +1710,18 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if UDP Test Url + + Concurrent Tests Per Batch + + + Number of configurations tested simultaneously in each batch. Set 0 for unlimited. + + + Delay Between Batches (seconds) + + + Wait time before starting the next batch. Set 0 to disable delay. + Local outbound address (SendThrough) @@ -1746,4 +1758,4 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if New Update - \ No newline at end of file + diff --git a/v2rayN/ServiceLib/Services/SpeedtestService.cs b/v2rayN/ServiceLib/Services/SpeedtestService.cs index 08258160..4f92bb28 100644 --- a/v2rayN/ServiceLib/Services/SpeedtestService.cs +++ b/v2rayN/ServiceLib/Services/SpeedtestService.cs @@ -8,7 +8,6 @@ public class SpeedtestService(Config config, Func updateF private readonly Config? _config = config; private readonly Func? _updateFunc = updateFunc; private static readonly ConcurrentBag _lstExitLoop = new(); - public void RunLoop(ESpeedActionType actionType, List selecteds) { Task.Run(async () => @@ -44,7 +43,7 @@ public class SpeedtestService(Config config, Func updateF switch (actionType) { case ESpeedActionType.Tcping: - await RunTcpingAsync(lstSelected); + await RunTcpingAsync(lstSelected, exitLoopKey); break; case ESpeedActionType.Realping: @@ -133,27 +132,67 @@ public class SpeedtestService(Config config, Func updateF return lstSelected; } - private async Task RunTcpingAsync(List selecteds) + private async Task RunTcpingAsync(List selecteds, string exitLoopKey) { - List tasks = []; - foreach (var it in selecteds) + int batchSize = _config.SpeedTestItem.BatchTesting; + int delay = _config.SpeedTestItem.DelayInterval; + + if (batchSize <= 0) { - tasks.Add(Task.Run(async () => - { - try - { - var responseTime = await GetTcpingTime(it.Address, it.Port); - - ProfileExManager.Instance.SetTestDelay(it.IndexId, responseTime); - await UpdateFunc(it.IndexId, responseTime.ToString()); - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - })); + batchSize = selecteds.Count; + } + + for (int i = 0; i < selecteds.Count; i += batchSize) + { + if (ShouldStopTest(exitLoopKey)) + { + return; + } + + var batch = selecteds.Skip(i).Take(batchSize).ToList(); + + List tasks = []; + + foreach (var it in batch) + { + if (ShouldStopTest(exitLoopKey)) + { + return; + } + + tasks.Add(Task.Run(async () => + { + try + { + if (ShouldStopTest(exitLoopKey)) + { + return; + } + + var responseTime = await GetTcpingTime(it.Address, it.Port); + + if (ShouldStopTest(exitLoopKey)) + { + return; + } + + ProfileExManager.Instance.SetTestDelay(it.IndexId, responseTime); + await UpdateFunc(it.IndexId, responseTime.ToString()); + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + })); + } + + await Task.WhenAll(tasks); + + if (delay > 0 && i + batchSize < selecteds.Count) + { + await Task.Delay(TimeSpan.FromSeconds(delay)); + } } - await Task.WhenAll(tasks); } private async Task RunRealPingBatchAsync(List lstSelected, string exitLoopKey, int pageSize = 0) @@ -210,26 +249,46 @@ public class SpeedtestService(Config config, Func updateF } await Task.Delay(1000); - List tasks = new(); - foreach (var it in selecteds) + int batchSize = _config.SpeedTestItem.BatchTesting; + int delay = _config.SpeedTestItem.DelayInterval; + + if (batchSize <= 0) { - if (!it.AllowTest) - { - await UpdateFunc(it.IndexId, ResUI.SpeedtestingSkip); - continue; - } - - if (ShouldStopTest(exitLoopKey)) - { - return false; - } - - tasks.Add(Task.Run(async () => - { - await DoRealPing(it); - })); + batchSize = selecteds.Count; + } + + for (int i = 0; i < selecteds.Count; i += batchSize) + { + var batch = selecteds.Skip(i).Take(batchSize).ToList(); + + List tasks = new(); + + foreach (var it in batch) + { + if (!it.AllowTest) + { + await UpdateFunc(it.IndexId, ResUI.SpeedtestingSkip); + continue; + } + + if (ShouldStopTest(exitLoopKey)) + { + return false; + } + + tasks.Add(Task.Run(async () => + { + await DoRealPing(it); + })); + } + + await Task.WhenAll(tasks); + + if (delay > 0 && i + batchSize < selecteds.Count) + { + await Task.Delay(TimeSpan.FromSeconds(delay)); + } } - await Task.WhenAll(tasks); } catch (Exception ex) { @@ -291,25 +350,45 @@ public class SpeedtestService(Config config, Func updateF } await Task.Delay(1000); - List tasks = new(); - foreach (var it in selecteds) + int batchSize = _config.SpeedTestItem.BatchTesting; + int delay = _config.SpeedTestItem.DelayInterval; + + if (batchSize <= 0) { - if (!it.AllowTest) - { - continue; - } - - if (ShouldStopTest(exitLoopKey)) - { - return false; - } - - tasks.Add(Task.Run(async () => - { - await DoUdpTest(it); - })); + batchSize = selecteds.Count; + } + + for (int i = 0; i < selecteds.Count; i += batchSize) + { + var batch = selecteds.Skip(i).Take(batchSize).ToList(); + + List tasks = new(); + + foreach (var it in batch) + { + if (!it.AllowTest) + { + continue; + } + + if (ShouldStopTest(exitLoopKey)) + { + return false; + } + + tasks.Add(Task.Run(async () => + { + await DoUdpTest(it); + })); + } + + await Task.WhenAll(tasks); + + if (delay > 0 && i + batchSize < selecteds.Count) + { + await Task.Delay(TimeSpan.FromSeconds(delay)); + } } - await Task.WhenAll(tasks); } catch (Exception ex) { diff --git a/v2rayN/ServiceLib/ViewModels/OptionSettingViewModel.cs b/v2rayN/ServiceLib/ViewModels/OptionSettingViewModel.cs index 122772c4..8a4802bf 100644 --- a/v2rayN/ServiceLib/ViewModels/OptionSettingViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/OptionSettingViewModel.cs @@ -62,6 +62,8 @@ public class OptionSettingViewModel : MyReactiveObject [Reactive] public string SpeedPingTestUrl { get; set; } [Reactive] public string UdpTestTarget { get; set; } [Reactive] public int MixedConcurrencyCount { get; set; } + [Reactive] public string BatchTesting { get; set; } + [Reactive] public string DelayInterval { get; set; } [Reactive] public bool EnableHWA { get; set; } [Reactive] public string SubConvertUrl { get; set; } [Reactive] public int MainGirdOrientation { get; set; } @@ -197,6 +199,8 @@ public class OptionSettingViewModel : MyReactiveObject SpeedTestTimeout = _config.SpeedTestItem.SpeedTestTimeout; SpeedTestUrl = _config.SpeedTestItem.SpeedTestUrl; MixedConcurrencyCount = _config.SpeedTestItem.MixedConcurrencyCount; + BatchTesting = _config.SpeedTestItem.BatchTesting.ToString(); + DelayInterval = _config.SpeedTestItem.DelayInterval.ToString(); SpeedPingTestUrl = _config.SpeedTestItem.SpeedPingTestUrl; UdpTestTarget = _config.SpeedTestItem.UdpTestTarget; EnableHWA = _config.GuiItem.EnableHWA; @@ -371,6 +375,10 @@ public class OptionSettingViewModel : MyReactiveObject _config.UiItem.CurrentFontFamily = CurrentFontFamily; _config.SpeedTestItem.SpeedTestTimeout = SpeedTestTimeout; _config.SpeedTestItem.MixedConcurrencyCount = MixedConcurrencyCount; + int.TryParse(BatchTesting, out var batchTesting); + int.TryParse(DelayInterval, out var delayInterval); + _config.SpeedTestItem.BatchTesting = batchTesting; + _config.SpeedTestItem.DelayInterval = delayInterval; _config.SpeedTestItem.SpeedTestUrl = SpeedTestUrl; _config.SpeedTestItem.SpeedPingTestUrl = SpeedPingTestUrl; _config.SpeedTestItem.UdpTestTarget = UdpTestTarget; diff --git a/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml b/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml index 2baea96c..fee63a8f 100644 --- a/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml +++ b/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml @@ -375,7 +375,7 @@ + RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto"> + + + + + + + + + + + this.Bind(ViewModel, vm => vm.SpeedPingTestUrl, v => v.cmbSpeedPingTestUrl.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.UdpTestTarget, v => v.cmbUdpTestTarget.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.MixedConcurrencyCount, v => v.cmbMixedConcurrencyCount.SelectedValue).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.BatchTesting, v => v.txtBatchTesting.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.DelayInterval, v => v.txtDelayInterval.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SubConvertUrl, v => v.cmbSubConvertUrl.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.MainGirdOrientation, view => view.cmbMainGirdOrientation.SelectedIndex).DisposeWith(disposables); diff --git a/v2rayN/v2rayN/Views/OptionSettingWindow.xaml b/v2rayN/v2rayN/Views/OptionSettingWindow.xaml index df46d963..b7f34f15 100644 --- a/v2rayN/v2rayN/Views/OptionSettingWindow.xaml +++ b/v2rayN/v2rayN/Views/OptionSettingWindow.xaml @@ -66,6 +66,8 @@ + + @@ -595,6 +597,8 @@ + + @@ -869,17 +873,65 @@ Margin="{StaticResource Margin8}" IsEditable="True" Style="{StaticResource DefComboBox}" /> - + + + + + + + vm.SpeedPingTestUrl, v => v.cmbSpeedPingTestUrl.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.UdpTestTarget, v => v.cmbUdpTestTarget.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.MixedConcurrencyCount, v => v.cmbMixedConcurrencyCount.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.BatchTesting, v => v.txtBatchTesting.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.DelayInterval, v => v.txtDelayInterval.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.EnableHWA, v => v.togEnableHWA.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SubConvertUrl, v => v.cmbSubConvertUrl.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.MainGirdOrientation, v => v.cmbMainGirdOrientation.SelectedIndex).DisposeWith(disposables);