workerman 是 php 实现 h5 多人在线游戏后端最轻量、最可控的选择,但需手动实现会话管理、房间系统、消息鉴权、playerid绑定、输入校验、频率限制、精准广播、心跳检测与断线清理等全套逻辑。

Workerman 是 PHP 实现 H5 多人在线游戏后端最轻量、最可控的选择,但前提是别把它当“黑盒框架”用——它不自动管理玩家会话、不内置房间系统、不处理消息鉴权,所有这些都得你亲手补全。
WebSocket 连接建立后必须立刻绑定唯一 playerID
Workerman 的 $connection 对象本身不带身份信息,onConnect 触发时只是裸连接。如果跳过这步直接进游戏逻辑,会出现“谁在移动”“谁落子了”完全不可追溯的问题。
- 客户端首次发来的登录包(如
{"type":"login","token":"abc123"})必须校验有效性,再生成或复用一个服务端可控的playerId(推荐用uniqid('p_')或bin2hex(random_bytes(8))) - 把这个
playerId显式存进全局容器,比如$GLOBALS['players'][$playerId] = $connection,或更稳妥地用SplObjectStorage关联连接与玩家数据 - 绝对不要依赖
$connection->id做业务逻辑——它只在当前进程内唯一,Worker 进程重启或负载均衡下就失效
onMessage 中必须做输入过滤和频率限制
H5 游戏前端完全不可信,onMessage 收到的任何数据都可能是伪造的。不做校验会导致外挂横行:坐标瞬移、无限发弹、伪造胜利状态。
- 每个消息体必须含
playerId字段,且和服务端记录的连接匹配;否则直接$connection->close() - 对关键操作(如
{"type":"move","x":120,"y":85})检查数值范围(x/y 是否越界)、变化幅度(单次位移是否超 5 像素)、时间戳或 sequence ID 防重放 - 用
$_SERVER['REQUEST_TIME_FLOAT']记录上次合法操作时间,对同一playerId限制 move 每秒 ≤10 次,attack ≤3 次,避免刷屏攻击 - 拒绝解析非 JSON 格式、字段缺失、类型错误(如
x是字符串而非数字)的数据,不抛异常,直接丢弃
广播逻辑不能无差别 foreach($connections)
Workerman 的 $worker->connections 是全部活跃连接集合,但 H5 游戏里绝大多数场景需要的是“同房间”或“视野内”广播,全量转发既浪费带宽又暴露隐私。
- 必须自己维护房间映射表,例如
$rooms['room_abc'] = ['player_x', 'player_y'],并在玩家加入/退出/断线时同步更新 - 移动、攻击等消息只推给同房间其他玩家;系统公告类消息才走全服广播
- 广播前做数据精简:原始消息可能含调试字段、完整坐标、冗余时间戳,推送时只保留必要字段(如
{"type":"state","id":"p_123","x":120,"y":85,"ts":1716249021}) - 避免在
onMessage回调里嵌套循环广播——高并发下易阻塞事件循环;可考虑把广播任务压入Worker::$processPool异步队列(需自行封装)
心跳与断线检测必须手动实现
Workerman 不自动发 ping/pong,浏览器或 NAT 网关静默断开后,$connection 仍留在 $worker->connections 里,导致“假在线”、资源泄漏、广播错乱。
- 在
onConnect后立即启动定时器:$connection->send('{"type":"ping"}'),并设置$connection->lastPing = $_SERVER['REQUEST_TIME_FLOAT'] - 客户端需响应 pong,服务端在
onMessage中识别{"type":"pong"}并刷新lastPing - 另起一个
Timer::add()每 15 秒扫描所有连接,若$_SERVER['REQUEST_TIME_FLOAT'] - $connection->lastPing > 30,则$connection->close()并从房间/玩家表中清理 - 务必在
onClose回调里做清理动作,但不能只靠它——因为静默断连不会触发onClose
真正难的不是写通 WebSocket 连接,而是让每个 playerId 在整个生命周期里始终可追溯、可控制、可隔离;Workerman 给你的是 TCP 连接的控制权,不是游戏逻辑的免写权。











