跳转到主内容
websoft网络软件专家 - 深耕网络技术,打造实用软件!

怎么用Workerman开发多玩家在线杀人类游戏(如狼人杀)的语音发言麦序控制?

语音麦序控制本质是「状态同步 + 请求排队」,即服务端用Redis原子命令强一致管理发言权队列与持麦者,禁止前端绕过调度直连媒体,Workerman仅负责信令调度而非音频传输。 语音麦序控制本质是「状态同步 + 请求排队」,不是实时音频转发 Workerman 本身不处理音频编解码或 WebRTC 信令,它只负责在 PHP 层维护玩家发言权的状态机。所谓“麦序”,就是让
player_id
按顺序获得
is_mic_open
权限,且同一时间最多 1 人(或预设 N 人)可发言。关键不在“传声音”,而在“谁被允许发”——这个决策必须由服务端强一致控制,客户端只能申请、等待、接收指令。 常见错误现象:
前端直接调用 navigator.mediaDevices.getUserMedia() 后就推流,服务端没拦截;多个玩家同时说话导致混音爆炸;抢麦后状态不同步,A 看到自己已开麦,B 却收不到 A 的音频
。这些都不是网络或音频问题,而是状态没管住。 所有麦序操作必须走 Workerman 的
onMessage
回调,禁止前端绕过服务端直连媒体服务器 使用
$_SESSION
或
Redis
存储房间级麦序队列(推荐 Redis,因需跨进程共享),不能仅靠 PHP 进程内数组 每个房间对应一个独立的
mic_queue
队列(如 Redis key:
room:1001:mic_queue
)和一个
mic_holder
(当前持麦者
player_id
) 客户端发起
request_mic
消息时,服务端检查是否已在队列中;若未在,则
LPUSH
到队列尾部,并返回排队位置(如 “你在第3位”) 用 Redis 实现原子性麦序调度,避免并发冲突 PHP 进程间无法共享内存,而 Workerman 默认多进程运行,
array_shift()
这类操作在多个 Worker 进程里并发执行会丢状态。必须用 Redis 的原子命令做队列管理,否则会出现“两人同时被分配到麦”或“跳过某人”的情况。 示例流程(服务端收到
request_mic
): Workerman 最新主版本,继续强化高性能异步网络能力,适合 WebSocket、HTTP 服务、长连接、IM、IoT 等场景。 本次版本主要围绕稳定性、协议兼容以及连接处理能力优化,依旧保持超轻量和纯 PHP 特性。 适用于 PHP 8.x 环境,可配合 webman、GatewayWorker 等生态使用。 下载
if ($data['type'] === 'request_mic') { $room_id = $data['room_id']; $player_id = $data['player_id']; // 原子性:只有未在队列中才入队 $queue_key = "room:{$room_id}:mic_queue"; $in_queue = $redis->sIsMember("room:{$room_id}:mic_set", $player_id); if (!$in_queue) { $redis->rPush($queue_key, $player_id); $redis->sAdd("room:{$room_id}:mic_set", $player_id); $pos = $redis->lLen($queue_key); $connection->send(json_encode(['type' => 'mic_queued', 'position' => $pos])); } }
用
sAdd
+
sIsMember
防止重复入队,比单纯
rPush
更可靠 释放麦时(
release_mic
),先
LPOP
获取下一位,再
DEL
当前
mic_holder
,并广播
mic_granted
给新持麦者 超时自动释放:给每个
mic_holder
设置
EXPIRE
(比如 60 秒),避免玩家断线后麦一直被占 前端必须严格遵循「申请 → 等待 → 授权 → 开始采集」四步,不能提前调用 getUserMedia 很多开发者让前端一进房间就执行
navigator.mediaDevices.getUserMedia()
,结果服务端还没批准就已在本地采集音频,造成资源浪费和隐私风险。正确做法是:只在收到服务端
mic_granted
后才启动采集,并立刻绑定到指定
RTCPeerConnection
或 WebSocket 二进制通道。 收到
{type: "mic_granted", player_id: "p203"}
后,再调用
getUserMedia({audio: true})
,并立即创建
MediaRecorder
或编码后发往媒体中继节点 前端需监听自身连接断开、页面隐藏等事件,主动发
release_mic
,否则服务端依赖心跳或超时清理,延迟高 UI 上的“举手”按钮点击后,应禁用并显示“已申请(第X位)”,而不是反复发送请求 不要在
setInterval
里轮询麦序状态——用服务端主动推送(Workerman 的
$connection->send()
)替代 音频实际传输必须交给专门媒体服务,Workerman 只做调度中枢 Workerman 是 IO 多路复用模型,适合长连接信令,但不适合持续收发音频帧(尤其 Opus 编码后的二进制流)。强行用它做音频中继会导致 CPU 暴涨、延迟飙升、Worker 进程卡死。 语音流走独立路径:WebRTC 直连(P2P 或 SFU)、或专用媒体服务(如 Mediasoup、Janus)——Workerman 只负责把
player_id
和
room_id
透传给它们的信令接口 Workerman 要做的唯一音频相关事,是当
mic_granted
时,调用外部 API 触发媒体服务为该玩家开启上行流权限(例如 POST 到
/api/room/1001/player/p203/enable_audio
) 如果坚持全栈 PHP,至少把音频帧转成 Base64 后通过 WebSocket 发送——但这仅适用于极低码率、演示用途,不可用于真实狼人杀场景 真正难的不是代码怎么写,而是厘清边界:Workerman 管“谁能说”,不管“怎么说”;音频格式、编解码、抖动缓冲、NAT 穿透,这些都得交出去。一旦混淆,后面全是坑。

相关文章