端口被占用导致开发服务器打开的是旧实例

环境配置难度 2/5约 2 分钟

问题现象

启动开发服务器时直接失败:

text
Error: listen EADDRINUSE: address already in use 127.0.0.1:4321

更让人困惑的是:有时候命令明明启动失败了,浏览器打开那个地址却还能看到页面, 只是内容永远是旧的,改代码也不刷新 —— 于是开始怀疑「热重载坏了」。

排查过程

用 Get-NetTCPConnection 比解析 netstat 的文本输出可靠得多,它直接返回对象:

powershell
# 谁在监听 4321
Get-NetTCPConnection -LocalPort 4321 -State Listen |
  Select-Object LocalAddress, LocalPort, OwningProcess

拿到 OwningProcess(也就是 PID)之后,看它到底是什么程序、什么时候启动的:

powershell
Get-Process -Id 13304 | Select-Object Id, ProcessName, Path, StartTime

StartTime 是关键线索:如果它比你这次启动还早,那基本就是上一次遗留的进程。

一个容易踩的坑:不要用 $pid 当变量名。它是 PowerShell 的只读自动变量, 保存着当前进程的 PID,赋值会直接报错: 无法覆盖变量 PID,因为该变量为只读变量或常量。 用 $procId、$target 之类的名字。

根因

上一次启动的进程没有被真正关闭。这个现象有几个常见来源:

  • 直接关掉终端窗口,只结束了前台 shell,子进程继续存活;
  • 用 npm run dev 时,npm 本身也是一个进程,真正监听端口的是它拉起的子进程,
    结束父进程不等于结束子进程;
  • 用脚本或 Start-Process 拉起的服务,如果没有保留句柄去结束它,
    很容易只杀掉外层、留下真正的服务进程;
  • 有些工具在端口被占用时会自动换一个端口继续启动,
    发号的人以为成功了,实际访问的地址已经不是原来那个(这是最容易造成误判的一种)。

解决办法

powershell
# 1) 查出占用端口的 PID
$procId = (Get-NetTCPConnection -LocalPort 4321 -State Listen -ErrorAction SilentlyContinue).OwningProcess

# 2) 先确认它确实是自己的旧进程(看进程名和启动时间,别急着杀)
Get-Process -Id $procId | Select-Object Id, ProcessName, Path, StartTime

# 3) 确认无误后再结束
Stop-Process -Id $procId -Force

如果不想结束任何进程,直接换端口:

powershell
npm run dev -- --port 5000

复盘

  • 不要下意识地批量杀进程。先看清进程名和启动时间:
    占用端口的可能是数据库、另一个项目,或者系统服务,杀了会引出别的问题。
  • 关服务器养成用 Ctrl+C 的习惯,别只关窗口。
  • 用脚本拉起服务时,要保证脚本退出也能带走服务进程,否则很容易留下这种「幽灵服务」。
  • 项目脚本如果能自动切换端口,最好同时把实际使用的端口显著打印出来,
    避免「成功了但访问的是另一个实例」。这个博客的 engine/dev.mjs 就是按这个原则写的。

相关问题

PowerShell 重定向写出中文乱码

命令行难度 2/5约 2 分钟

用 Out-File 或 > 生成的中文文件被其它程序读成乱码,原因是 Windows PowerShell 默认不写 UTF-8。

PowerShell 里 && 不能用来连接命令

命令行难度 1/5约 1 分钟

从文档里复制来的 npm run build && npm run serve 在 Windows 上直接报错,因为 && 是 PowerShell 7 才支持的语法。