这个报错在说什么
应用启动时报 bind(): Address already in use(EADDRINUSE),意思是内核拒绝把该地址与端口分配给新进程。原因分三类:端口已被别的进程监听;旧的连接处于 TIME-WAIT 且未设置 SO_REUSEADDR;绑定的地址不匹配(0.0.0.0 与具体 IP 冲突,或容器与宿主机各有一张独立的端口表)。处置思路永远是先定位持有者,再决定是复用、等回收还是改配置。
基本信息
| 适用平台 | Linux(Windows 对应报错为 10048,用 netstat -ano 定位) |
|---|---|
| 错误码 | EADDRINUSE(Linux errno 98)、WSAEADDRINUSE(Windows 10048) |
| 三类成因 | 进程占用 / TIME-WAIT 残留 / 绑定地址不匹配 |
| 定位命令 | ss -tlnp 'sport = :8080'、lsof -i :8080 |
| 处置方向 | 复用端口、等待回收、或改端口与绑定地址 |
典型使用场景
- 服务重启后起不来:多半是旧进程还没退干净,新旧实例抢同一端口
- 容器反复重启报占用:容器与宿主机共享网络命名空间时的端口冲突
- 本地开发同时跑两个实例:端口未改导致第二个启动失败
- 修改监听地址后启动失败:从 127.0.0.1 改成 0.0.0.0 时与既有绑定冲突
- 短暂高并发后无法启动:TIME-WAIT 残留与未设置 SO_REUSEADDR 叠加
定位与处置
定位与处置
ss -tlnp 'sport = :8080' # 谁在监听这个端口
lsof -i :8080 # 精确到进程与可执行文件
ss -tan state time-wait 'sport = :8080' # 是否有 TIME-WAIT 残留占用该端口
sysctl net.ipv4.tcp_tw_reuse net.ipv4.ip_local_port_range # 相关内核开关与端口范围
journalctl -u myapp -n 50 --no-pager # 应用侧日志,确认绑定地址是否与预期一致
systemctl status myapp # 旧实例是否仍在 active 或 failed 状态成因与处置对照
| 成因 | 特征 | 处置方式 |
|---|---|---|
| 另一进程监听 | ss -tlnp 能看到明确的 PID | 确认服务归属后停掉旧进程,或在配置中改端口 |
| TIME-WAIT 残留 | ss 显示 time-wait,无属主进程 | 等待回收;应用侧设置 SO_REUSEADDR |
| 绑定地址冲突 | 同一端口不同地址各有一行 | 统一为 0.0.0.0 或改用不同地址与端口 |
| 命名空间隔离 | 容器内无占用,宿主机却报错 | 核对 netns 与端口映射,避免重复映射 |
| 旧进程未退净 | systemd 状态为 deactivating | 调大 TimeoutStopSec 或先确认旧进程退出 |
服务重启后偶发此错,多数是旧进程退出慢:systemd 停服务超时后新实例已开始启动,两代进程并存抢端口。看 journalctl 的停启时间戳能快速确认。