一、出现的问题
1、最近,部分客户在反馈,升级的过程中,偶尔出现升级的时候,开启IPC失败。观察了日志,是启动进程键通信的时候,服务的端口监听的时候,无法访问。这个个问题很是诡异
二、问题排查
1、使用 “netstat -ano | findstr 端口” 的命令,发现该端口是没有进程占用的
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 随机保留(占用)的问题 | 一个兆基
4、开启hyper-v之后,使用 “netsh int ipv4 show excludedportrange protocol=tcp” 的命令,确实可以看到,好多段的端口范围都被排除了。
也就是说,如果只判断了端口是否被占用,但是端口没有进程占用,但是获取的端口又在排除的范围之内,一旦把该排除的端口做端口监听,就会报错。
三、解决问题
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、在代码上动态过滤掉被占用或者被保留的端口
[code] internal class ComputePortService { public int DefaultIpcPort { get; set; } = 18743; /// /// 获取有效的IPC端口 /// /// /// 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 excludePorts) { if (excludePorts.Any(excludePort => port >= excludePort.StartPort && port p.Port == port) || udpListeners.Any(p => p.Port == port) || connections.Any(c => c.LocalEndPoint.Port == port); } /// /// 获取保留端口 /// /// private List GetExcludedPortRange() { var excludedRanges = new List(); 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; } } /// /// 解析 netsh 命令输出,提取预留端口范围 /// /// netsh 命令原始输出 /// 解析后的端口范围列表 private List PortConverter(string output) { var ranges = new List(); 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 |