workerman 是 php 实现 websocket 最稳选择,但需注意:默认监听 tcp 需显式启用 websocket 协议;nginx 反代须透传 upgrade 和 connection 头;$data 已解帧,发送前需 json 编码并确保为字符串;广播须维护客户端容器或使用 gatewayworker 绑定 uid;修改代码后须彻底 stop 并 kill 残留进程再 start。

Workerman 是 PHP 实现 WebSocket 实时通信最稳、最省心的选择——它不依赖扩展,纯 PHP 编写,PHP 8.4 完全兼容,且避免了原生 socket 手撕握手、帧解析、心跳、广播等极易出错的底层逻辑。
WebSocket 连接失败:ws://localhost:2346 一直 pending 或报 failed
这不是代码写错了,而是 Workerman 默认监听的是 raw TCP,不自动处理 HTTP Upgrade 握手。哪怕你写了 new Worker('websocket://0.0.0.0:2346'),若没配对启动方式或代理,浏览器仍连不上。
- 确保启动命令是
php start.php start(非php start.php start -d后台后就不管了),并确认控制台输出类似Websocket worker starting... - 浏览器必须用
ws://localhost:2346(开发环境)或wss://yourdomain.com/wss(生产),不能用http://协议访问 - 若走 Nginx 反代,配置里必须透传两个关键头:
proxy_set_header Upgrade $http_upgrade和proxy_set_header Connection "upgrade",缺一不可 - 检查端口是否被占用:
lsof -i :2346或netstat -tuln | grep 2346,旧进程残留会导致新代码不生效
onMessage 收到的数据是 null、空字符串或乱码
Workerman 已自动解帧,$data 应该是 UTF-8 字符串;出现空值通常是因为客户端发的不是合法文本帧,或服务端未正确响应 handshake。
- 客户端发送前务必
JSON.stringify(),不要直接 send 对象;服务端接收后可用json_decode($data, true)解析,但先判断is_string($data) && !empty($data) - 中文乱码大概率是没加
JSON_UNESCAPED_UNICODE:发送时用json_encode($msg, JSON_UNESCAPED_UNICODE),否则变成\u4f60\u597d -
$connection->send()只接受string,传数组会静默失败,无报错、无日志、无响应——这是最常被忽略的坑 - 如果前端用 Apifox / WeChat DevTools 测试,注意它们默认发的是 raw 文本,不是 JSON,需手动切格式
广播消息只发给自己,或部分连接收不到
Workerman 的 $worker->connections 包含所有 TCP 连接(包括内部管理连接),不能直接遍历它来广播;WebSocket 连接需显式维护容器。
- 别写
foreach ($worker->connections as $conn) { $conn->send(...); }——这会把消息发给非 WebSocket 连接,导致Call to a member function send() on null - 正确做法是维护一个全局数组,例如
$clients = [],在onConnect里存:$clients[$connection->id] = $connection,onClose里删:unset($clients[$connection->id]) - 广播时跳过当前连接:
foreach ($clients as $client) { if ($client !== $connection) $client->send($msg); } - 如需按用户 ID 管理(登录后绑定 uid),必须调用
\GatewayWorker\Gateway::bindUid($connection->id, $uid),且改用Gateway::sendToAll()或sendToUid(),而不是直接操作 connection
改了代码却没生效,reload 像没发生一样
Workerman 没有真正的热重载机制。php start.php restart 只是 kill 主进程,子进程可能还在跑旧逻辑,造成“改了但收不到新消息”的假象。
- 每次修改后,先彻底停掉:
php start.php stop - 再杀残余进程:
ps aux | grep workerman | grep -v grep | awk '{print $2}' | xargs kill -9 - 最后重新启动:
php start.php start - 验证是否干净:启动后看日志时间戳,或临时在
onMessage里加echo "v2.1\n";,确认输出更新
真正难的从来不是写几行 onMessage,而是连接生命周期管理——谁该进房间、谁已掉线、心跳怎么判、断线后消息要不要补、高并发下连接数组如何不崩溃。这些细节不写进 $clients 或不用 GatewayWorker 的 uid 绑定体系,迟早会在 200+ 并发时暴露出来。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











