必须建立 client_id 到 connection 的可复用映射关系以实现精准推送;客户端首次连接需发送 bind 消息绑定 ID;服务端通过 self::$client_map 索引并维护映射,onClose 时及时清理,否则导致消息错发或丢失。

要在 PHP 项目中实现基于客户端 ID 的精准消息推送,必须绕过 Workerman 默认的 connection_id 临时性缺陷,建立业务侧可识别、可复用的 ID 映射关系。直接调用 $connection->send() 向所有连接广播是无效的,因为目标用户可能不在线、连接已断开或未完成身份绑定。
初始化 WebSocket 服务并启用 WSS
使用 websocket://0.0.0.0:8282 启动服务,若需 HTTPS 加密通信(如微信小程序、H5 页面强制要求),必须配置 SSL 证书路径并改用 wss:// 协议。Workerman 不内置 HTTPS 支持,需通过 Nginx 反向代理或在 Worker 构造时传入 SSL 上下文参数。
执行命令启动服务:php start.php start -d。确保防火墙及云服务器安全组已放行 8282 端口。
【必须确认证书路径正确且私钥无密码,否则 WSS 握手失败,前端报错 net::ERR_CONNECTION_CLOSED】
客户端连接后绑定自定义 ID
客户端首次连接成功,立即发送 JSON 消息绑定业务 ID:
{"type":"bind","client_id":"user_10086"}
服务端在 onMessage 中解析该消息,检查 $data['type'] === 'bind' 后执行三步操作:
① 若该 client_id 已存在映射,先从 self::$client_map 中移除旧连接,避免多端登录冲突导致消息错发;
② 将当前 $connection 实例写入 self::$client_map[$client_id] = $connection;
③ 同时为连接对象挂载属性 $connection->client_id = $client_id,便于后续 onClose 或心跳检测时反查。
这一步不做绑定,后续任何 push 请求都无法定位目标连接——self::$client_map 是整个精准推送逻辑的唯一索引表。
后台触发单点精准推送
方法一:HTTP 接口触发(推荐用于管理后台)
提供一个普通 PHP 脚本(如 push_api.php),接收 GET 参数:?target_id=user_10086&content=订单已支付;
脚本内直接读取 Events::$client_map['user_10086'],若存在则调用 $connection->send(json_encode(['msg'=>$content]));
若不存在,说明目标用户未在线或未完成 bind,不报错也不重试——这是业务层应处理的离线逻辑。
方法二:内部 TCP 文本协议推送(适合高并发指令流)
另起一个 text://0.0.0.0:5678 内部监听 Worker,接收格式为 {"target_id":"user_10086","content":"系统通知"} 的纯文本;
解析后走同一条映射查找路径,成功即发,失败即丢弃;
【严禁在此处尝试重连或队列持久化——Workerman 进程内无共享内存,无法跨进程访问 $client_map】
连接异常时自动清理映射
第一步:在 onClose 回调中遍历 self::$client_map,比对每个 value 是否等于当前关闭的 $connection;
第二步:一旦匹配,用 array_keys(self::$client_map, $connection)[0] 取出 key,再执行 unset(self::$client_map[$key]);
第三步:同时清除连接对象上的 $connection->client_id 属性(可选,防内存泄漏);
这一步漏掉会导致映射表持续膨胀,相同 client_id 多次登录后只保留最后一次连接,历史连接仍占位却无法通信,最终出现「明明在线却收不到消息」的故障。

















