├── .github └── workflows │ └── dotnet-desktop.yml ├── .gitignore ├── App.xaml ├── App.xaml.cs ├── AssemblyInfo.cs ├── DNSSpeedTester.csproj ├── DNSSpeedTester.sln ├── Helpers ├── NetworkDiagnostics.cs ├── RelayCommand.cs └── ViewModelBase.cs ├── MainWindow.xaml ├── MainWindow.xaml.cs ├── Models ├── DnsServer.cs ├── NetworkAdapter.cs └── TestDomain.cs ├── README.MD ├── Services ├── DataPersistenceService.cs ├── DnsServerDto.cs ├── DnsSettingService.cs ├── DnsTestService.cs └── IPAddressConverter.cs ├── ViewModels └── MainViewModel.cs └── global.json /.github/workflows/dotnet-desktop.yml: -------------------------------------------------------------------------------- 1 | name: .NET Publish and Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' # 当匹配 'v*.*.*' 格式的标签被推送时触发 7 | workflow_dispatch: # 允许手动触发 8 | 9 | jobs: 10 | build-and-release: 11 | name: Build and Release 12 | runs-on: windows-latest # 使用 Windows 环境 13 | 14 | permissions: 15 | contents: write # 需要写入权限来创建 Release 和上传构建产物 16 | 17 | steps: 18 | - name: Checkout code 19 | uses: actions/checkout@v4 20 | 21 | - name: Setup .NET 22 | uses: actions/setup-dotnet@v4 23 | with: 24 | dotnet-version: '9.0.x' 25 | 26 | - name: Build and publish for win-x64 27 | run: dotnet publish DNSSpeedTester.csproj -c Release -r win-x64 --output ./publish-x64 28 | 29 | - name: Rename win-x64 executable 30 | run: ren publish-x64\DNSSpeedTester.exe DNSSpeedTester-x64.exe 31 | 32 | - name: Archive win-x64 artifact 33 | uses: actions/upload-artifact@v4 34 | with: 35 | name: DNSSpeedTester-win-x64-files 36 | path: ./publish-x64 37 | 38 | - name: Build and publish for win-x86 39 | run: dotnet publish DNSSpeedTester.csproj -c Release -r win-x86 --output ./publish-x86 40 | 41 | - name: Rename win-x86 executable 42 | run: ren publish-x86\DNSSpeedTester.exe DNSSpeedTester-x86.exe 43 | 44 | - name: Archive win-x86 artifact 45 | uses: actions/upload-artifact@v4 46 | with: 47 | name: DNSSpeedTester-win-x86-files 48 | path: ./publish-x86 49 | 50 | - name: Create Release and Upload Assets 51 | if: startsWith(github.ref, 'refs/tags/') # 仅当事件是标签推送时执行 52 | uses: softprops/action-gh-release@v2 53 | with: 54 | files: | 55 | ./publish-*/*.exe 56 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # 忽略Rider生成的文件 2 | .run/ 3 | riderModule.iml 4 | /_ReSharper.Caches/ 5 | 6 | # 忽略所有编译输出目录 7 | bin/ 8 | obj/ 9 | 10 | # 忽略Visual Studio生成的用户特定文件 11 | *.suo 12 | *.user 13 | *.userosscache 14 | *.sln.docstates 15 | 16 | # 忽略Visual Studio项目文件中的临时文件 17 | **/*.csproj.user 18 | **/*.csproj.vspscc 19 | **/*.vssscc 20 | 21 | # 忽略NuGet包缓存 22 | packages/ 23 | 24 | # 忽略Visual Studio Code生成的文件 25 | .vscode/ 26 | .idea/ 27 | .vs/ 28 | *.code-workspace 29 | 30 | # 忽略操作系统生成的文件 31 | .DS_Store 32 | Thumbs.db 33 | 34 | # 忽略日志文件 35 | *.log 36 | 37 | # 忽略测试结果文件 38 | TestResults/ 39 | -------------------------------------------------------------------------------- /App.xaml: -------------------------------------------------------------------------------- 1 |  6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 16 | 17 | 19 | 21 | 23 | 25 | 27 | 29 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using System.Windows.Threading; 3 | 4 | namespace DNSSpeedTester; 5 | 6 | public partial class App : Application 7 | { 8 | protected override void OnStartup(StartupEventArgs e) 9 | { 10 | base.OnStartup(e); 11 | 12 | // 注册全局未捕获异常处理 13 | AppDomain.CurrentDomain.UnhandledException += HandleUnhandledException; 14 | DispatcherUnhandledException += HandleDispatcherException; 15 | } 16 | 17 | private void HandleUnhandledException(object sender, UnhandledExceptionEventArgs e) 18 | { 19 | if (e.ExceptionObject is Exception ex) ShowErrorMessage($"发生未处理的异常: {ex.Message}"); 20 | } 21 | 22 | private void HandleDispatcherException(object sender, 23 | DispatcherUnhandledExceptionEventArgs e) 24 | { 25 | ShowErrorMessage($"发生未处理的异常: {e.Exception.Message}"); 26 | e.Handled = true; 27 | } 28 | 29 | private static void ShowErrorMessage(string message) 30 | { 31 | MessageBox.Show(message, "错误", MessageBoxButton.OK, MessageBoxImage.Error); 32 | } 33 | } -------------------------------------------------------------------------------- /AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | [assembly: ThemeInfo( 4 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located 5 | //(used if a resource is not found in the page, 6 | // or application resource dictionaries) 7 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located 8 | //(used if a resource is not found in the page, 9 | // app, or any theme specific resource dictionaries) 10 | )] -------------------------------------------------------------------------------- /DNSSpeedTester.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | WinExe 5 | net9.0-windows 6 | enable 7 | enable 8 | true 9 | 10 | 11 | 12 | true 13 | 14 | false 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | true 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /DNSSpeedTester.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DNSSpeedTester", "DNSSpeedTester.csproj", "{C1E8B880-38CF-4268-B742-ACDCC6813EC7}" 4 | EndProject 5 | Global 6 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 7 | Debug|Any CPU = Debug|Any CPU 8 | Release|Any CPU = Release|Any CPU 9 | EndGlobalSection 10 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 11 | {C1E8B880-38CF-4268-B742-ACDCC6813EC7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 12 | {C1E8B880-38CF-4268-B742-ACDCC6813EC7}.Debug|Any CPU.Build.0 = Debug|Any CPU 13 | {C1E8B880-38CF-4268-B742-ACDCC6813EC7}.Release|Any CPU.ActiveCfg = Release|Any CPU 14 | {C1E8B880-38CF-4268-B742-ACDCC6813EC7}.Release|Any CPU.Build.0 = Release|Any CPU 15 | EndGlobalSection 16 | EndGlobal 17 | -------------------------------------------------------------------------------- /Helpers/NetworkDiagnostics.cs: -------------------------------------------------------------------------------- 1 | using System.Management; 2 | using System.Security.Principal; 3 | using System.Text; 4 | using System.Windows; 5 | 6 | namespace DNSSpeedTester.Helpers; 7 | 8 | public static class NetworkDiagnostics 9 | { 10 | public static void RunDiagnostics() 11 | { 12 | try 13 | { 14 | var sb = new StringBuilder(); 15 | sb.AppendLine("=== 网络适配器诊断 ==="); 16 | sb.AppendLine($"时间: {DateTime.Now}"); 17 | sb.AppendLine($"操作系统: {Environment.OSVersion.VersionString}"); 18 | sb.AppendLine($"64位系统: {Environment.Is64BitOperatingSystem}"); 19 | sb.AppendLine($"进程权限: {(IsRunningAsAdmin() ? "管理员" : "普通用户")}"); 20 | sb.AppendLine(); 21 | 22 | // 检查 WMI 服务 23 | sb.AppendLine("检查 WMI 服务状态..."); 24 | CheckWmiService(sb); 25 | sb.AppendLine(); 26 | 27 | // 尝试获取网络适配器 28 | sb.AppendLine("尝试获取网络适配器信息..."); 29 | GetNetworkAdaptersInfo(sb); 30 | 31 | // 显示诊断信息 32 | MessageBox.Show(sb.ToString(), "网络适配器诊断", MessageBoxButton.OK, MessageBoxImage.Information); 33 | } 34 | catch (Exception ex) 35 | { 36 | MessageBox.Show($"运行诊断时出错: {ex.Message}", "诊断错误", MessageBoxButton.OK, MessageBoxImage.Error); 37 | } 38 | } 39 | 40 | 41 | private static bool IsRunningAsAdmin() 42 | { 43 | try 44 | { 45 | var principal = new WindowsPrincipal(WindowsIdentity.GetCurrent()); 46 | return principal.IsInRole(WindowsBuiltInRole.Administrator); 47 | } 48 | catch 49 | { 50 | return false; 51 | } 52 | } 53 | 54 | private static void CheckWmiService(StringBuilder sb) 55 | { 56 | try 57 | { 58 | using (var searcher = new ManagementObjectSearcher("SELECT * FROM Win32_Service WHERE Name='Winmgmt'")) 59 | { 60 | foreach (var service in searcher.Get()) 61 | { 62 | var state = service["State"]?.ToString() ?? "未知"; 63 | var startMode = service["StartMode"]?.ToString() ?? "未知"; 64 | sb.AppendLine($"WMI 服务状态: {state}"); 65 | sb.AppendLine($"WMI 服务启动模式: {startMode}"); 66 | return; 67 | } 68 | } 69 | 70 | sb.AppendLine("未找到 WMI 服务信息"); 71 | } 72 | catch (Exception ex) 73 | { 74 | sb.AppendLine($"检查 WMI 服务时出错: {ex.Message}"); 75 | } 76 | } 77 | 78 | private static void GetNetworkAdaptersInfo(StringBuilder sb) 79 | { 80 | try 81 | { 82 | sb.AppendLine("方法 1: Win32_NetworkAdapter"); 83 | using (var searcher = 84 | new ManagementObjectSearcher( 85 | "SELECT * FROM Win32_NetworkAdapter WHERE NetConnectionID IS NOT NULL")) 86 | { 87 | var adapters = searcher.Get(); 88 | var count = 0; 89 | foreach (var adapter in adapters) 90 | { 91 | count++; 92 | sb.AppendLine($"- 适配器 {count}: {adapter["Description"]}"); 93 | sb.AppendLine($" ID: {adapter["DeviceID"]}"); 94 | sb.AppendLine($" 名称: {adapter["NetConnectionID"]}"); 95 | sb.AppendLine($" MAC: {adapter["MACAddress"]}"); 96 | } 97 | 98 | sb.AppendLine($"总共找到 {count} 个适配器"); 99 | } 100 | 101 | sb.AppendLine("\n方法 2: Win32_NetworkAdapterConfiguration"); 102 | using (var searcher = 103 | new ManagementObjectSearcher( 104 | "SELECT * FROM Win32_NetworkAdapterConfiguration WHERE IPEnabled = True")) 105 | { 106 | var configs = searcher.Get(); 107 | var count = 0; 108 | foreach (var config in configs) 109 | { 110 | count++; 111 | sb.AppendLine($"- 配置 {count}: {config["Description"]}"); 112 | sb.AppendLine($" 索引: {config["Index"]}"); 113 | sb.AppendLine($" DHCP启用: {config["DHCPEnabled"]}"); 114 | 115 | if (config["IPAddress"] is string[] ipAddresses && ipAddresses.Length > 0) 116 | sb.AppendLine($" IP地址: {string.Join(", ", ipAddresses)}"); 117 | 118 | if (config["DNSServerSearchOrder"] is string[] dnsServers && dnsServers.Length > 0) 119 | sb.AppendLine($" DNS服务器: {string.Join(", ", dnsServers)}"); 120 | } 121 | 122 | sb.AppendLine($"总共找到 {count} 个网络配置"); 123 | } 124 | } 125 | catch (Exception ex) 126 | { 127 | sb.AppendLine($"获取网络适配器信息时出错: {ex.Message}"); 128 | sb.AppendLine($"异常类型: {ex.GetType().FullName}"); 129 | sb.AppendLine($"堆栈跟踪: {ex.StackTrace}"); 130 | } 131 | } 132 | } -------------------------------------------------------------------------------- /Helpers/RelayCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Input; 2 | 3 | namespace DNSSpeedTester.Helpers; 4 | 5 | public class RelayCommand : ICommand 6 | { 7 | private readonly Func _canExecute; 8 | private readonly Action _execute; 9 | 10 | public RelayCommand(Action execute, Func? canExecute = null) 11 | { 12 | _execute = execute ?? throw new ArgumentNullException(nameof(execute)); 13 | _canExecute = canExecute ?? (_ => true); 14 | } 15 | 16 | public event EventHandler? CanExecuteChanged 17 | { 18 | add => CommandManager.RequerySuggested += value; 19 | remove => CommandManager.RequerySuggested -= value; 20 | } 21 | 22 | public bool CanExecute(object? parameter) 23 | { 24 | return _canExecute(parameter); 25 | } 26 | 27 | public void Execute(object? parameter) 28 | { 29 | _execute(parameter); 30 | } 31 | 32 | public void RaiseCanExecuteChanged() 33 | { 34 | CommandManager.InvalidateRequerySuggested(); 35 | } 36 | } -------------------------------------------------------------------------------- /Helpers/ViewModelBase.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Runtime.CompilerServices; 3 | 4 | namespace DNSSpeedTester.Helpers; 5 | 6 | public abstract class ViewModelBase : INotifyPropertyChanged 7 | { 8 | public event PropertyChangedEventHandler? PropertyChanged; 9 | 10 | protected void OnPropertyChanged([CallerMemberName] string? propertyName = null) 11 | { 12 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 13 | } 14 | 15 | protected bool SetProperty(ref T storage, T value, [CallerMemberName] string? propertyName = null) 16 | { 17 | if (Equals(storage, value)) return false; 18 | 19 | storage = value; 20 | OnPropertyChanged(propertyName); 21 | return true; 22 | } 23 | } -------------------------------------------------------------------------------- /MainWindow.xaml: -------------------------------------------------------------------------------- 1 |  24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | DNS 测速与设置工具 62 | 63 | 64 | 65 | 67 | 68 | 69 | 74 | 75 | 80 | 81 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 117 | 118 | 125 | 126 | 136 | 137 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 164 | 165 | 166 | 167 | 168 | 169 | 171 | 172 | 174 | 175 | 176 | 177 | 178 | 179 | 186 | 187 | 196 | 197 | 198 | 199 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 215 | 216 | 218 | 219 | 220 | 221 | 230 | 231 | 232 | 233 | 235 | 239 | 240 | 265 | 266 | 267 | 268 | 269 | 270 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 301 | 302 | 307 | 308 | 318 | 319 | 320 | 321 | 322 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 337 | 338 | 343 | 344 | 349 | 350 | 360 | 361 | 362 | 363 | 364 | 365 | 367 | 368 | 369 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | -------------------------------------------------------------------------------- /MainWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using System.Windows.Input; 3 | using MaterialDesignThemes.Wpf; 4 | 5 | namespace DNSSpeedTester; 6 | 7 | public partial class MainWindow : Window 8 | { 9 | public MainWindow() 10 | { 11 | InitializeComponent(); 12 | } 13 | 14 | private void TitleBar_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) 15 | { 16 | if (e.ClickCount == 2) 17 | MaximizeRestoreButton_Click(sender, e); 18 | else 19 | DragMove(); 20 | } 21 | 22 | private void MinimizeButton_Click(object sender, RoutedEventArgs e) 23 | { 24 | WindowState = WindowState.Minimized; 25 | } 26 | 27 | private void MaximizeRestoreButton_Click(object sender, RoutedEventArgs e) 28 | { 29 | if (WindowState == WindowState.Maximized) 30 | { 31 | WindowState = WindowState.Normal; 32 | MaximizeIcon.Kind = PackIconKind.WindowMaximize; 33 | } 34 | else 35 | { 36 | WindowState = WindowState.Maximized; 37 | MaximizeIcon.Kind = PackIconKind.WindowRestore; 38 | } 39 | } 40 | 41 | private void CloseButton_Click(object sender, RoutedEventArgs e) 42 | { 43 | Close(); 44 | } 45 | } -------------------------------------------------------------------------------- /Models/DnsServer.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | 3 | namespace DNSSpeedTester.Models; 4 | 5 | public class DnsServer 6 | { 7 | public DnsServer(string name, string primaryIp, string? secondaryIp = null, bool isCustom = false) 8 | { 9 | Name = name; 10 | PrimaryIP = IPAddress.Parse(primaryIp); 11 | SecondaryIP = secondaryIp is not null ? IPAddress.Parse(secondaryIp) : null; 12 | IsCustom = isCustom; 13 | } 14 | 15 | public DnsServer() 16 | { 17 | } 18 | 19 | public string Name { get; set; } 20 | public IPAddress PrimaryIP { get; set; } 21 | public IPAddress? SecondaryIP { get; set; } 22 | public bool IsCustom { get; set; } 23 | public int? Latency { get; set; } 24 | public string Status { get; set; } = "未测试"; 25 | public string StatusDetail { get; set; } = string.Empty; 26 | 27 | public string LatencyDisplay => Latency.HasValue ? $"{Latency.Value} 毫秒" : Status; 28 | } -------------------------------------------------------------------------------- /Models/NetworkAdapter.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | 3 | namespace DNSSpeedTester.Models; 4 | 5 | public class NetworkAdapter 6 | { 7 | public NetworkAdapter(string id, string name, string description) 8 | { 9 | Id = id; 10 | Name = name; 11 | Description = description; 12 | DnsServers = new List(); 13 | } 14 | 15 | public string Id { get; set; } 16 | public string Name { get; set; } 17 | public string Description { get; set; } 18 | public bool IsConnected { get; set; } 19 | public bool IsDhcpEnabled { get; set; } 20 | public List DnsServers { get; set; } = new(); 21 | 22 | public override string ToString() 23 | { 24 | return Description; 25 | } 26 | } -------------------------------------------------------------------------------- /Models/TestDomain.cs: -------------------------------------------------------------------------------- 1 | namespace DNSSpeedTester.Models; 2 | 3 | public class TestDomain 4 | { 5 | public TestDomain(string name, string domain, string category = "常用", bool isCustom = false) 6 | { 7 | Name = name; 8 | Domain = domain; 9 | Category = category; 10 | IsCustom = isCustom; 11 | } 12 | 13 | public TestDomain() 14 | { 15 | } 16 | 17 | public string Name { get; set; } 18 | public string Domain { get; set; } 19 | public string Category { get; set; } 20 | public bool IsCustom { get; set; } 21 | 22 | public override string ToString() 23 | { 24 | return $"{Name} [{Domain}]"; 25 | } 26 | } -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | # DNS 测速与设置工具 使用说明 2 | 3 | **版本:** 1.0.0 4 | **日期:** 2025-05-08 5 | **作者:** xihan123 6 | 7 | ## 目录 8 | 9 | 1. [项目介绍](#1-项目介绍) 10 | 2. [功能特点](#2-功能特点) 11 | 3. [使用指南](#3-使用指南) 12 | - [3.1 界面概览](#31-界面概览) 13 | - [3.2 测试DNS速度](#32-测试dns速度) 14 | - [3.3 设置DNS服务器](#33-设置dns服务器) 15 | - [3.4 添加自定义测试域名](#34-添加自定义测试域名) 16 | - [3.5 添加自定义DNS服务器](#35-添加自定义dns服务器) 17 | 18 | ## 1. 项目介绍 19 | 20 | DNS测速与设置工具是一款专为网络优化设计的实用工具,可以帮助用户测试各个公共DNS服务器的响应速度,并轻松设置系统DNS服务器。通过使用本工具,您可以找到最适合自己网络环境的DNS服务器,从而提高网页加载速度和整体上网体验。 21 | 22 | ## 2. 功能特点 23 | 24 | - **多DNS服务器测速**:内置多个国内外公共DNS服务器,可一键测试延迟 25 | - **真实用户体验测速**:使用优化的测速算法,更符合实际上网体验 26 | - **自定义测试域名**:支持添加自己常用的网站域名作为测试目标 27 | - **一键设置DNS**:轻松将系统DNS设置为测试结果中表现最好的服务器 28 | - **自定义DNS服务器**:可以添加自己的DNS服务器进行测试和设置 29 | - **网络适配器管理**:支持多网卡环境,可以为不同网卡设置不同DNS 30 | - **现代化界面**:美观易用的Material Design界面设计 31 | 32 | ## 3. 使用指南 33 | 34 | ### 3.1 界面概览 35 | 36 | 程序界面主要分为以下几个部分: 37 | 38 | - 顶部:网络适配器选择及DNS设置操作区 39 | - 中部:测试域名选择及测试控制区 40 | - 中下部:DNS服务器列表显示区 41 | - 底部:添加自定义测试域名和DNS服务器区 42 | 43 | ### 3.2 测试DNS速度 44 | 45 | 1. 在顶部下拉菜单中选择要测试的网络适配器 46 | 2. 在中部下拉菜单中选择测试域名(或使用默认域名) 47 | 3. 点击"开始测速"按钮 48 | 4. 等待测试完成,DNS服务器列表会按延迟从低到高排序 49 | 5. 最快的DNS服务器将自动被选中 50 | 51 | **测试结果说明**: 52 | 53 | - 延迟值越低,表示DNS响应越快 54 | - "成功"状态表示测试正常完成 55 | - "超时"状态表示DNS服务器响应超时 56 | - "错误"状态表示测试过程中发生错误 57 | 58 | ### 3.3 设置DNS服务器 59 | 60 | 1. 在DNS服务器列表中选择您想要设置的DNS服务器 61 | 2. 确认已在顶部选择正确的网络适配器 62 | 3. 点击"设置所选DNS"按钮 63 | 4. 系统会请求管理员权限(如果尚未获取) 64 | 5. 设置成功后,底部状态栏会显示确认信息 65 | 66 | **恢复自动获取DNS**: 67 | 68 | - 如需恢复为自动获取DNS,请点击"恢复DHCP"按钮 69 | 70 | ### 3.4 添加自定义测试域名 71 | 72 | 1. 在底部的"域名名称"文本框中输入自定义域名的描述(如"我的网站") 73 | 2. 在"域名"文本框中输入完整域名(如"www.example.com") 74 | 3. 点击"添加测试域名"按钮 75 | 4. 添加成功后,该域名会出现在测试域名下拉列表中,标记为"自定义"类别 76 | 77 | **随机域名**: 78 | 79 | - 工具提供"随机域名"选项,用于绕过缓存进行更准确的测速 80 | - 点击随机域名旁的刷新按钮可生成新的随机域名 81 | 82 | ### 3.5 添加自定义DNS服务器 83 | 84 | 1. 在底部的"DNS名称"文本框中输入DNS服务器名称(如"我的DNS") 85 | 2. 在"主DNS地址"文本框中输入主DNS服务器IP地址(如"8.8.8.8") 86 | 3. 在"备用DNS地址"文本框中输入备用DNS服务器IP地址(可选) 87 | 4. 点击"添加DNS"按钮 88 | 5. 添加成功后,新DNS服务器会出现在列表中,标记为自定义服务器 89 | 90 | ## 4. 常见问题解答 91 | 92 | ### Q: 为什么程序需要管理员权限? 93 | 94 | **A:** 修改系统DNS设置需要管理员权限。这是Windows系统的安全限制,用于保护网络设置不被未授权程序更改。 95 | 96 | ### Q: 无法获取网络适配器怎么办? 97 | 98 | **A:** 99 | 100 | 1. 确保以管理员身份运行程序 101 | 2. 点击"诊断"按钮查看详细错误信息 102 | 3. 检查WMI服务是否正常运行(可通过服务管理器检查) 103 | 4. 重启计算机后再次尝试 104 | 105 | ### Q: 为什么测试结果显示所有DNS服务器延迟都很低? 106 | 107 | **A:** 108 | 109 | 1. 首次测试时可能会利用系统DNS缓存,导致结果异常低 110 | 2. 尝试使用"随机域名"选项进行测试,或点击刷新按钮更新随机域名 111 | 3. 多次测试以获取更准确的结果 112 | 113 | ### Q: 如何判断哪个DNS服务器最适合我? 114 | 115 | **A:** 116 | 117 | 1. 延迟低的DNS服务器通常更快 118 | 2. 但也要考虑DNS服务器的稳定性和安全性 119 | 3. 建议选择知名公司提供的DNS服务器 120 | 4. 国内用户可优先考虑国内DNS服务器(如阿里DNS、114DNS等) 121 | 122 | ### Q: 设置DNS后没有生效怎么办? 123 | 124 | **A:** 125 | 126 | 1. 确保设置成功(底部状态栏会显示确认信息) 127 | 2. 打开命令提示符,运行`ipconfig /flushdns`清除DNS缓存 128 | 3. 重启网络适配器或重新连接网络 129 | 4. 通过`ipconfig /all`命令确认DNS设置是否已更改 130 | 131 | --- 132 | -------------------------------------------------------------------------------- /Services/DataPersistenceService.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.IO; 3 | using System.Text; 4 | using System.Text.Json; 5 | using System.Text.Json.Serialization; 6 | using DNSSpeedTester.Models; 7 | 8 | namespace DNSSpeedTester.Services; 9 | 10 | public class DataPersistenceService 11 | { 12 | private readonly string _customDnsFilePath; 13 | private readonly string _customDomainsFilePath; 14 | private readonly JsonSerializerOptions _jsonOptions; 15 | private readonly string _logFilePath; 16 | 17 | public DataPersistenceService() 18 | { 19 | var appDataPath = Path.Combine( 20 | Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), 21 | "DNSSpeedTester"); 22 | 23 | try 24 | { 25 | if (!Directory.Exists(appDataPath)) Directory.CreateDirectory(appDataPath); 26 | } 27 | catch (Exception ex) 28 | { 29 | LogError($"Failed to create app data directory: {ex.Message}"); 30 | } 31 | 32 | _customDnsFilePath = Path.Combine(appDataPath, "custom_dns.json"); 33 | _customDomainsFilePath = Path.Combine(appDataPath, "custom_domains.json"); 34 | _logFilePath = Path.Combine(appDataPath, "error_logs.txt"); 35 | 36 | _jsonOptions = new JsonSerializerOptions 37 | { 38 | WriteIndented = true, 39 | PropertyNameCaseInsensitive = true, 40 | DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, 41 | Converters = { new IpAddressConverter() } 42 | }; 43 | } 44 | 45 | public void SaveCustomDnsServers(List? customDnsServers) 46 | { 47 | if (customDnsServers == null) 48 | { 49 | LogError("Cannot save null DNS server list"); 50 | return; 51 | } 52 | 53 | try 54 | { 55 | LogInfo($"Attempting to save {customDnsServers.Count} DNS servers"); 56 | 57 | // Convert DnsServer objects to DTOs to avoid IPAddress serialization issues 58 | var dtoList = new List(); 59 | foreach (var server in customDnsServers) dtoList.Add(DnsServerDto.FromDnsServer(server)); 60 | 61 | SaveToFile(_customDnsFilePath, dtoList); 62 | LogInfo($"Successfully saved {dtoList.Count} DNS servers"); 63 | } 64 | catch (Exception ex) 65 | { 66 | LogError($"Exception in SaveCustomDnsServers: {ex}"); 67 | // Re-throw to allow caller to handle or display error 68 | throw; 69 | } 70 | } 71 | 72 | public List LoadCustomDnsServers() 73 | { 74 | try 75 | { 76 | var dtoList = LoadFromFile>(_customDnsFilePath); 77 | 78 | if (dtoList == null || dtoList.Count == 0) 79 | { 80 | LogInfo("No custom DNS servers found to load"); 81 | return new List(); 82 | } 83 | 84 | // Convert DTOs back to DnsServer objects 85 | var result = new List(); 86 | foreach (var dto in dtoList) result.Add(dto.ToDnsServer()); 87 | 88 | LogInfo($"Loaded {result.Count} custom DNS servers"); 89 | return result; 90 | } 91 | catch (Exception ex) 92 | { 93 | LogError($"Exception loading custom DNS servers: {ex.Message}"); 94 | return new List(); 95 | } 96 | } 97 | 98 | public void SaveCustomTestDomains(List? customDomains) 99 | { 100 | if (customDomains == null) 101 | { 102 | LogError("Cannot save null test domain list"); 103 | return; 104 | } 105 | 106 | try 107 | { 108 | LogInfo($"Attempting to save {customDomains.Count} test domains"); 109 | SaveToFile(_customDomainsFilePath, customDomains); 110 | LogInfo($"Successfully saved {customDomains.Count} test domains"); 111 | } 112 | catch (Exception ex) 113 | { 114 | LogError($"Exception in SaveCustomTestDomains: {ex}"); 115 | throw; 116 | } 117 | } 118 | 119 | public List LoadCustomTestDomains() 120 | { 121 | try 122 | { 123 | var result = LoadFromFile>(_customDomainsFilePath) ?? new List(); 124 | LogInfo($"Loaded {result.Count} custom test domains"); 125 | return result; 126 | } 127 | catch (Exception ex) 128 | { 129 | LogError($"Exception loading custom test domains: {ex.Message}"); 130 | return new List(); 131 | } 132 | } 133 | 134 | private void SaveToFile(string filePath, T data) 135 | { 136 | try 137 | { 138 | // Create a backup of the existing file if it exists 139 | if (File.Exists(filePath)) 140 | { 141 | var backupPath = $"{filePath}.bak"; 142 | File.Copy(filePath, backupPath, true); 143 | LogInfo($"Created backup at {backupPath}"); 144 | } 145 | 146 | // Serialize to a string first 147 | var json = JsonSerializer.Serialize(data, _jsonOptions); 148 | 149 | // Write to a temporary file first, then move it to the destination 150 | var tempPath = $"{filePath}.tmp"; 151 | File.WriteAllText(tempPath, json); 152 | 153 | if (File.Exists(filePath)) 154 | File.Delete(filePath); 155 | 156 | File.Move(tempPath, filePath); 157 | } 158 | catch (Exception ex) 159 | { 160 | LogError($"Error saving to file '{filePath}': {ex}"); 161 | throw; 162 | } 163 | } 164 | 165 | private T? LoadFromFile(string filePath) where T : class 166 | { 167 | try 168 | { 169 | if (!File.Exists(filePath)) 170 | return null; 171 | 172 | var json = File.ReadAllText(filePath); 173 | if (string.IsNullOrWhiteSpace(json)) 174 | return null; 175 | 176 | return JsonSerializer.Deserialize(json, _jsonOptions); 177 | } 178 | catch (Exception ex) 179 | { 180 | LogError($"Error loading from file '{filePath}': {ex.Message}"); 181 | 182 | // Try to recover from backup 183 | try 184 | { 185 | var backupPath = $"{filePath}.bak"; 186 | if (File.Exists(backupPath)) 187 | { 188 | LogInfo($"Attempting to restore from backup: {backupPath}"); 189 | var backupJson = File.ReadAllText(backupPath); 190 | return JsonSerializer.Deserialize(backupJson, _jsonOptions); 191 | } 192 | } 193 | catch (Exception backupEx) 194 | { 195 | LogError($"Failed to restore from backup: {backupEx.Message}"); 196 | } 197 | 198 | throw; 199 | } 200 | } 201 | 202 | private void LogInfo(string message) 203 | { 204 | WriteLog("INFO", message); 205 | } 206 | 207 | private void LogError(string message) 208 | { 209 | WriteLog("ERROR", message); 210 | } 211 | 212 | private void WriteLog(string level, string message) 213 | { 214 | var timestamp = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss"); 215 | var username = Environment.UserName; 216 | var logEntry = $"{timestamp} {username}{level}: {message}"; 217 | 218 | Debug.WriteLine(logEntry); 219 | 220 | try 221 | { 222 | ManageLogFileSize(); 223 | 224 | using (var fileStream = new FileStream(_logFilePath, FileMode.Append, FileAccess.Write, FileShare.Read)) 225 | using (var writer = new StreamWriter(fileStream, Encoding.UTF8)) 226 | { 227 | writer.WriteLine(logEntry); 228 | } 229 | } 230 | catch (Exception ex) 231 | { 232 | Debug.WriteLine($"Failed to write to log file: {ex.Message}"); 233 | } 234 | } 235 | 236 | private void ManageLogFileSize() 237 | { 238 | try 239 | { 240 | if (!File.Exists(_logFilePath)) 241 | return; 242 | 243 | var fileInfo = new FileInfo(_logFilePath); 244 | const long maxSizeBytes = 5 * 1024 * 1024; // 5MB max log size 245 | 246 | if (fileInfo.Length > maxSizeBytes) 247 | { 248 | var oldLogPath = $"{_logFilePath}.old"; 249 | if (File.Exists(oldLogPath)) 250 | File.Delete(oldLogPath); 251 | 252 | File.Move(_logFilePath, oldLogPath); 253 | } 254 | } 255 | catch 256 | { 257 | // Ignore errors in log rotation 258 | } 259 | } 260 | } -------------------------------------------------------------------------------- /Services/DnsServerDto.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using DNSSpeedTester.Models; 3 | 4 | namespace DNSSpeedTester.Services; 5 | 6 | public class DnsServerDto 7 | { 8 | // Properties matching your DnsServer class 9 | public string Name { get; set; } = string.Empty; 10 | public string PrimaryIPString { get; set; } = string.Empty; 11 | public string? SecondaryIPString { get; set; } 12 | public bool IsCustom { get; set; } 13 | public int? Latency { get; set; } 14 | public string Status { get; set; } = "未测试"; 15 | public string StatusDetail { get; set; } = string.Empty; 16 | 17 | // Convert from DnsServer to DTO 18 | public static DnsServerDto FromDnsServer(DnsServer server) 19 | { 20 | return new DnsServerDto 21 | { 22 | Name = server.Name ?? string.Empty, 23 | PrimaryIPString = server.PrimaryIP?.ToString() ?? string.Empty, 24 | SecondaryIPString = server.SecondaryIP?.ToString(), 25 | IsCustom = server.IsCustom, 26 | Latency = server.Latency, 27 | Status = server.Status ?? "未测试", 28 | StatusDetail = server.StatusDetail ?? string.Empty 29 | }; 30 | } 31 | 32 | // Convert from DTO back to DnsServer 33 | public DnsServer ToDnsServer() 34 | { 35 | var server = new DnsServer 36 | { 37 | Name = Name ?? string.Empty, 38 | IsCustom = IsCustom, 39 | Latency = Latency, 40 | Status = Status ?? "未测试", 41 | StatusDetail = StatusDetail ?? string.Empty 42 | }; 43 | 44 | // Parse IP addresses from their string representations 45 | if (!string.IsNullOrEmpty(PrimaryIPString)) server.PrimaryIP = IPAddress.Parse(PrimaryIPString); 46 | 47 | if (!string.IsNullOrEmpty(SecondaryIPString)) server.SecondaryIP = IPAddress.Parse(SecondaryIPString); 48 | 49 | return server; 50 | } 51 | } -------------------------------------------------------------------------------- /Services/DnsSettingService.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Management; 3 | using System.Net; 4 | using System.Windows; 5 | using DNSSpeedTester.Models; 6 | 7 | namespace DNSSpeedTester.Services; 8 | 9 | public class DnsSettingService 10 | { 11 | // 获取网络适配器列表 12 | public List GetNetworkAdapters() 13 | { 14 | var adapters = new List(); 15 | 16 | try 17 | { 18 | // 使用更可靠的WMI查询获取网络适配器信息 19 | // 查询所有网络适配器,不限制为已连接状态 20 | using (var searcher = 21 | new ManagementObjectSearcher( 22 | "SELECT * FROM Win32_NetworkAdapter WHERE NetConnectionID IS NOT NULL")) 23 | { 24 | foreach (var obj in searcher.Get()) 25 | try 26 | { 27 | var deviceId = obj["DeviceID"]?.ToString(); 28 | var name = obj["NetConnectionID"]?.ToString(); 29 | var description = obj["Description"]?.ToString(); 30 | 31 | // 只有当所有必要数据都存在时才添加适配器 32 | if (!string.IsNullOrEmpty(deviceId) && !string.IsNullOrEmpty(name)) 33 | { 34 | var isConnected = false; 35 | try 36 | { 37 | // 尝试检查连接状态 (NetConnectionStatus=2表示已连接) 38 | var status = obj["NetConnectionStatus"]; 39 | isConnected = status != null && Convert.ToInt32(status) == 2; 40 | } 41 | catch 42 | { 43 | // 如果无法确定连接状态,则默认为已连接 44 | isConnected = true; 45 | } 46 | 47 | var adapter = new NetworkAdapter(deviceId, name, description) 48 | { 49 | IsConnected = isConnected 50 | }; 51 | adapters.Add(adapter); 52 | 53 | // 获取配置信息 54 | LoadNetworkAdapterConfig(adapter); 55 | } 56 | } 57 | catch (Exception ex) 58 | { 59 | Debug.WriteLine($"处理单个网络适配器时出错: {ex.Message}"); 60 | } 61 | } 62 | 63 | // 如果没有找到网络适配器,尝试备用方法 64 | if (adapters.Count == 0) 65 | using (var searcher = 66 | new ManagementObjectSearcher( 67 | "SELECT * FROM Win32_NetworkAdapterConfiguration WHERE IPEnabled = True")) 68 | { 69 | foreach (var obj in searcher.Get()) 70 | try 71 | { 72 | var index = obj["Index"]?.ToString() ?? ""; 73 | var description = obj["Description"]?.ToString() ?? ""; 74 | 75 | if (!string.IsNullOrEmpty(index) && !string.IsNullOrEmpty(description)) 76 | { 77 | var adapter = new NetworkAdapter(index, description, description); 78 | adapter.IsConnected = true; 79 | 80 | // 获取 DNS 服务器 81 | if (obj["DNSServerSearchOrder"] is string[] dnsServers && dnsServers.Length > 0) 82 | foreach (var dns in dnsServers) 83 | try 84 | { 85 | adapter.DnsServers.Add(IPAddress.Parse(dns)); 86 | } 87 | catch 88 | { 89 | } 90 | 91 | adapters.Add(adapter); 92 | } 93 | } 94 | catch (Exception ex) 95 | { 96 | Debug.WriteLine($"处理备用网络配置时出错: {ex.Message}"); 97 | } 98 | } 99 | } 100 | catch (Exception ex) 101 | { 102 | MessageBox.Show($"获取网络适配器时出错: {ex.Message}\n\n这可能是由于权限不足或WMI服务问题导致的。\n请确保以管理员身份运行程序。", 103 | "错误", MessageBoxButton.OK, MessageBoxImage.Error); 104 | 105 | Debug.WriteLine($"获取网络适配器时出错: {ex.Message}"); 106 | Debug.WriteLine($"异常堆栈: {ex.StackTrace}"); 107 | } 108 | 109 | return adapters; 110 | } 111 | 112 | // 加载网络适配器配置 113 | private void LoadNetworkAdapterConfig(NetworkAdapter adapter) 114 | { 115 | try 116 | { 117 | using (var searcher = new ManagementObjectSearcher( 118 | $"SELECT * FROM Win32_NetworkAdapterConfiguration WHERE Index = {adapter.Id}")) 119 | { 120 | foreach (var obj in searcher.Get()) 121 | { 122 | try 123 | { 124 | adapter.IsDhcpEnabled = Convert.ToBoolean(obj["DHCPEnabled"] ?? false); 125 | 126 | // 获取 DNS 服务器 127 | if (obj["DNSServerSearchOrder"] is string[] dnsServers && dnsServers.Length > 0) 128 | foreach (var dns in dnsServers) 129 | try 130 | { 131 | adapter.DnsServers.Add(IPAddress.Parse(dns)); 132 | } 133 | catch 134 | { 135 | } 136 | } 137 | catch (Exception ex) 138 | { 139 | Debug.WriteLine($"读取网络适配器配置属性时出错: {ex.Message}"); 140 | } 141 | 142 | break; // 只处理第一个匹配的配置 143 | } 144 | } 145 | } 146 | catch (Exception ex) 147 | { 148 | Debug.WriteLine($"加载网络适配器配置时出错: {ex.Message}"); 149 | } 150 | } 151 | 152 | // 其他方法保持不变... 153 | // 设置 DNS 服务器 154 | public async Task SetDnsServersAsync(NetworkAdapter adapter, DnsServer dnsServer) 155 | { 156 | return await Task.Run(() => SetDnsServers(adapter, dnsServer)); 157 | } 158 | 159 | private OperationResult SetDnsServers(NetworkAdapter adapter, DnsServer dnsServer) 160 | { 161 | try 162 | { 163 | using (var searcher = new ManagementObjectSearcher( 164 | $"SELECT * FROM Win32_NetworkAdapterConfiguration WHERE Index = {adapter.Id}")) 165 | { 166 | foreach (ManagementObject obj in searcher.Get()) 167 | { 168 | // 准备 DNS 服务器地址 169 | var dnsServerAddresses = new List(); 170 | dnsServerAddresses.Add(dnsServer.PrimaryIP.ToString()); 171 | 172 | if (dnsServer.SecondaryIP != null) dnsServerAddresses.Add(dnsServer.SecondaryIP.ToString()); 173 | 174 | // 设置 DNS 服务器 175 | var inParams = obj.GetMethodParameters("SetDNSServerSearchOrder"); 176 | inParams["DNSServerSearchOrder"] = dnsServerAddresses.ToArray(); 177 | var outParams = obj.InvokeMethod("SetDNSServerSearchOrder", inParams, null); 178 | 179 | var returnValue = (uint)outParams["ReturnValue"]; 180 | if (returnValue == 0) 181 | { 182 | // 更新适配器对象的DNS服务器列表 183 | adapter.DnsServers.Clear(); 184 | adapter.DnsServers.AddRange(dnsServerAddresses.Select(IPAddress.Parse)); 185 | adapter.IsDhcpEnabled = false; 186 | 187 | return new OperationResult 188 | { 189 | Success = true, 190 | Message = $"已成功将 {adapter.Description} 的 DNS 服务器设置为 {dnsServer.Name}" 191 | }; 192 | } 193 | 194 | return new OperationResult 195 | { 196 | Success = false, 197 | Message = $"设置 DNS 服务器失败,错误代码: {returnValue}" 198 | }; 199 | } 200 | } 201 | 202 | return new OperationResult 203 | { 204 | Success = false, 205 | Message = $"未找到网络适配器 {adapter.Description}" 206 | }; 207 | } 208 | catch (Exception ex) 209 | { 210 | return new OperationResult 211 | { 212 | Success = false, 213 | Message = $"设置 DNS 服务器时出错: {ex.Message}" 214 | }; 215 | } 216 | } 217 | 218 | // 重置为 DHCP 219 | public async Task ResetToDhcpAsync(NetworkAdapter adapter) 220 | { 221 | return await Task.Run(() => ResetToDhcp(adapter)); 222 | } 223 | 224 | private OperationResult ResetToDhcp(NetworkAdapter adapter) 225 | { 226 | try 227 | { 228 | using (var searcher = new ManagementObjectSearcher( 229 | $"SELECT * FROM Win32_NetworkAdapterConfiguration WHERE Index = {adapter.Id}")) 230 | { 231 | foreach (ManagementObject obj in searcher.Get()) 232 | { 233 | // 设置 DNS 为自动获取 234 | var outParams = obj.InvokeMethod("SetDNSServerSearchOrder", new object[] { null }); 235 | var returnValue = (uint)outParams; 236 | 237 | if (returnValue == 0) 238 | { 239 | // 更新适配器对象 240 | adapter.DnsServers.Clear(); 241 | adapter.IsDhcpEnabled = true; 242 | 243 | return new OperationResult 244 | { 245 | Success = true, 246 | Message = $"已成功将 {adapter.Description} 的 DNS 设置为自动获取 (DHCP)" 247 | }; 248 | } 249 | 250 | return new OperationResult 251 | { 252 | Success = false, 253 | Message = $"重置为 DHCP 失败,错误代码: {returnValue}" 254 | }; 255 | } 256 | } 257 | 258 | return new OperationResult 259 | { 260 | Success = false, 261 | Message = $"未找到网络适配器 {adapter.Description}" 262 | }; 263 | } 264 | catch (Exception ex) 265 | { 266 | return new OperationResult 267 | { 268 | Success = false, 269 | Message = $"重置为 DHCP 时出错: {ex.Message}" 270 | }; 271 | } 272 | } 273 | } 274 | 275 | public class OperationResult 276 | { 277 | public bool Success { get; set; } 278 | public string Message { get; set; } 279 | } -------------------------------------------------------------------------------- /Services/DnsTestService.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Net; 3 | using System.Net.NetworkInformation; 4 | using DnsClient; 5 | using DNSSpeedTester.Models; 6 | 7 | namespace DNSSpeedTester.Services; 8 | 9 | public class DnsTestService 10 | { 11 | // 获取常用测试域名列表 - 保持不变 12 | public static List GetCommonTestDomains() 13 | { 14 | return new List 15 | { 16 | // 国内网站 17 | new("百度", "www.baidu.com", "国内"), 18 | new("淘宝", "www.taobao.com", "国内"), 19 | new("腾讯", "www.qq.com", "国内"), 20 | new("网易", "www.163.com", "国内"), 21 | new("哔哩哔哩", "www.bilibili.com", "国内"), 22 | new("知乎", "www.zhihu.com", "国内"), 23 | 24 | // 国际网站 25 | new("谷歌", "www.google.com", "国际"), 26 | new("YouTube", "www.youtube.com", "国际"), 27 | new("微软", "www.microsoft.com", "国际"), 28 | new("亚马逊", "www.amazon.com", "国际"), 29 | new("Facebook", "www.facebook.com", "国际"), 30 | new("Twitter", "twitter.com", "国际"), 31 | 32 | // CDN/云服务 33 | new("CloudFlare", "www.cloudflare.com", "CDN/云服务"), 34 | new("Akamai", "www.akamai.com", "CDN/云服务"), 35 | new("AWS", "aws.amazon.com", "CDN/云服务"), 36 | new("Azure", "azure.microsoft.com", "CDN/云服务"), 37 | 38 | // 随机域名 (避免缓存) 39 | new("随机域名", GenerateRandomDomain(), "特殊测试") 40 | }; 41 | } 42 | 43 | 44 | // 生成随机域名 - 保持不变 45 | private static string GenerateRandomDomain() 46 | { 47 | return $"{Guid.NewGuid().ToString("N")[..8]}.example.com"; 48 | } 49 | 50 | // 测试本机到DNS服务器的延迟 - 对外接口保持不变 51 | public async Task TestDnsServerAsync(DnsServer server, string testDomain) 52 | { 53 | // 保存对当前server的引用 54 | var serverToTest = server; 55 | 56 | try 57 | { 58 | serverToTest.Status = "测试中..."; 59 | serverToTest.Latency = null; 60 | 61 | // 使用增强的测试方法 62 | var finalLatency = await EnhancedDnsSpeedTest(serverToTest.PrimaryIP, testDomain); 63 | 64 | if (finalLatency.HasValue) 65 | { 66 | serverToTest.Latency = finalLatency.Value; 67 | serverToTest.Status = "成功"; 68 | serverToTest.StatusDetail = $"DNS响应时间: {finalLatency}ms"; 69 | } 70 | else 71 | { 72 | serverToTest.Status = "超时"; 73 | serverToTest.StatusDetail = "DNS查询失败或超时"; 74 | serverToTest.Latency = null; 75 | } 76 | 77 | return serverToTest; 78 | } 79 | catch (Exception ex) 80 | { 81 | serverToTest.Status = "错误"; 82 | serverToTest.StatusDetail = ex.Message; 83 | serverToTest.Latency = null; 84 | return serverToTest; 85 | } 86 | } 87 | 88 | // 增强版DNS速度测试 - 完全重写的测试方法 89 | private async Task EnhancedDnsSpeedTest(IPAddress serverIP, string testDomain) 90 | { 91 | // 创建测试结果列表 92 | var latencies = new List(); 93 | 94 | // 1. TCP测试 - 较慢但更准确 95 | var tcpLatency = await MeasureTcpDnsLatency(serverIP, testDomain); 96 | if (tcpLatency.HasValue) latencies.Add(tcpLatency.Value); 97 | 98 | // 2. 强制刷新缓存的UDP测试 99 | var udpLatency = await MeasureUdpDnsLatency(serverIP, testDomain); 100 | if (udpLatency.HasValue) latencies.Add(udpLatency.Value); 101 | 102 | // 3. 随机子域名查询测试 103 | var randomLatency = await MeasureRandomDnsLatency(serverIP); 104 | if (randomLatency.HasValue) latencies.Add(randomLatency.Value); 105 | 106 | // 4. PING测试作为基线参考 107 | var pingLatency = await MeasurePingLatency(serverIP); 108 | if (pingLatency.HasValue) latencies.Add(pingLatency.Value); 109 | 110 | // 如果没有有效结果,返回null 111 | if (latencies.Count == 0) return null; 112 | 113 | // 结果处理策略 114 | // - 如果只有1个结果,直接返回 115 | // - 如果有2个结果,取较大值 116 | // - 如果有3个或更多结果,排序后取中值,避免异常值 117 | if (latencies.Count == 1) return latencies[0]; 118 | 119 | if (latencies.Count == 2) 120 | // 取较大值能更好反映实际网络延迟 121 | return Math.Max(latencies[0], latencies[1]); 122 | 123 | // 排序并取中值 124 | latencies.Sort(); 125 | return latencies[latencies.Count / 2]; 126 | } 127 | 128 | // TCP DNS查询测试 - 强制建立连接,更准确反映网络路径 129 | private async Task MeasureTcpDnsLatency(IPAddress serverIP, string testDomain) 130 | { 131 | try 132 | { 133 | var lookupClient = new LookupClient(new LookupClientOptions 134 | { 135 | UseCache = false, 136 | Timeout = TimeSpan.FromSeconds(5), 137 | Retries = 0, 138 | // ServerIPs = new[] { serverIP }, 139 | UseTcpOnly = true, // 强制使用TCP 140 | EnableAuditTrail = false 141 | }); 142 | 143 | // 预热连接 144 | try 145 | { 146 | await lookupClient.QueryAsync("www.example.com", QueryType.A); 147 | } 148 | catch 149 | { 150 | /* 忽略预热错误 */ 151 | } 152 | 153 | // 短暂延迟确保预热完成 154 | await Task.Delay(50); 155 | 156 | // 执行多次测试并取均值 157 | var validTests = 0; 158 | var totalLatency = 0; 159 | 160 | for (var i = 0; i < 3; i++) 161 | { 162 | var stopwatch = Stopwatch.StartNew(); 163 | try 164 | { 165 | var result = await lookupClient.QueryAsync(testDomain, QueryType.A); 166 | stopwatch.Stop(); 167 | 168 | if (!result.HasError) 169 | { 170 | validTests++; 171 | totalLatency += (int)stopwatch.ElapsedMilliseconds; 172 | } 173 | } 174 | catch 175 | { 176 | /* 继续测试 */ 177 | } 178 | 179 | // 间隔一段时间 180 | await Task.Delay(200); 181 | } 182 | 183 | if (validTests > 0) return totalLatency / validTests; 184 | } 185 | catch 186 | { 187 | /* 返回null */ 188 | } 189 | 190 | return null; 191 | } 192 | 193 | // UDP DNS查询测试 - 更符合实际用户场景 194 | private async Task MeasureUdpDnsLatency(IPAddress serverIP, string testDomain) 195 | { 196 | try 197 | { 198 | var lookupClient = new LookupClient(new LookupClientOptions 199 | { 200 | UseCache = false, 201 | Timeout = TimeSpan.FromSeconds(3), 202 | Retries = 0, 203 | //ServerIPs = new[] { serverIP }, 204 | UseTcpOnly = false, // 使用UDP 205 | EnableAuditTrail = false 206 | }); 207 | 208 | // 不进行预热,直接测试真实情况 209 | 210 | // 使用不常见的记录类型,降低缓存命中率 211 | var queryTypes = new[] { QueryType.AAAA, QueryType.MX, QueryType.TXT }; 212 | 213 | var validTests = 0; 214 | var totalLatency = 0; 215 | 216 | foreach (var queryType in queryTypes) 217 | { 218 | var stopwatch = Stopwatch.StartNew(); 219 | try 220 | { 221 | var result = await lookupClient.QueryAsync(testDomain, queryType); 222 | stopwatch.Stop(); 223 | 224 | validTests++; 225 | totalLatency += (int)stopwatch.ElapsedMilliseconds; 226 | } 227 | catch 228 | { 229 | /* 继续测试 */ 230 | } 231 | 232 | // 间隔一段时间 233 | await Task.Delay(100); 234 | } 235 | 236 | if (validTests > 0) return totalLatency / validTests; 237 | } 238 | catch 239 | { 240 | /* 返回null */ 241 | } 242 | 243 | return null; 244 | } 245 | 246 | // 随机子域名查询测试 - 完全避开任何缓存 247 | private async Task MeasureRandomDnsLatency(IPAddress serverIP) 248 | { 249 | try 250 | { 251 | var lookupClient = new LookupClient(new LookupClientOptions 252 | { 253 | UseCache = false, 254 | Timeout = TimeSpan.FromSeconds(3), 255 | Retries = 0, 256 | //ServerIPs = new[] { serverIP }, 257 | UseTcpOnly = false, 258 | EnableAuditTrail = false 259 | }); 260 | 261 | var validTests = 0; 262 | var totalLatency = 0; 263 | 264 | // 测试3次不同的随机域名 265 | for (var i = 0; i < 3; i++) 266 | { 267 | // 每次生成一个新的随机域名 268 | var randomDomain = GenerateRandomDomain(); 269 | 270 | var stopwatch = Stopwatch.StartNew(); 271 | try 272 | { 273 | var result = await lookupClient.QueryAsync(randomDomain, QueryType.A); 274 | stopwatch.Stop(); 275 | 276 | // 注意:即使查询不到结果也算有效测试,因为这是期望的 277 | validTests++; 278 | totalLatency += (int)stopwatch.ElapsedMilliseconds; 279 | } 280 | catch 281 | { 282 | /* 继续测试 */ 283 | } 284 | 285 | // 间隔一段时间 286 | await Task.Delay(100); 287 | } 288 | 289 | if (validTests > 0) return totalLatency / validTests; 290 | } 291 | catch 292 | { 293 | /* 返回null */ 294 | } 295 | 296 | return null; 297 | } 298 | 299 | // PING测试 - 提供基线网络延迟参考 300 | private async Task MeasurePingLatency(IPAddress serverIP) 301 | { 302 | try 303 | { 304 | using (var ping = new Ping()) 305 | { 306 | var validPings = 0; 307 | var totalPingLatency = 0; 308 | var pingsToRun = 4; 309 | 310 | for (var i = 0; i < pingsToRun; i++) 311 | try 312 | { 313 | var reply = await ping.SendPingAsync(serverIP, 3000, new byte[32]); 314 | 315 | if (reply.Status == IPStatus.Success) 316 | { 317 | validPings++; 318 | totalPingLatency += (int)reply.RoundtripTime; 319 | } 320 | 321 | // 短暂休眠,避免过快发送ping请求 322 | await Task.Delay(100); 323 | } 324 | catch 325 | { 326 | /* 忽略单次ping失败 */ 327 | } 328 | 329 | if (validPings > 0) 330 | // Ping延迟通常比DNS查询低,所以乘以1.2作为修正 331 | return (int)(totalPingLatency / validPings * 1.2); 332 | } 333 | } 334 | catch 335 | { 336 | /* 返回null */ 337 | } 338 | 339 | return null; 340 | } 341 | 342 | // 获取常见公共DNS服务器列表 - 保持不变 343 | public static DnsServer[] GetCommonDnsServers() 344 | { 345 | return new[] 346 | { 347 | new DnsServer("Google DNS", "8.8.8.8", "8.8.4.4"), 348 | new DnsServer("Cloudflare DNS", "1.1.1.1", "1.0.0.1"), 349 | new DnsServer("Quad9", "9.9.9.9", "149.112.112.112"), 350 | new DnsServer("OpenDNS", "208.67.222.222", "208.67.220.220"), 351 | new DnsServer("AdGuard DNS", "94.140.14.14", "94.140.15.15"), 352 | new DnsServer("阿里 DNS", "223.5.5.5", "223.6.6.6"), 353 | new DnsServer("DNSPod", "119.29.29.29", "182.254.116.116"), 354 | new DnsServer("114 DNS", "114.114.114.114", "114.114.115.115"), 355 | new DnsServer("腾讯 DNS", "119.28.28.28", "182.254.118.118"), 356 | new DnsServer("百度 DNS", "180.76.76.76"), 357 | new DnsServer("360 DNS", "101.226.4.6", "218.30.118.6"), 358 | new DnsServer("CNNIC SDNS", "1.2.4.8", "210.2.4.8"), 359 | new DnsServer("DNS PAI", "101.226.4.6", "218.30.118.6"), 360 | new DnsServer("火山引擎 DNS", "180.184.1.1", "180.184.2.2") 361 | }; 362 | } 363 | } -------------------------------------------------------------------------------- /Services/IPAddressConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using System.Text.Json; 3 | using System.Text.Json.Serialization; 4 | 5 | namespace DNSSpeedTester.Services; 6 | 7 | public class IpAddressConverter : JsonConverter 8 | { 9 | public override IPAddress? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 10 | { 11 | var ipString = reader.GetString(); 12 | return string.IsNullOrEmpty(ipString) ? null : IPAddress.Parse(ipString); 13 | } 14 | 15 | public override void Write(Utf8JsonWriter writer, IPAddress value, JsonSerializerOptions options) 16 | { 17 | writer.WriteStringValue(value?.ToString()); 18 | } 19 | } -------------------------------------------------------------------------------- /ViewModels/MainViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.ObjectModel; 2 | using System.Net; 3 | using System.Windows; 4 | using System.Windows.Input; 5 | using DNSSpeedTester.Helpers; 6 | using DNSSpeedTester.Models; 7 | using DNSSpeedTester.Services; 8 | 9 | namespace DNSSpeedTester.ViewModels; 10 | 11 | public class MainViewModel : ViewModelBase 12 | { 13 | // 服务 14 | private readonly DataPersistenceService _dataPersistenceService = new(); 15 | private readonly DnsSettingService _dnsSettingService = new(); 16 | private readonly DnsTestService _dnsTestService = new(); 17 | 18 | private bool _isBusy; 19 | 20 | // 新 DNS 条目输入 21 | private string _newDnsName; 22 | 23 | private string _newPrimaryDns; 24 | 25 | private string _newSecondaryDns; 26 | 27 | // 新测试域名输入 28 | private string _newTestDomainName; 29 | 30 | private string _newTestDomainValue; 31 | 32 | // 选中项 33 | private DnsServer? _selectedDnsServer; 34 | 35 | private NetworkAdapter? _selectedNetworkAdapter; 36 | 37 | private TestDomain? _selectedTestDomain; 38 | 39 | // 状态信息 40 | private string _statusMessage; 41 | 42 | // 测试结果 43 | private int _testedCount, _totalCount; 44 | 45 | 46 | // 构造函数 47 | public MainViewModel() 48 | { 49 | // 初始化集合 50 | DnsServers = new ObservableCollection(); 51 | NetworkAdapters = new ObservableCollection(); 52 | TestDomains = new ObservableCollection(); 53 | 54 | // 初始化命令 55 | StartTestCommand = new RelayCommand(async obj => await StartDnsTest(), _ => !IsBusy); 56 | SetDnsCommand = new RelayCommand(async obj => await SetDns(), 57 | _ => !IsBusy && SelectedDnsServer != null && SelectedNetworkAdapter != null); 58 | ResetToDhcpCommand = new RelayCommand(async obj => await ResetToDhcp(), 59 | _ => !IsBusy && SelectedNetworkAdapter != null); 60 | AddCustomDnsCommand = new RelayCommand(obj => AddCustomDns(), _ => !IsBusy && CanAddCustomDns()); 61 | RemoveCustomDnsCommand = new RelayCommand(obj => RemoveCustomDns(obj as DnsServer), _ => true); 62 | RunNetworkDiagnosticsCommand = new RelayCommand(_ => NetworkDiagnostics.RunDiagnostics(), _ => true); 63 | 64 | // 测试域名命令 65 | AddTestDomainCommand = new RelayCommand(obj => AddCustomTestDomain(), _ => !IsBusy && CanAddTestDomain()); 66 | RemoveTestDomainCommand = new RelayCommand(obj => RemoveTestDomain(obj as TestDomain), _ => true); 67 | RefreshRandomDomainCommand = new RelayCommand(obj => RefreshRandomTestDomain(), _ => !IsBusy); 68 | 69 | // 初始化数据 70 | LoadData(); 71 | } 72 | 73 | // 添加诊断命令 74 | public ICommand RunNetworkDiagnosticsCommand { get; } 75 | 76 | // 集合 77 | public ObservableCollection DnsServers { get; } 78 | public ObservableCollection NetworkAdapters { get; } 79 | public ObservableCollection TestDomains { get; } 80 | 81 | public DnsServer? SelectedDnsServer 82 | { 83 | get => _selectedDnsServer; 84 | set => SetProperty(ref _selectedDnsServer, value); 85 | } 86 | 87 | public NetworkAdapter? SelectedNetworkAdapter 88 | { 89 | get => _selectedNetworkAdapter; 90 | set => SetProperty(ref _selectedNetworkAdapter, value); 91 | } 92 | 93 | public TestDomain? SelectedTestDomain 94 | { 95 | get => _selectedTestDomain; 96 | set => SetProperty(ref _selectedTestDomain, value); 97 | } 98 | 99 | public string NewTestDomainName 100 | { 101 | get => _newTestDomainName; 102 | set 103 | { 104 | SetProperty(ref _newTestDomainName, value); 105 | CommandManager.InvalidateRequerySuggested(); 106 | } 107 | } 108 | 109 | public string NewTestDomainValue 110 | { 111 | get => _newTestDomainValue; 112 | set 113 | { 114 | SetProperty(ref _newTestDomainValue, value); 115 | CommandManager.InvalidateRequerySuggested(); 116 | } 117 | } 118 | 119 | public string NewDnsName 120 | { 121 | get => _newDnsName; 122 | set 123 | { 124 | SetProperty(ref _newDnsName, value); 125 | CommandManager.InvalidateRequerySuggested(); 126 | } 127 | } 128 | 129 | public string NewPrimaryDns 130 | { 131 | get => _newPrimaryDns; 132 | set 133 | { 134 | SetProperty(ref _newPrimaryDns, value); 135 | CommandManager.InvalidateRequerySuggested(); 136 | } 137 | } 138 | 139 | public string NewSecondaryDns 140 | { 141 | get => _newSecondaryDns; 142 | set 143 | { 144 | SetProperty(ref _newSecondaryDns, value); 145 | CommandManager.InvalidateRequerySuggested(); 146 | } 147 | } 148 | 149 | public string StatusMessage 150 | { 151 | get => _statusMessage; 152 | set => SetProperty(ref _statusMessage, value); 153 | } 154 | 155 | public bool IsBusy 156 | { 157 | get => _isBusy; 158 | set 159 | { 160 | SetProperty(ref _isBusy, value); 161 | ((RelayCommand)StartTestCommand).RaiseCanExecuteChanged(); 162 | ((RelayCommand)SetDnsCommand).RaiseCanExecuteChanged(); 163 | ((RelayCommand)ResetToDhcpCommand).RaiseCanExecuteChanged(); 164 | ((RelayCommand)AddCustomDnsCommand).RaiseCanExecuteChanged(); 165 | ((RelayCommand)AddTestDomainCommand).RaiseCanExecuteChanged(); 166 | ((RelayCommand)RefreshRandomDomainCommand).RaiseCanExecuteChanged(); 167 | } 168 | } 169 | 170 | public int TestedCount 171 | { 172 | get => _testedCount; 173 | set => SetProperty(ref _testedCount, value); 174 | } 175 | 176 | public int TotalCount 177 | { 178 | get => _totalCount; 179 | set => SetProperty(ref _totalCount, value); 180 | } 181 | 182 | // 命令 183 | public ICommand StartTestCommand { get; } 184 | public ICommand SetDnsCommand { get; } 185 | public ICommand ResetToDhcpCommand { get; } 186 | public ICommand AddCustomDnsCommand { get; } 187 | public ICommand RemoveCustomDnsCommand { get; } 188 | public ICommand AddTestDomainCommand { get; } 189 | public ICommand RemoveTestDomainCommand { get; } 190 | public ICommand RefreshRandomDomainCommand { get; } 191 | 192 | // 加载数据 193 | private void LoadData() 194 | { 195 | try 196 | { 197 | IsBusy = true; 198 | StatusMessage = "正在加载数据..."; 199 | 200 | // 加载 DNS 服务器 201 | DnsServers.Clear(); 202 | var commonServers = DnsTestService.GetCommonDnsServers(); 203 | var customServers = _dataPersistenceService.LoadCustomDnsServers(); 204 | 205 | foreach (var server in commonServers.Concat(customServers)) DnsServers.Add(server); 206 | 207 | // 加载网络适配器 208 | NetworkAdapters.Clear(); 209 | var adapters = _dnsSettingService.GetNetworkAdapters() 210 | .Where(a => a.IsConnected) 211 | .ToList(); 212 | 213 | foreach (var adapter in adapters) NetworkAdapters.Add(adapter); 214 | 215 | if (NetworkAdapters.Count > 0) SelectedNetworkAdapter = NetworkAdapters[0]; 216 | 217 | // 加载测试域名 218 | LoadTestDomains(); 219 | 220 | StatusMessage = $"已加载 {DnsServers.Count} 个 DNS 服务器和 {NetworkAdapters.Count} 个网络适配器"; 221 | } 222 | catch (Exception ex) 223 | { 224 | StatusMessage = $"加载数据时出错: {ex.Message}"; 225 | } 226 | finally 227 | { 228 | IsBusy = false; 229 | } 230 | } 231 | 232 | // 加载测试域名 233 | private void LoadTestDomains() 234 | { 235 | TestDomains.Clear(); 236 | 237 | // 加载内置的测试域名 238 | var commonDomains = DnsTestService.GetCommonTestDomains(); 239 | 240 | // 加载自定义测试域名 241 | var customDomains = _dataPersistenceService.LoadCustomTestDomains(); 242 | 243 | foreach (var domain in commonDomains.Concat(customDomains)) TestDomains.Add(domain); 244 | 245 | // 默认选中第一个域名 246 | if (TestDomains.Count > 0) 247 | // 默认选择百度 248 | SelectedTestDomain = TestDomains.FirstOrDefault(d => d.Domain == "www.baidu.com") ?? TestDomains[0]; 249 | } 250 | 251 | // 开始 DNS 测试 252 | private async Task StartDnsTest() 253 | { 254 | if (SelectedTestDomain == null) 255 | { 256 | StatusMessage = "请先选择测试域名"; 257 | return; 258 | } 259 | 260 | try 261 | { 262 | IsBusy = true; 263 | TotalCount = DnsServers.Count; 264 | TestedCount = 0; 265 | StatusMessage = $"开始测试 DNS 服务器 (测试域名: {SelectedTestDomain.Domain})..."; 266 | 267 | // 重要:克隆当前的DNS服务器列表,以保留原始顺序和引用 268 | var dnsServersList = DnsServers.ToList(); 269 | 270 | // 为每个DNS服务器准备测试 271 | foreach (var server in dnsServersList) 272 | { 273 | server.Status = "测试中..."; 274 | server.Latency = null; 275 | } 276 | 277 | // 为每个服务器创建测试任务 278 | var tasks = new Dictionary>(); 279 | foreach (var server in dnsServersList) 280 | { 281 | // 为每个服务器创建一个独立的测试任务,使用选定的测试域名 282 | var task = _dnsTestService.TestDnsServerAsync(server, SelectedTestDomain.Domain); 283 | tasks.Add(server, task); 284 | } 285 | 286 | // 等待所有任务完成 287 | while (tasks.Count > 0) 288 | { 289 | // 等待任何一个任务完成 290 | var completedTask = await Task.WhenAny(tasks.Values); 291 | 292 | // 找出完成的任务对应的DNS服务器 293 | var serverEntry = tasks.FirstOrDefault(x => x.Value == completedTask); 294 | var server = serverEntry.Key; 295 | var task = serverEntry.Value; 296 | 297 | // 从待处理任务中删除 298 | tasks.Remove(server); 299 | 300 | try 301 | { 302 | // 获取测试结果 303 | var testedServer = await task; 304 | 305 | // 手动更新UI集合中的对象属性 306 | var serverInCollection = DnsServers.FirstOrDefault(s => 307 | s.Name == testedServer.Name && 308 | s.PrimaryIP.ToString() == testedServer.PrimaryIP.ToString()); 309 | 310 | if (serverInCollection != null) 311 | { 312 | serverInCollection.Latency = testedServer.Latency; 313 | serverInCollection.Status = testedServer.Status; 314 | serverInCollection.StatusDetail = testedServer.StatusDetail; 315 | } 316 | } 317 | catch (Exception ex) 318 | { 319 | // 处理单个测试的异常 320 | if (server != null) 321 | { 322 | server.Status = "错误"; 323 | server.StatusDetail = ex.Message; 324 | server.Latency = null; 325 | } 326 | } 327 | 328 | // 更新进度 329 | TestedCount++; 330 | StatusMessage = $"测试进度: {TestedCount}/{TotalCount}"; 331 | } 332 | 333 | // 按延迟排序 334 | var sortedServers = new List( 335 | DnsServers.OrderBy(s => s.Latency.HasValue ? s.Latency.Value : int.MaxValue) 336 | ); 337 | 338 | // 更新UI列表 339 | Application.Current.Dispatcher.Invoke(() => 340 | { 341 | DnsServers.Clear(); 342 | foreach (var server in sortedServers) DnsServers.Add(server); 343 | 344 | // 选择延迟最低的服务器 345 | SelectedDnsServer = DnsServers.FirstOrDefault(s => s.Latency.HasValue); 346 | }); 347 | 348 | StatusMessage = $"DNS 测速完成,测量结果为本机到各 DNS 服务器解析 {SelectedTestDomain.Domain} 的往返延迟"; 349 | } 350 | catch (Exception ex) 351 | { 352 | StatusMessage = $"测试中发生错误: {ex.Message}"; 353 | } 354 | finally 355 | { 356 | IsBusy = false; 357 | } 358 | } 359 | 360 | // 设置 DNS 361 | private async Task SetDns() 362 | { 363 | if (SelectedDnsServer == null || SelectedNetworkAdapter == null) 364 | { 365 | StatusMessage = "请先选择 DNS 服务器和网络适配器"; 366 | return; 367 | } 368 | 369 | try 370 | { 371 | IsBusy = true; 372 | StatusMessage = $"正在设置 DNS 服务器: {SelectedDnsServer.Name}..."; 373 | 374 | var result = await _dnsSettingService.SetDnsServersAsync(SelectedNetworkAdapter, SelectedDnsServer); 375 | StatusMessage = result.Message; 376 | } 377 | catch (Exception ex) 378 | { 379 | StatusMessage = $"设置 DNS 时出错: {ex.Message}"; 380 | } 381 | finally 382 | { 383 | IsBusy = false; 384 | } 385 | } 386 | 387 | // 恢复为 DHCP 388 | private async Task ResetToDhcp() 389 | { 390 | if (SelectedNetworkAdapter == null) 391 | { 392 | StatusMessage = "请先选择网络适配器"; 393 | return; 394 | } 395 | 396 | try 397 | { 398 | IsBusy = true; 399 | StatusMessage = "正在恢复为自动获取 DNS..."; 400 | 401 | var result = await _dnsSettingService.ResetToDhcpAsync(SelectedNetworkAdapter); 402 | StatusMessage = result.Message; 403 | } 404 | catch (Exception ex) 405 | { 406 | StatusMessage = $"恢复 DHCP 时出错: {ex.Message}"; 407 | } 408 | finally 409 | { 410 | IsBusy = false; 411 | } 412 | } 413 | 414 | // 添加自定义 DNS 415 | private void AddCustomDns() 416 | { 417 | try 418 | { 419 | if (!CanAddCustomDns()) 420 | { 421 | StatusMessage = "请输入有效的 DNS 名称和主 DNS 服务器地址"; 422 | return; 423 | } 424 | 425 | var newDns = new DnsServer( 426 | NewDnsName.Trim(), 427 | NewPrimaryDns.Trim(), 428 | !string.IsNullOrWhiteSpace(NewSecondaryDns) ? NewSecondaryDns.Trim() : null, 429 | true); 430 | 431 | DnsServers.Add(newDns); 432 | 433 | // 清空输入字段 434 | NewDnsName = string.Empty; 435 | NewPrimaryDns = string.Empty; 436 | NewSecondaryDns = string.Empty; 437 | 438 | // 保存自定义 DNS 列表 439 | SaveCustomDnsServers(); 440 | 441 | StatusMessage = $"已添加自定义 DNS 服务器: {newDns.Name}"; 442 | } 443 | catch (Exception ex) 444 | { 445 | StatusMessage = $"添加自定义 DNS 时出错: {ex.Message}"; 446 | } 447 | } 448 | 449 | // 删除自定义 DNS 450 | private void RemoveCustomDns(DnsServer server) 451 | { 452 | if (server == null || !server.IsCustom) return; 453 | 454 | var result = MessageBox.Show( 455 | $"确定要删除自定义 DNS 服务器 '{server.Name}' 吗?", 456 | "删除确认", 457 | MessageBoxButton.YesNo, 458 | MessageBoxImage.Question); 459 | 460 | if (result == MessageBoxResult.Yes) 461 | { 462 | DnsServers.Remove(server); 463 | SaveCustomDnsServers(); 464 | StatusMessage = $"已删除自定义 DNS 服务器: {server.Name}"; 465 | } 466 | } 467 | 468 | // 添加自定义测试域名 469 | private void AddCustomTestDomain() 470 | { 471 | try 472 | { 473 | if (!CanAddTestDomain()) 474 | { 475 | StatusMessage = "请输入有效的域名名称和值"; 476 | return; 477 | } 478 | 479 | var newDomain = new TestDomain( 480 | NewTestDomainName.Trim(), 481 | NewTestDomainValue.Trim(), 482 | "自定义", 483 | true); 484 | 485 | TestDomains.Add(newDomain); 486 | SelectedTestDomain = newDomain; 487 | 488 | // 清空输入字段 489 | NewTestDomainName = string.Empty; 490 | NewTestDomainValue = string.Empty; 491 | 492 | // 保存自定义测试域名 493 | SaveCustomTestDomains(); 494 | 495 | StatusMessage = $"已添加自定义测试域名: {newDomain.Name} [{newDomain.Domain}]"; 496 | } 497 | catch (Exception ex) 498 | { 499 | StatusMessage = $"添加自定义测试域名时出错: {ex.Message}"; 500 | } 501 | } 502 | 503 | // 删除测试域名 504 | private void RemoveTestDomain(TestDomain domain) 505 | { 506 | if (domain == null || !domain.IsCustom) return; 507 | 508 | var result = MessageBox.Show( 509 | $"确定要删除自定义测试域名 '{domain.Name} [{domain.Domain}]' 吗?", 510 | "删除确认", MessageBoxButton.YesNo, 511 | MessageBoxImage.Question); 512 | 513 | if (result == MessageBoxResult.Yes) 514 | { 515 | TestDomains.Remove(domain); 516 | SaveCustomTestDomains(); 517 | StatusMessage = $"已删除自定义测试域名: {domain.Name}"; 518 | 519 | // 如果删除的是当前选中的域名,则选择默认域名 520 | if (SelectedTestDomain == domain) 521 | SelectedTestDomain = TestDomains.FirstOrDefault(d => d.Domain == "www.baidu.com") ?? 522 | TestDomains.FirstOrDefault(); 523 | } 524 | } 525 | 526 | // 刷新随机测试域名 527 | private void RefreshRandomTestDomain() 528 | { 529 | try 530 | { 531 | var randomDomain = TestDomains.FirstOrDefault(d => d.Category == "特殊测试"); 532 | if (randomDomain != null) 533 | { 534 | var randomPart = Guid.NewGuid().ToString().Replace("-", "").Substring(0, 8); 535 | var newDomainValue = $"{randomPart}.example.com"; 536 | 537 | // 更新随机域名 538 | var index = TestDomains.IndexOf(randomDomain); 539 | TestDomains.Remove(randomDomain); 540 | 541 | var newRandomDomain = new TestDomain("随机域名", newDomainValue, "特殊测试"); 542 | TestDomains.Insert(index, newRandomDomain); 543 | 544 | if (SelectedTestDomain == randomDomain) SelectedTestDomain = newRandomDomain; 545 | 546 | StatusMessage = $"已刷新随机测试域名: {newDomainValue}"; 547 | } 548 | } 549 | catch (Exception ex) 550 | { 551 | StatusMessage = $"刷新随机域名时出错: {ex.Message}"; 552 | } 553 | } 554 | 555 | // 保存自定义 DNS 列表 556 | private void SaveCustomDnsServers() 557 | { 558 | try 559 | { 560 | var customServers = DnsServers.Where(s => s.IsCustom).ToList(); 561 | _dataPersistenceService.SaveCustomDnsServers(customServers); 562 | } 563 | catch (Exception ex) 564 | { 565 | StatusMessage = $"保存自定义 DNS 列表时出错: {ex.Message}"; 566 | } 567 | } 568 | 569 | // 保存自定义测试域名 570 | private void SaveCustomTestDomains() 571 | { 572 | try 573 | { 574 | var customDomains = TestDomains.Where(d => d.IsCustom).ToList(); 575 | _dataPersistenceService.SaveCustomTestDomains(customDomains); 576 | } 577 | catch (Exception ex) 578 | { 579 | StatusMessage = $"保存自定义测试域名列表时出错: {ex.Message}"; 580 | } 581 | } 582 | 583 | // 验证是否可以添加自定义 DNS 584 | private bool CanAddCustomDns() 585 | { 586 | if (string.IsNullOrWhiteSpace(NewDnsName) || string.IsNullOrWhiteSpace(NewPrimaryDns)) return false; 587 | 588 | try 589 | { 590 | IPAddress.Parse(NewPrimaryDns); 591 | 592 | if (!string.IsNullOrWhiteSpace(NewSecondaryDns)) IPAddress.Parse(NewSecondaryDns); 593 | 594 | return true; 595 | } 596 | catch 597 | { 598 | return false; 599 | } 600 | } 601 | 602 | // 验证是否可以添加自定义测试域名 603 | private bool CanAddTestDomain() 604 | { 605 | if (string.IsNullOrWhiteSpace(NewTestDomainName) || string.IsNullOrWhiteSpace(NewTestDomainValue)) return false; 606 | 607 | // 验证域名格式是否有效 608 | try 609 | { 610 | // 简单验证,接受任何非空值 611 | var domain = NewTestDomainValue.Trim(); 612 | return domain.Length > 0 && domain.Contains("."); 613 | } 614 | catch 615 | { 616 | return false; 617 | } 618 | } 619 | } -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "9.0.203", 4 | "rollForward": "latestMinor", 5 | "allowPrerelease": false 6 | } 7 | } --------------------------------------------------------------------------------