端口被占用导致开发服务器打开的是旧实例
问题现象
启动开发服务器时直接失败:
Error: listen EADDRINUSE: address already in use 127.0.0.1:4321更让人困惑的是:有时候命令明明启动失败了,浏览器打开那个地址却还能看到页面, 只是内容永远是旧的,改代码也不刷新 —— 于是开始怀疑「热重载坏了」。
排查过程
用 Get-NetTCPConnection 比解析 netstat 的文本输出可靠得多,它直接返回对象:
# 谁在监听 4321
Get-NetTCPConnection -LocalPort 4321 -State Listen |
Select-Object LocalAddress, LocalPort, OwningProcess拿到 OwningProcess(也就是 PID)之后,看它到底是什么程序、什么时候启动的:
Get-Process -Id 13304 | Select-Object Id, ProcessName, Path, StartTimeStartTime 是关键线索:如果它比你这次启动还早,那基本就是上一次遗留的进程。
一个容易踩的坑:不要用
$pid当变量名。它是 PowerShell 的只读自动变量, 保存着当前进程的 PID,赋值会直接报错:无法覆盖变量 PID,因为该变量为只读变量或常量。用$procId、$target之类的名字。
根因
上一次启动的进程没有被真正关闭。这个现象有几个常见来源:
- 直接关掉终端窗口,只结束了前台 shell,子进程继续存活;
- 用
npm run dev时,npm本身也是一个进程,真正监听端口的是它拉起的子进程,
结束父进程不等于结束子进程; - 用脚本或
Start-Process拉起的服务,如果没有保留句柄去结束它,
很容易只杀掉外层、留下真正的服务进程; - 有些工具在端口被占用时会自动换一个端口继续启动,
发号的人以为成功了,实际访问的地址已经不是原来那个(这是最容易造成误判的一种)。
解决办法
# 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如果不想结束任何进程,直接换端口:
npm run dev -- --port 5000复盘
- 不要下意识地批量杀进程。先看清进程名和启动时间:
占用端口的可能是数据库、另一个项目,或者系统服务,杀了会引出别的问题。 - 关服务器养成用
Ctrl+C的习惯,别只关窗口。 - 用脚本拉起服务时,要保证脚本退出也能带走服务进程,否则很容易留下这种「幽灵服务」。
- 项目脚本如果能自动切换端口,最好同时把实际使用的端口显著打印出来,
避免「成功了但访问的是另一个实例」。这个博客的engine/dev.mjs就是按这个原则写的。