最直接、兼容性最好的端口占用判断方式是用TcpClient.ConnectAsync模拟连接:连通说明有服务监听,拒绝连接(ConnectionRefused)大概率未监听,超时则无法确定;需设短超时(如500ms)并匹配测试地址(如127.0.0.1或0.0.0.0)。
用
尝试连接来判断端口是否被占用
最直接、兼容性最好的方式不是查进程或监听列表,而是模拟一次连接——如果连得上,说明端口正在被监听(大概率被占用);连不上,不一定空闲(可能防火墙拦截、目标未开监听、跨网段不可达)。注意:这测的是“是否有服务在监听”,不是“是否被某个进程独占”。
常见错误现象:
报错 “No connection could be made because the target machine actively refused it” 表示端口有监听但拒绝连接(比如只接受本地连接),而 “A connection attempt failed...” 更可能是端口根本没监听或网络不通。
仅适用于 TCP 端口;UDP 需用
发送探测包,但无可靠响应机制,不推荐用于判断占用
连接超时必须设短(如 500ms),否则阻塞主线程;建议用
+
本地回环地址(
)和实际监听地址(如
或具体 IP)行为可能不同,测试时需匹配目标场景
示例:
用
查本机监听地址
这个方法能拿到当前系统所有 TCP 监听端口的
列表,适合做“本机自查”,不依赖网络连通性,但只能看到本机进程开启的监听,且需要管理员权限才能看到其他用户进程的监听项(Windows 上尤其明显)。
容易踩的坑:
和
是两个独立条目;前者表示监听所有接口,后者仅限本地;若程序绑定
,检查
会漏判。
返回结果不含进程名或 PID,无法直接知道谁占的 —— 如需定位,得配合
扫描,但跨用户/权限受限时失败率高
.NET 5+ 支持
,旧版 .NET Framework 可用,但 .NET Core 3.1 及以前需用
IPv6 地址(如
)和 IPv4 地址分开处理,别漏掉
对应的监听
示例(检查是否监听 8080):
C知道
CSDN推出的一款AI技术问答工具
下载
为什么不用
或
?
调用外部命令看似简单,但实际落地问题多:权限控制难(普通用户无法看到系统进程)、解析输出易出错(不同 Windows 版本字段顺序/空格不一致)、跨平台失效(Linux/macOS 命令不同)、启动进程有开销。PowerShell 的
虽原生,但需加载模块、非默认执行策略下常被禁用,且返回对象结构在不同系统版本中不稳定。
性能与可靠性权衡:一次
连接尝试耗时毫秒级;而启动
进程 + 解析文本平均 100ms 起,还可能被杀软拦截或卡住。
若你已在写部署脚本或 CI 工具,且明确运行在管理员权限的 Windows 环境下,用 PowerShell 是可选项,但别嵌在核心业务逻辑里
绝对避免用字符串匹配
—— 端口号可能出现在 PID 列(比如 PID 是 8080),导致误报
Linux 下等价命令是
,但 C# 程序里调用它会让代码失去可移植性
绑定前检查端口可用性的正确姿势
真正要避免“Address already in use”异常,不是靠提前扫描,而是让 Socket 自己处理:用
允许端口重用(TIME_WAIT 状态下也能 bind),再配合
(Windows)或
(Linux/macOS)精细控制。但注意,
不等于“允许冲突绑定”,它只是放宽了内核限制。
监听前设置
是防御性标配
.NET 中
默认不启用
,需传入自定义
实例才能控制
多个进程绑定同一端口只有在特定条件下才合法(如 SO_REUSEPORT + 同一用户),不要假设它总能成功
所以,与其花力气反复探测,不如把异常处理写扎实:
端口占用判断没有银弹。连得通 ≠ 一定能用(可能协议不匹配),查到监听 ≠ 一定是你要防的那个服务,而
开了也不代表不会冲突——关键看你的场景到底要防御什么:是启动时避免报错?还是运行中感知依赖服务是否存活?目标不同,方案就得切开看。
TcpClientSocketExceptionUdpClientConnectAsyncCancellationToken"127.0.0.1""0.0.0.0"var client = new TcpClient();
try
{
await client.ConnectAsync("127.0.0.1", 8080, TimeSpan.FromMilliseconds(500));
Console.WriteLine("端口 8080 正在被监听");
}
catch (SocketException ex) when (ex.SocketErrorCode == SocketError.ConnectionRefused)
{
Console.WriteLine("端口 8080 未监听(大概率空闲)");
}
catch (OperationCanceledException)
{
Console.WriteLine("连接超时 → 无法确定,可能被占用也可能被拦截");
}
finally
{
client.Close();
}IPGlobalProperties.GetActiveTcpListeners()IPAddress:Port0.0.0.0:8080127.0.0.1:80800.0.0.0127.0.0.1System.Diagnostics.ProcessGetActiveTcpListeners()IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpListeners()::1IPAddress.IPv6Anyvar listeners = IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpListeners();
bool isListening = listeners.Any(x => x.Port == 8080 &&
(x.Address.Equals(IPAddress.Any) ||
x.Address.Equals(IPAddress.Loopback) ||
x.Address.Equals(IPAddress.IPv6Any) ||
x.Address.Equals(IPAddress.IPv6Loopback));netstatGet-NetTCPConnectionGet-NetTCPConnectionTcpClientnetstatnetstat -ano | findstr :8080ss -tln | grep ':8080'SocketOptionName.ReuseAddressSO_EXCLUSIVEADDRUSESO_REUSEPORTReuseAddresssocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true)TcpListenerReuseAddressSockettry
{
var listener = new TcpListener(IPAddress.Any, 8080);
listener.Server.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
listener.Start();
}
catch (SocketException ex) when (ex.SocketErrorCode == SocketError.AddressAlreadyInUse)
{
// 真正需要关心的错误:此时再决定是换端口、报错还是等重试
}ReuseAddress