“Address already in use”却查不到占用进程,说明端口被内核残留状态(如TIME_WAIT堆积、孤儿socket)占用;应先用ss -tuln确认监听状态,若pid=0则需启用SO_REUSEADDR、清理TIME_WAIT或重启网络服务。

Workerman启动时报“Address already in use”却用lsof或netstat查不到占用进程,说明端口可能被内核残留状态、僵尸socket或未显示PID的系统服务占着,不是常规用户进程在跑。
先确认端口是否真被监听
执行ss -tuln | grep :端口号(如ss -tuln | grep :2345),比netstat更底层、更可靠。如果输出为空,说明内核根本没监听,Workerman压根没起来——别急着杀进程,先看日志。
如果输出类似tcp LISTEN 0 128 *:2345 *:* users:(("php",pid=0,fd=6))且pid=0,这就是关键信号:【内核已分配端口但无对应用户进程】,通常是TIME_WAIT堆积或SO_REUSEADDR未启用导致的假占用。
检查TIME_WAIT连接是否卡死
执行ss -tan state time-wait | grep :端口号 | wc -l,若返回值大于100,说明大量旧连接还没释放完,新bind会失败。
临时缓解:在Workerman启动脚本的Worker初始化前加一行$worker->context['so_reuseaddr'] = true;,让socket复用地址。这步必须加在new Worker()之后、Worker::runAll()之前,否则无效。
注意:Linux默认开启net.ipv4.tcp_tw_reuse,但Workerman不自动继承该参数,不手动设context就不起作用。
强制清理内核级残留绑定
方法一:重启网络子系统(仅限测试环境)
执行sudo systemctl restart networking(Debian/Ubuntu)或sudo systemctl restart network(CentOS/RHEL),可清空所有未关联进程的监听状态。
方法二:重置本地端口范围并刷新连接表
运行echo 32768 65535 > /proc/sys/net/ipv4/ip_local_port_range → sudo sysctl -w net.ipv4.tcp_fin_timeout=30 → sudo ss -napi | head -20确认输出变短。
方法三:直接换端口启动(最快验证手段)
修改new Worker('tcp://0.0.0.0:2345')为new Worker('tcp://0.0.0.0:2346'),保存后执行php start.php start。如果成功,证明原端口确属内核级残留,非程序冲突。
排查Docker或WSL虚拟网卡干扰
第一步:查宿主机是否开了Docker Desktop或WSL2
执行ip addr show | grep -E "(docker|veth|wsl)",若有输出,说明虚拟网卡可能劫持了端口绑定。
第二步:临时禁用相关服务
Docker用户运行sudo systemctl stop docker;WSL2用户在PowerShell中执行wsl --shutdown,再试Workerman启动。
第三步:若确定是虚拟网卡问题,改用tcp://127.0.0.1:2345绑定(仅本地调试)或在Docker run时加--network host模式绕过NAT层。
终极手段:从内核模块层面释放
① 查当前端口绑定的inode号:ss -tulnep | grep :端口号,记下括号里的inode值(如ino:12345678)
② 定位该inode对应的文件:find /proc/*/fd -lname "socket:[12345678]" 2>/dev/null
③ 若返回为空,说明该socket已无进程关联,属于孤儿状态
④ 执行sudo sysctl -w net.ipv4.tcp_fin_timeout=15 → 等待30秒 → 再次ss -tuln | grep :端口号确认消失
⑤ 启动Workerman











