onmessage第一行必须无条件更新lastmessagetime;定时器检查需先isconnected()再判断超时;心跳阈值应按链路最短环节设定,客户端间隔为服务端阈值的0.5倍;onclose须统一清理所有关联资源。

onMessage里必须无条件更新lastMessageTime
客户端发来的任何原始数据,不管是不是合法JSON、有没有type字段、解析会不会报错,第一行就得更新时间戳。否则{"type":"ping"}因JSON decode失败被return,$connection->lastMessageTime就卡在旧值不动,后续检查直接误判超时。
正确写法是:
$worker->onMessage = function($connection, $msg) {
// 第一行就更新,不加任何判断
$connection->lastMessageTime = time();
<pre class="brush:php;toolbar:false;"><pre class="brush:php;toolbar:false;">// 后续再做业务解析
$data = json_decode($msg, true);
if (json_last_error() !== JSON_ERROR_NONE) {
return;
}
if ($data['type'] === 'ping') {
$connection->send(json_encode(['type' => 'pong']));
return;
}
// 其他业务逻辑...};
定时器检查必须带isConnected()防护
Workerman的Timer::add()
onClose),此时直接调$connection->close()会抛warning甚至崩溃。检查逻辑必须加两层防护:
- 用
$connection->isConnected()确认连接当前可操作 - 用
time() - ($connection->lastMessageTime ?? 0) > $timeout判断是否真超时
示例:
<pre class="brush:php;toolbar:false;">Timer::add(45, function() use ($worker, $timeout = 90) {
$now = time();
foreach ($worker->connections as $connection) {
// 必须先检查连接状态,再读时间戳
if (!$connection->isConnected()) {
continue;
}
$last = $connection->lastMessageTime ?? $now;
if ($now - $last > $timeout) {
$connection->close();
}
}
});心跳超时阈值不能硬编码为60秒
60秒是Nginx默认proxy_read_timeout
实际应按最短链路环节定阈值:
- 纯内网部署:设75秒(留15秒缓冲)
- 经Nginx+公网:设45秒(严守NAT最短超时)
- IoT模组直连:查模组AT固件文档,常见为90秒,对应服务端设150秒
客户端心跳间隔要严格小于服务端阈值,建议按阈值 × 0.5设置,比如服务端用45秒,客户端ping就设25秒。
close()前别漏掉资源清理逻辑
单纯调$connection->close()只断TCP,但若该连接绑定了UID或关联了Redis订阅、数据库句柄、临时文件等,这些资源不会自动释放,容易引发内存泄漏或句柄耗尽。
务必在onClose里补全清理,且要和心跳清理路径保持一致:
<pre class="brush:php;toolbar:false;">$worker->onClose = function($connection) {
// 清理绑定关系
if (isset($connection->uid)) {
Gateway::unbindUid($connection->id, $connection->uid);
}
// 清理自定义属性(避免下次复用时残留)
unset($connection->lastMessageTime, $connection->uid, $connection->device_id);
};注意:如果用了GatewayWorker
onClose里不要手动close(),它会自动处理;纯Worker模式才需要自己关。真正难的不是写close()这行代码,而是所有路径——onClose、心跳定时器、异常中断捕获——都得统一清理同一套状态,稍有遗漏,假连接就会在$worker->connections里静默堆积,直到Worker::$maxConnection被占满。











