You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

318 lines
10 KiB
C#

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#region << 版 本 注 释 >>
/*--------------------------------------------------------------------
* 版权所有 (c) 2026 WenJY 保留所有权利。
* CLR版本4.0.30319.42000
* 机器名称Mr.Wen's MacBook Pro
* 命名空间Sln.IntelliBelt.Socket
* 唯一标识E4DF9972-3038-4900-A2C1-507F30B63F54
*
* 创建者WenJY
* 电子邮箱:
* 创建时间2026-05-27 11:13:35
* 版本V1.0.0
* 描述:
*
*--------------------------------------------------------------------
* 修改人:
* 时间:
* 修改说明:
*
* 版本V1.0.0
*--------------------------------------------------------------------*/
#endregion << 版 本 注 释 >>
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace Sln.IntelliBelt.Socket;
/// <summary>
/// 端口信息查询结果
/// </summary>
public class PortProcessInfo
{
public int Port { get; set; }
public string ProcessName { get; set; }
public int Pid { get; set; }
public string LocalAddress { get; set; }
public string RemoteAddress { get; set; }
public string State { get; set; }
public override string ToString()
{
return $"端口:{Port} | 程序:{ProcessName} | PID:{Pid} | {LocalAddress}->{RemoteAddress} | 状态:{State}";
}
}
public static class PortHelper
{
/// <summary>
/// 根据端口号获取使用该端口的程序信息
/// </summary>
/// <param name="port">端口号</param>
/// <returns>程序信息列表</returns>
public static List<PortProcessInfo> GetProcessByPort(int port)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)
|| RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
return GetProcessByPortUnix(port);
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return GetProcessByPortWindows(port);
}
throw new PlatformNotSupportedException("不支持当前操作系统");
}
#region Unix (macOS / Linux)
private static List<PortProcessInfo> GetProcessByPortUnix(int port)
{
var result = new List<PortProcessInfo>();
try
{
var psi = new ProcessStartInfo
{
FileName = "lsof",
Arguments = $"-i :{port} -n -P",
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
};
using var process = Process.Start(psi);
var output = process!.StandardOutput.ReadToEnd();
process.WaitForExit();
var lines = output.Split('\n', StringSplitOptions.RemoveEmptyEntries);
// 跳过表头(第一行)
foreach (var line in lines.Skip(1))
{
var parts = line.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length < 9) continue;
var command = parts[0];
var pidStr = parts[1];
var namePart = parts[8]; // 格式: localhost:port->localhost:port 或 *:port
if (!int.TryParse(pidStr, out int pid)) continue;
var info = new PortProcessInfo
{
Port = port,
ProcessName = command,
Pid = pid
};
// 解析地址和状态
ParseUnixConnection(namePart, info);
// 如果有状态标识(如 (ESTABLISHED)),取最后一个括号内的内容
var lastPart = parts[^1];
if (lastPart.StartsWith('(') && lastPart.EndsWith(')'))
{
info.State = lastPart.Trim('(', ')');
}
else
{
info.State = "LISTEN";
}
result.Add(info);
}
}
catch (Exception ex)
{
Console.Error.WriteLine($"执行 lsof 失败: {ex.Message}");
}
return result;
}
private static void ParseUnixConnection(string namePart, PortProcessInfo info)
{
// 格式类似: localhost:6000->localhost:51183 或 *:51183
var arrowIdx = namePart.IndexOf("->");
if (arrowIdx > 0)
{
info.LocalAddress = namePart[..arrowIdx];
info.RemoteAddress = namePart[(arrowIdx + 2)..];
}
else
{
info.LocalAddress = namePart;
info.RemoteAddress = "-";
}
}
#endregion
#region Windows
private static List<PortProcessInfo> GetProcessByPortWindows(int port)
{
var result = new List<PortProcessInfo>();
try
{
// 1. 用 netstat 找到 PID
var connections = new List<(string local, string remote, string state, int pid)>();
var psi = new ProcessStartInfo
{
FileName = "netstat",
Arguments = "-ano",
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
};
using var process = Process.Start(psi);
var output = process!.StandardOutput.ReadToEnd();
process.WaitForExit();
foreach (var line in output.Split('\n', StringSplitOptions.RemoveEmptyEntries).Skip(4))
{
var parts = line.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length < 5) continue;
var localAddr = parts[1]; // 如 0.0.0.0:8080
var remoteAddr = parts[2];
var state = parts.Length == 5 ? "LISTEN" : parts[3];
// 取最后一列作为 PID
var lastPart = parts[^1];
if (!int.TryParse(lastPart, out int pid)) continue;
// 检查是否匹配目标端口
var localPort = localAddr.Split(':').Last();
var remotePort = remoteAddr.Split(':').Last();
if (localPort == port.ToString() || remotePort == port.ToString())
{
connections.Add((localAddr, remoteAddr, state, pid));
}
}
// 2. 用 tasklist 找程序名
var pidNameMap = new Dictionary<int, string>();
psi = new ProcessStartInfo
{
FileName = "tasklist",
Arguments = "/FO CSV /NH",
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
};
using var process2 = Process.Start(psi);
var tasklistOutput = process2!.StandardOutput.ReadToEnd();
process2.WaitForExit();
foreach (var line in tasklistOutput.Split('\n', StringSplitOptions.RemoveEmptyEntries))
{
var trimmed = line.Trim().Trim('"');
var csvParts = trimmed.Split("\",\"");
if (csvParts.Length >= 2 && int.TryParse(csvParts[1], out int pid))
{
pidNameMap[pid] = csvParts[0];
}
}
// 3. 组装结果
foreach (var (local, remote, state, pid) in connections)
{
var processName = pidNameMap.TryGetValue(pid, out var name) ? name : "(未知)";
result.Add(new PortProcessInfo
{
Port = port,
ProcessName = processName,
Pid = pid,
LocalAddress = local,
RemoteAddress = remote,
State = state
});
}
}
catch (Exception ex)
{
Console.Error.WriteLine($"查询失败: {ex.Message}");
}
return result;
}
#endregion
/// <summary>
/// 判断端口是否被本地 Intel 程序Sln.Intel占用
/// </summary>
/// <param name="port">端口号</param>
/// <returns>true 表示端口被本地 Sln.Intel 相关进程占用</returns>
public static bool IsIntelLocalPort(int port)
{
var list = GetProcessByPort(port);
return list.Any(info =>
// 程序名包含 Sln.Intel忽略大小写
info.ProcessName.Contains("Sln.Intel",
StringComparison.OrdinalIgnoreCase) //&& IsLocalAddress(info.RemoteAddress);;
// 且连接双方都在本机localhost 或 127.0.0.1
&& IsLocalAddress(info.LocalAddress)
&& IsLocalAddress(info.RemoteAddress));
}
/// <summary>
/// 判断地址是否为本机地址
/// </summary>
private static bool IsLocalAddress(string address)
{
if (string.IsNullOrEmpty(address)) return false;
// 支持 localhost、127.0.0.1、*:* 等本机形式
return address.StartsWith("localhost:", StringComparison.OrdinalIgnoreCase)
|| address.StartsWith("127.0.0.1:", StringComparison.OrdinalIgnoreCase)
|| address.StartsWith("*:", StringComparison.OrdinalIgnoreCase)
|| address.StartsWith("[::1]:", StringComparison.OrdinalIgnoreCase)
|| address.StartsWith("0.0.0.0:", StringComparison.OrdinalIgnoreCase);
}
#region 便捷方法
/// <summary>
/// 获取端口对应的第一个程序名称(最常用)
/// </summary>
/// <param name="port">端口号</param>
/// <returns>程序名,无结果时返回 null</returns>
public static string? GetProcessNameByPort(int port)
{
var list = GetProcessByPort(port);
return list.FirstOrDefault()?.ProcessName;
}
/// <summary>
/// 输出端口占用详情到控制台
/// </summary>
public static void PrintPortInfo(int port)
{
var list = GetProcessByPort(port);
if (list.Count == 0)
{
Console.WriteLine($"端口 {port} 没有被任何程序占用");
return;
}
foreach (var item in list)
{
Console.WriteLine(item);
}
}
#endregion
}