WebSocket 400 错误先查 onConnect 是否触发,常见因前后端协议/域名/端口不一致;消息收发问题需在 onConnect 绑定 uid/role,onMessage 中须 json_decode 解析字符串,并用定时器扫描 Redis 离线队列重发。

Webman 启动后 WebSocket 连接 400 错误怎么查
直接看 onConnect 是否被触发,没触发说明握手失败。常见原因不是端口被占,而是前端 ws URL 协议/域名/端口和后端监听不一致。比如前端用 wss://example.com:8787,但 Webman 只监听了 ws://0.0.0.0:8787,且没配 Nginx 反代或 SSL,就会返回 400。
实操建议:
- 先用
curl -i http://127.0.0.1:8787确认 HTTP 服务通,再用浏览器控制台手动 new WebSocket('ws://127.0.0.1:8787') 测试最简连接 - 检查
config/server.php中'websocket'配置项是否启用,端口是否与启动命令中指定的一致(如php start.php start -d默认读取该文件) - 若走 HTTPS 域名访问,必须在 Nginx 层做 wss 反向代理,Webman 本身不处理 TLS;Nginx 配置里要加
proxy_http_version 1.1;和proxy_set_header Upgrade $http_upgrade;
访客消息发出去了,客服收不到——连接没绑定用户 ID
Webman 默认的 $connection 对象不带业务身份,onMessage 里拿到的只是原始 socket 连接。如果没在 onConnect 阶段完成鉴权并把用户角色、ID 写进连接上下文,后续就无法路由消息。
实操建议:
- 前端建立 WebSocket 连接时,必须带上 token 或 visitor_id 参数,例如:
new WebSocket('ws://127.0.0.1:8787?token=abc123') - 在
onConnect回调里解析 query string,调用 TP6 或自建接口校验 token,成功后写入:$connection->uid = $uid; $connection->role = 'visitor'; - 不要依赖全局数组存连接(如
$worker->connections),改用ConnectionInterface::getExt()或直接挂属性更可靠
onMessage 收到数据是字符串还是数组?
Workerman/Webman 默认把 WebSocket 帧当作原始字符串传入 onMessage,不会自动 JSON 解析。你看到的 {"type":"chat","text":"hi"} 是字符串,不是 PHP 数组,直接 isset($data['type']) 会报错。
Webman 2.2.0版本强化了 TCP/UDP 服务支持,优化路由组管理,并增强异步任务处理能力。结合协程与连接池技术,Webman 能轻松应对高并发场景,适用于网站、接口服务、即时通讯、物联网及游戏开发,兼具高性能、灵活扩展与稳定可靠,是多场景 PHP 服务开发的理想选择。
实操建议:
- 务必在
onMessage开头加$msg = json_decode($data, true); if (json_last_error() !== JSON_ERROR_NONE) { return; } - 生产环境建议加字段校验:检查
isset($msg['type']) && in_array($msg['type'], ['chat', 'heartbeat', 'typing']) - 避免用
eval()或unserialize()处理客户端输入,只信任 JSON
客服离线时消息存 Redis,但没触发重发
消息存离线队列只是第一步,关键在「谁来捞」和「何时推」。Webman 是常驻进程,但它不主动轮询 Redis,需要靠定时器或外部任务驱动。
实操建议:
- 在
onWorkerStart里注册一个Timer::add(5, function () { ... }),每 5 秒扫描一次lrange ws:offline:* 0 10并尝试投递 - 投递前先查接收方当前在线连接:
$connections = \Webman\Connection\Manager::all();,匹配$conn->uid === $to_uid - 投递成功后用
lrem ws:offline:{$to_uid} 0 $msg_json清除已发消息;失败则保留,等下次轮询
真正容易被忽略的是连接清理时机——用户刷新页面或关掉标签页时,onClose 不一定立刻触发,得靠心跳超时机制配合 Redis 过期时间(比如设 3 分钟),否则离线队列会越积越多。

















