
本文介绍一种基于定时轮询和状态比对的方案,用于在无法监听玩家离线事件的前提下,精准计算每位玩家在第三方游戏服务器上的累计在线时间。核心在于识别“重连重置计时”并增量更新总时长。
在多人在线游戏监控场景中(如 Steam、Arma、Minecraft 服务器等),我们常需统计玩家的累计在线时长,但受限于无法部署服务端日志钩子或 WebSocket 离线事件监听(例如非自有服务器、仅能通过 Valve Server Query 协议获取快照数据),直接累加 time 字段会导致严重误差——因为该字段是会话级递增秒数:玩家每次重连后归零重新计时。
解决方案的关键洞察是:time 值的突降即代表一次重连行为。因此,在每 5 分钟一次的 PHP 定时任务中,应按以下逻辑更新数据库:
// 假设 $query 来自 Server Query 的当前玩家快照
// $db_players 来自数据库中该玩家上一次记录
$time = (int)$query['time']; // 当前会话已在线秒数(例:1234)
$last = (int)$db_players['last_time']; // 上次记录的 time 值(例:2100)
$total = (int)$db_players['total_time']; // 数据库中累计总时长(例:8760)
// 判断是否发生重连:当前 time < 上次 time → 说明玩家已离线并重新加入
if ($time < $last) {
// 新会话开始,仅累加本次 time(因上次会话已结束,但未被显式捕获)
$new_total = $total + $time;
} else {
// 持续在线,累加本次与上次的差值
$new_total = $total + ($time - $last);
}
// 同步更新数据库
$stmt = $pdo->prepare("
INSERT INTO players (id, name, last_score, total_score, last_time, total_time)
VALUES (?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
name = VALUES(name),
last_score = VALUES(last_score),
total_score = VALUES(total_score),
last_time = VALUES(last_time),
total_time = VALUES(total_time)
");
$stmt->execute([
$query['id'],
$query['name'],
$query['score'],
$db_players['total_score'] + $query['score'], // 可选:同步累计得分
$time,
$new_total
]);⚠️ 注意事项:
- 初始值处理:首次插入玩家时,last_time 和 total_time 均设为 $time(即 $new_total = $time);
- 防并发冲突:若多实例 cron 同时运行,建议用 INSERT ... ON DUPLICATE KEY UPDATE 或行级锁(如 SELECT ... FOR UPDATE)保障原子性;
- 精度权衡:5 分钟轮询意味着单次会话最长可能漏计 ≤5 分钟(如玩家在两次查询间离线又未重连),属可接受误差范围;
- 异常过滤:可增加校验(如 $time > 86400 表示超 24 小时,大概率异常,跳过更新)提升鲁棒性。
此方法已被成功应用于 GameTracker 类服务,稳定支撑数十万玩家的实时排名与历史统计。其本质是将无状态快照建模为带状态迁移的时序事件流——无需服务端配合,仅靠客户端智能比对,即可逼近真实在线行为。










