记录 Windows系统开启hyper-v ,部分端口被保留,导致端口不能使用而报错的问题

一、出现的问题

1、最近,部分客户在反馈,升级的过程中,偶尔出现升级的时候,开启IPC失败。观察了日志,是启动进程间通信的时候,服务的端口监听的时候,无法访问。这个问题很是诡异

img_v3_02sp_750b85e5-59be-41fc-a930-f493ae4c685g

 

img_v3_02sp_4351f120-738f-4b52-bc68-cb480ce4877g

 

二、问题排查

1、使用 “netstat -ano | findstr 端口” 的命令,发现该端口是没有进程占用的

image

 2、观察了我们获取可用端口的代码,是有判断端口是否被某些进程占用的过滤条件的,而且报错的不是在这个函数,而是已经拿到了端口后,做端口监听才抛出了如上的异常日志

  private bool IsPortInUse(int port)
        {
            var properties = IPGlobalProperties.GetIPGlobalProperties();
            IPEndPoint[] tcpListeners = properties.GetActiveTcpListeners();
            IPEndPoint[] udpListeners = properties.GetActiveUdpListeners();
            TcpConnectionInformation[] connections = properties.GetActiveTcpConnections();

            return tcpListeners.Any(p => p.Port == port)
                   || udpListeners.Any(p => p.Port == port)
                   || connections.Any(c => c.LocalEndPoint.Port == port);
        }

3、搜索网上的出现的相同的问题,大多都指向了 开启了 hyper-v之后,端口被系统保留了。

解决 Windows 10 端口被 Hyper-V 随机保留(占用)的问题 | 一个兆基

image

img_v3_02sp_59b87443-5bdb-4e44-a30e-302ce6d79f3g

 4、开启hyper-v之后,使用 “netsh int ipv4 show excludedportrange protocol=tcp” 的命令,确实可以看到,好多段的端口范围都被排除了。

也就是说,如果只判断了端口是否被占用,但是端口没有进程占用,但是获取的端口又在排除的范围之内,一旦把该排除的端口做端口监听,就会报错。

image

 三、解决问题

1、简单但是不一定正确的方法,可以在远程客户的机器上临时解决使用(需要管理员身份)

 运行 net stop winnat 停止 winnat 服务,然后再运行 net start winnat 启动 winnat 服务。

net stop winnat
net start winnat

2、重新设置TCP的动态端口范围(需要管理员身份),修改完需要重启电脑

netsh int ipv4 set dynamic tcp start=49152 num=16384
netsh int ipv6 set dynamic tcp start=49152 num=16384

