“Error: listen EADDRINUSE: address already in use :::3000” 是前端和 Node 开发里出现最多的报错之一。这篇在本机复现它,把报错里每个字段是什么说清楚,把不同监听地址会不会冲突画成矩阵,并复现最常见的“鬼进程”和 reusePort 的情形。
第一个进程:listening :: 9701 pid 22137
第二个进程(没处理 error 事件)退出码 1:
Error: listen EADDRINUSE: address already in use :::9701
at listenInCluster (node:net:2499:12)
Emitted 'error' event on Server instance at:
code: 'EADDRINUSE',
errno: -48,
syscall: 'listen',
address: '::',
port: 9701
Node.js v26.12.0
第二个进程(on("error") 里处理)退出码 2:
error 事件: EADDRINUSE | listen EADDRINUSE: address already in use :::9701错误对象带 code、errno、syscall、address、port;没处理 error 事件进程会直接崩溃。
$ lsof -nP -iTCP:9702 -sTCP:LISTEN
COMMAND PID NODE NAME
node 22187 TCP *:9702 (LISTEN)
再监听 9702:error 事件: EADDRINUSE | listen EADDRINUSE: address already in use :::9702
kill 之后再监听 9702:listening :: 9702 pid 22192lsof 找 pid,确认是自己的旧进程再 kill。
先占(第一个) 不写host 127.0.0.1 0.0.0.0 :: ::1 localhost ← 再占(第二个)
不写host 冲突 可以 冲突 冲突 可以 可以 (:: 9711 pid 22427)
127.0.0.1 可以 冲突 可以 可以 可以 可以 (127.0.0.1 9712 pid 23048)
0.0.0.0 可以 可以 冲突 可以 可以 可以 (0.0.0.0 9713 pid 23897)
:: 冲突 可以 冲突 冲突 可以 可以 (:: 9714 pid 24808)
::1 可以 可以 可以 可以 冲突 冲突 (::1 9715 pid 25370)
localhost 可以 可以 可以 可以 冲突 冲突 (::1 9716 pid 25986)
——能同时监听不代表没问题:先占 :: 的进程 A,再让进程 B 只监听 127.0.0.1,同一个端口上各个地址的请求会被谁处理?
http://127.0.0.1:9730/ -> B(只监听 127.0.0.1)
http://[::1]:9730/ -> A(先占 ::)
http://localhost:9730/ -> A(先占 ::)不同 host 之间有的冲突有的不冲突;能同时监听时,不同地址会被不同的服务回应。
listen(0):listening :: 52619 pid 26768
从 9740 开始往上找空闲端口(9740、9741 已被占):
端口 9740 被占用,换 9741
端口 9741 被占用,换 9742
listening :: 9742
hits 0
listen({ port: 9745, reusePort: true }) 退出码 2:error 事件: ENOTSUP | listen ENOTSUP: operation not supported on socket 0.0.0.0:9745
(这台 macOS 上 reusePort 直接是 ENOTSUP;其他系统上是否支持,本篇没有验证)listen(0) 或自己加 1 重试;reusePort 在 macOS 上不支持。
parent pid 26924 child pid 26954 杀掉父进程之后,父进程存活:False 再监听 9750:error 事件: EADDRINUSE | listen EADDRINUSE: address already in use :::9750 lsof 看到占端口的是 pid 26954(就是上面的 child pid)
杀掉父进程不会杀掉脱离的子进程,端口仍被占着。