Workerman推送断连主因是中间设备静默断开长连接,需双向启用心跳机制:服务端设HEARTBEAT_TIME=25秒并定时检测lastMessageTime,客户端心跳间隔须小于该值,推送前须校验连接真实存活状态。

Workerman主动推送信息时连接频繁断开,说明长连接未被有效维持,防火墙、NAT设备或服务端空闲超时正在 silently 断连已建立的 TCP 连接,客户端却无感知,导致后续推送失败。
确认连接断开的真实原因
先排除误判:不是客户端主动关闭,也不是服务端崩溃,而是连接在无数据传输一段时间后被中间设备(如云服务器安全组、企业防火墙、家用路由器)强制回收。这种断连不会触发 $worker->onClose,因为 FIN 包根本没发出来。
用 netstat -an | grep :端口号 在服务端检查连接状态,若大量连接处于 ESTABLISHED 但客户端收不到消息,基本可锁定为“假在线真断开”。
【必须做】在客户端和服务端双向启用心跳机制,否则所有推送都不可靠。
服务端实现可靠心跳检测
第一步:定义心跳常量(放在 Worker 初始化前)
define('HEARTBEAT_TIME', 25); // 心跳间隔设为25秒,必须小于MySQL wait_timeout和防火墙超时值
第二步:在 onMessage 中记录每次收到消息的时间戳
$worker->onMessage = function($connection, $msg) { $connection->lastMessageTime = time(); };
第三步:启动定时器,每秒扫描所有连接并踢掉超时者
$worker->onWorkerStart = function($worker) { Timer::add(1, function() use ($worker) { $now = time(); foreach ($worker->connections as $connection) { if (empty($connection->lastMessageTime)) { $connection->close(); continue; } if ($now - $connection->lastMessageTime > HEARTBEAT_TIME + 5) { $connection->close(); } }); } ); };
注意:这里加了 +5 容错窗口,避免网络抖动误判;若用 WebSocket 协议,还可配合 $connection->pong() 主动探测。
客户端保持心跳发送(以浏览器为例)
方法一:原生 WebSocket 发送字符串心跳包
const ws = new WebSocket('ws://your-domain.com:2346'); ws.onopen = () => { setInterval(() => { if (ws.readyState === WebSocket.OPEN) { ws.send('ping'); } }, 20000); };
方法二:发送二进制心跳(更轻量,避免被业务逻辑误解析)
ws.send(new Uint8Array([0x01])); // 单字节0x01作为心跳标识
【关键】客户端心跳间隔必须严格小于服务端 HEARTBEAT_TIME,否则服务端会先判定超时关闭连接。
推送前强制验证连接可用性
不要直接调用 $connection->send() 推送,先检查连接是否真实存活:
if ($connection && $connection->isConnected() && !empty($connection->lastMessageTime) && (time() - $connection->lastMessageTime) send($data); }
这个判断比单纯查 isConnected() 更可靠——后者只检测 socket 句柄是否有效,不反映网络通路是否通畅。
对 UID 映射推送场景,遍历前务必过滤掉已超时连接,否则 foreach ($uidConnections[$uid] as $conn) 会向死连接反复写入,触发 EPIPE 错误并拖慢整个循环。

















