用onMessage通过类型标识区分三类数据:握手包设$connection->channel;指令用"cmd:"前缀或JSON cmd字段;画面帧用首字节标记(如0x01为H.264);须加双向心跳防假成功,多进程下用Redis Pub/Sub中转确保精准投递。

Workerman 本身不内置帧同步、编解码或桌面捕获能力,但作为底层 TCP/UDP 事件驱动容器,它非常适合做「指令流的低延迟转发中枢」——关键在于把控制指令、画面帧、输入事件三类数据严格分离、按需路由,而不是堆砌功能。
怎么用 onMessage 区分指令、鼠标键盘、画面帧三类数据?
不能靠协议自动识别,必须在应用层加类型标识。常见错误是把所有数据混在一个连接里发,导致画面帧阻塞指令下发(尤其在弱网下)。
- 客户端主动协商:连接建立后先发一个带
type字段的握手包,比如{"type":"control","version":"1.0"},服务端存到$connection->channel = 'control' - 指令通道固定前缀:如
"cmd:mouse_move,x=120,y=85"或{"cmd":"key_down","code":"Enter"},onMessage里用stripos($data, 'cmd:') === 0或json_decode($data, true)['cmd']快速分流 - 画面帧走独立连接或加首字节标记:比如首字节为
0x01表示 H.264 NALU,0x02表示 PNG 截图,避免 JSON 解析开销;客户端必须支持按帧请求关键帧(I帧),否则丢包后无法恢复
为什么 $connection->send() 发了但远程没收到?
因为 send() 只写入内核 socket 缓冲区,不保证送达。云游戏/远程桌面对时延敏感,这种“假成功”最容易掩盖真实问题。
- 典型表现:本地测试一切正常,局域网延迟稳定,一上公网(尤其跨运营商)就卡顿、指令延迟 2~3 秒,
onClose却没触发 - 必须加双向心跳:服务端用
Timer::add(15, function() { foreach ($worker->connections as $conn) $conn->send('{"ping":'.time().'}'); });,客户端收到后立刻回{"pong":1716492120} - 客户端超时未回(比如 30 秒),服务端主动
$conn->close();不要依赖 TCP keepalive,默认值太长(2 小时),根本来不及止损
多进程下怎么保证指令只发给目标终端?
Workerman 默认每个 Worker 进程只管理自己接受的连接,$worker->connections 是进程私有的。如果客户端 A 指令要转发给客户端 B,而 A、B 连在不同进程,直接遍历会漏发。
- 简单方案:强制单进程运行(
$worker->count = 1),适合政务自助机等终端数可控场景 - 生产方案:用 Redis Pub/Sub 中转指令,所有 Worker 进程订阅同一个 channel,A 发指令 → 写 Redis → 所有 Worker 收到 → 各自查本进程内是否有目标
$connection→ 有则send() - 别用全局数组缓存连接:PHP-FPM 模式下可行,但 Workerman 常驻内存,
static $all_connections = []在多进程下各进程独立,无法共享
真正难的不是转发,而是让每条指令在 16ms 内完成「接收→解析→路由→发送」闭环。任何数据库查询、JSON 大对象序列化、同步文件读写,都会拖垮帧率。指令处理逻辑必须是纯内存计算,状态靠 $connection->xxx 属性维护,别碰磁盘和网络 IO。


















