bigywater.tech
常见报错 · 29

Node.js Error: listen EADDRINUSE:不同监听地址互相冲突的完整矩阵,127.0.0.1 与 :: 同端口时请求归谁,reusePort 在 macOS 上 ENOTSUP

“Error: listen EADDRINUSE: address already in use :::3000” 是前端和 Node 开发里出现最多的报错之一。这篇在本机复现它,把报错里每个字段是什么说清楚,把不同监听地址会不会冲突画成矩阵,并复现最常见的“鬼进程”和 reusePort 的情形。

-48
这台 macOS 上 EADDRINUSE 的 errno(Linux 上数值不同,本篇没有验证)
6 × 6
实测的监听地址组合矩阵:不写 host、127.0.0.1、0.0.0.0、::、::1、localhost 互相是否冲突
ENOTSUP
这台 macOS 上 listen 加 reusePort: true 的结果

报错里有什么

实测 · 本机执行输出
  第一个进程: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 22192

lsof 找 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)

杀掉父进程不会杀掉脱离的子进程,端口仍被占着。

分步演示

端口被占用
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64;报错文本都是真实复现时原样捕获的(命令行工具统一用 LC_ALL=C 取英文原文,路径换成 );所有服务都是本机上自己起的、只监听 127.0.0.1,不访问外网;计时用 time.perf_counter,数字每次运行会有小幅抖动。本篇用 Node v26.12.0;所有服务都是脚本里起的简单 http 服务,监听 9701 以上的端口,实验结束后全部结束;lsof 用于查看监听进程;进程号每次运行都不同,所以文中没有引用
    • 没有验证:Linux 上 errno 的数值(-98)与地址冲突矩阵(Linux 的绑定规则与 macOS 不同);reusePort 在 Linux 上的行为与连接分发;Windows;Docker 容器里的端口映射冲突(那是另一类报错);cluster 模块;SO_REUSEADDR 之外的 TIME_WAIT 情形(本篇没有测)
    京ICP备2026067502号-1京公网安备11010502063487号