Workerman的TCP心跳需应用层自行实现,heartbeat_idle_time仅被动检测有通信历史的连接空闲时长,无法覆盖静默连接;标准方案是客户端定时发ping、服务端onMessage回pong并手动更新lastPingTime,同时配合定时扫描踢出假死连接。

Workerman 的 TCP 心跳必须由应用层自己实现,Workerman 本身不提供自动心跳机制。 它只负责连接的收发和维持,超时断开靠 connection_timeout 和 heartbeat_idle_time 配合手动心跳逻辑完成。
为什么不能只靠 heartbeat_idle_time
很多人以为设了 heartbeat_idle_time = 60 就能自动保活,其实它只起“被动检测”作用:只有当客户端发过数据、服务端收到后才开始计时;如果客户端从不发任何包(比如纯监听型设备),这个计时器压根不会启动,连接会一直挂着直到被系统 kill 或防火墙中断。
-
heartbeat_idle_time不是心跳发送间隔,而是“最后一次收/发数据后允许空闲的最大秒数” - 它只在连接有通信历史的前提下生效,静默连接不受控
- 仅靠它无法解决 NAT 超时、中间设备丢连接等问题
标准做法:客户端定时发 ping,服务端用 onMessage 回 pong
这是最通用、兼容性最好的方式。客户端每 30 秒发一次 ping(可自定义字符串或二进制标记),服务端收到后立即回一个 pong 并刷新连接活跃时间。
use Workerman\Worker;
$worker = new Worker('tcp://0.0.0.0:1234');
$worker->onMessage = function($connection, $data) {
// 简单判断是否为心跳包(按实际协议约定)
if ($data === "ping\n" || $data === "\x01") {
$connection->send("pong\n");
// 强制刷新心跳计时器(关键!)
$connection->lastPingTime = time();
return;
}
// 处理业务数据...
};- 务必在收到
ping后手动更新$connection->lastPingTime,否则heartbeat_idle_time不会重置 - 客户端要处理
pong超时(比如发完ping5 秒没回就主动断连重连) - 避免用 JSON 或复杂解析做心跳判断,增加 CPU 开销且易出错
服务端也要主动踢掉假死连接
光等客户端发 ping 不够保险。得配合 onClose + 定时扫描,清理长时间没发任何数据的连接。
$worker->onWorkerStart = function($worker) {
// 每 10 秒扫一次
\Workerman\Lib\Timer::add(10, function() use ($worker) {
foreach ($worker->connections as $connection) {
if (time() - $connection->lastPingTime > 90) {
$connection->close();
}
}
});
};-
lastPingTime需在onConnect初始化(如$connection->lastPingTime = time();),否则首次判断可能为 null 导致误杀 - 扫描间隔建议 ≤
heartbeat_idle_time的 1/3,太长会导致假死连接滞留 - 别在
onMessage里直接调$connection->close(),容易引发并发异常,用定时器异步处理更稳
真正难的不是写几行 ping/pong,而是把心跳和业务协议对齐、覆盖所有异常路径(比如客户端崩溃没发 FIN、网络闪断后重连未清旧连接)、以及让客户端和服务端的心跳超时策略互相咬合。漏掉任意一环,都可能在线上表现为“连接明明还在,消息却收不到”。

















