Workerman 启动报“Address already in use”需先定位占用端口的进程:Linux/macOS用lsof -i :端口号或netstat -tulnp | grep,Windows用netstat -ano | findstr;再检查Workerman残留子进程并彻底清理,同步更新register地址与客户端配置,开发环境可临时启用SO_REUSEADDR缓解TIME_WAIT问题。

Workerman 启动报 Address already in use 怎么快速定位是谁占的端口
这个错误本质是 Linux/Unix 系统级提示:你要 bind 的端口(比如 2345)已经被另一个进程占了,Workerman 拿不到控制权。别急着 kill -9,先搞清谁在用。
常用排查命令(Linux/macOS):
-
lsof -i :<port></port>(例如lsof -i :2345),直接列出占用该端口的进程 PID 和命令名 -
netstat -tulnp | grep :<port></port>(需要 root 权限才显示 PID/程序名) - Windows 下用
netstat -ano | findstr :<port></port>,再用tasklist | findstr <pid></pid>查进程名
注意:lsof 在某些最小化系统(如 Alpine)默认没装,得先 apk add lsof;Workerman 默认监听 0.0.0.0:2345,查的时候别漏掉 0.0.0.0 或 ::(IPv6)地址变体。
Workerman 重启后仍报端口占用,大概率是子进程没干净退出
Workerman 是多进程模型,主进程 fork 出多个 worker 进程。如果上次异常退出(比如 kill -9 主进程、断电、OOM killer 干掉子进程),部分子进程可能还在后台跑着,继续占着端口。
- 先用
ps aux | grep workerman看有没有残留的php进程(尤其是带WorkerMan或你项目路径的) - 别只 kill 主进程 PID,要连带干掉所有相关子进程:
kill -9 $(pgrep -f "YourAppPath/start.php") - 更稳妥的做法:启动前加清理逻辑,比如在
start.php开头加exec("killall -u $(whoami) php 2>/dev/null");(仅测试环境,生产慎用)
常见陷阱:你以为 restart 成功了,其实旧 worker 还活着,新 worker 启动失败,日志里只写一句 Address already in use,根本不会告诉你“有 3 个旧进程卡在 ESTABLISHED 状态”。
想换端口?别只改 $worker->listen,还要检查 register 地址和客户端配置
Workerman 常用两层结构:业务 worker 监听客户端请求,register worker(注册中心)负责进程管理。改一个地方,另一处可能还卡在旧端口。
- 业务 worker 端口在
$worker = new Worker("text://0.0.0.0:<port>");</port>里改 - register worker 默认走
127.0.0.1:1236,改法是启动时传参:php start.php start -d --register-addr=127.0.0.1:<new_port></new_port> - 如果你用 GatewayWorker,
Gateway类构造时的$gateway = new Gateway("websocket://0.0.0.0:<port>");</port>和$gateway->registerAddress属性必须同步更新 - 客户端连接代码(JS / APP)里硬编码的端口也得同步改,否则连不上不是报错,是超时
端口改完不生效?八成是 register 地址没对齐,Workerman 主进程会反复尝试连 register,连不上就卡住,最后才抛出端口错误——它只是表象,根源在通信链路断了。
开发机上频繁启停,可以临时启用 SO_REUSEADDR 避免 TIME_WAIT 卡端口
Linux 下 socket 关闭后会进入 TIME_WAIT 状态(默认 60 秒),期间端口不可重用。开发时高频重启,很容易撞上这个窗口期。
- Workerman 本身不直接暴露 socket 选项,但可以在创建
Worker实例后手动设置:$worker = new Worker('tcp://0.0.0.0:2345'); $worker->onWorkerStart = function($worker) { if (isset($worker->socket)) { socket_set_option($worker->socket, SOL_SOCKET, SO_REUSEADDR, 1); } }; - 注意:这只能缓解开发场景,不能解决真正被其他进程长期占用的问题
- 生产环境禁用此设置,
SO_REUSEADDR可能导致旧连接残包干扰新连接,尤其在负载高、网络不稳定时
真正难处理的是那些不释放端口的“幽灵进程”——它们不响应信号、不写日志、不报错,只安静地把端口焊死。这种时候,lsof + strace -p <pid></pid> 才是最后一招。