3、在代码上动态过滤掉被占用或者被保留的端口

 internal class ComputePortService
 {

     public  int DefaultIpcPort { get; set; } = 18743;

     /// <summary>
     /// 获取有效的IPC端口
     /// </summary>
     /// <returns></returns>
     /// <exception cref="Exception"></exception>
     public int GetValidIpcPort()
     {
         //获取保留端口范围
         var excludedPortRange = GetExcludedPortRange();
         for (int i = 0; i < 10000; i++)
         {
             var port = DefaultIpcPort + i;
             if (port >= 65535)
             {
                 return 0;
             }
             bool isInUse = IsPortInUse(port, excludedPortRange);
             Console.WriteLine($"{port} 占用结果:{isInUse}");
             if (!isInUse)
             {
                 return port;
             }
         }

         Console.WriteLine("No available IPC port found.");
         return 0;
     }



     private bool IsPortInUse(int port, List<PortRange> excludePorts)
     {
         if (excludePorts.Any(excludePort => port >= excludePort.StartPort && port <= excludePort.EndPort))
         {
             Console.WriteLine($"{port} 在排除端口里边,直接返回已经占用");
             return true;
         }
         var properties = IPGlobalProperties.GetIPGlobalProperties();
         IPEndPoint[] tcpListeners = properties.GetActiveTcpListeners();
         IPEndPoint[] udpListeners = properties.GetActiveUdpListeners();
         TcpConnectionInformation[] connections = properties.GetActiveTcpConnections();

         return tcpListeners.Any(p => p.Port == port)
                                || udpListeners.Any(p => p.Port == port)
                                || connections.Any(c => c.LocalEndPoint.Port == port);

     }






     /// <summary>
     /// 获取保留端口
     /// </summary>
     /// <returns></returns>
     private List<PortRange> GetExcludedPortRange()
     {
         var excludedRanges = new List<PortRange>();
         try
         {
             var encoding = GetSystemConsoleEncoding();
             // 配置 netsh 命令启动信息
             var processStartInfo = new ProcessStartInfo
             {
                 FileName = "netsh.exe",
                 Arguments = "int ipv4 show excludedportrange protocol=tcp",
                 RedirectStandardOutput = true, // 捕获命令输出
                 RedirectStandardError = true,  // 捕获错误信息
                 UseShellExecute = false,       // 必须为 false 才能重定向输出
                 CreateNoWindow = true,         // 后台执行,不显示命令窗口
                 // 关键:适配中文/英文系统编码,避免乱码
                 StandardOutputEncoding = encoding,
                 StandardErrorEncoding = encoding
             };

             // 启动进程并执行命令
             using var process = new Process { StartInfo = processStartInfo };
             process.Start();
             var output = process.StandardOutput.ReadToEnd();
             var error = process.StandardError.ReadToEnd();
             process.WaitForExit();
             // 处理命令执行失败(如 netsh 命令不存在,理论上 Windows 都内置)
             if (process.ExitCode != 0)
             {
                 Console.WriteLine($"netsh 命令执行失败:{error}(退出码:{process.ExitCode}");
                 return excludedRanges;
             }
             Console.WriteLine($"获取保留端口的命令结果:{output}");

             // 解析命令输出,提取端口范围
             excludedRanges = PortConverter(output);
             return excludedRanges;
         }
         catch (Exception ex)
         {
             Console.WriteLine($"获取预留端口范围失败:{ex.Message}", ex);
             return excludedRanges;
         }
     }


     /// <summary>
     /// 解析 netsh 命令输出,提取预留端口范围
     /// </summary>
     /// <param name="output">netsh 命令原始输出</param>
     /// <returns>解析后的端口范围列表</returns>
     private List<PortRange> PortConverter(string output)
     {
         var ranges = new List<PortRange>();
         if (string.IsNullOrWhiteSpace(output))
             return ranges;
         var regex = new Regex(@"\s+(\d+)\s+(\d+)", RegexOptions.Multiline | RegexOptions.IgnoreCase);
         var matches = regex.Matches(output);
         foreach (Match match in matches)
         {
             // 安全解析端口号(确保是合法端口范围)
             if (match.Groups.Count == 3
                 && int.TryParse(match.Groups[1].Value, out var startPort)
                 && int.TryParse(match.Groups[2].Value, out var endPort)
                 && startPort >= 0 && endPort >= startPort && endPort <= 65535)
             {
                 ranges.Add(new PortRange(startPort, endPort));
             }
         }
         return ranges;
     }

     /// <summary>
     /// 获取系统控制台编码(解决中文/英文系统输出乱码问题)
     /// </summary>
     /// <returns>系统控制台默认编码</returns>
     private Encoding GetSystemConsoleEncoding()
     {
         try
         {
             return Console.OutputEncoding;
         }
         catch
         {
             return System.Globalization.CultureInfo.CurrentCulture.Name.Contains("zh")
                 ? Encoding.GetEncoding("GBK")
                 : Encoding.UTF8;
         }
     }
 }

调用如下,这样拿到的端口就是正在可用的端口了

   public MainWindow()
   {
       InitializeComponent();
       Loaded += MainWindow_Loaded;
       ComputePortService = new ComputePortService();
   }


   private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
   {
       var result = ComputePortService.GetValidIpcPort();
       Console.WriteLine($"获取可用的端口为:{result}");
   }

参考博文:

解决 Windows 10 端口被 Hyper-V 随机保留(占用)的问题 | 一个兆基

更改 Hyper-V 保留占用端口 - AirboZH 的博客

 

posted @ 2025-12-15 15:10  wuty007  阅读(3)  评论(0)    收藏  举报