PHP实现游戏服务器端定时添加木材功能的技术求助
嘿,我来帮你解决这个问题!你现在的写法有几个关键问题,导致页面一直加载,而且根本没法正常工作——咱们一步步来理顺:
为什么你的当前代码会出问题?
你写的递归+sleep()的方式完全不符合PHP的运行逻辑:
sleep()是同步阻塞的:一旦调用,整个PHP进程会挂起10秒,期间不会响应任何请求,页面自然会一直处于加载状态,直到超时或者栈溢出。- 无限递归会耗尽内存:每次调用
Criar_Madeira()都会在内存里留下调用栈,次数多了直接触发致命错误,进程崩溃。 - PHP请求是短生命周期的:用户的HTTP请求结束后,PHP进程就会销毁,根本没法持续运行无限循环的定时任务。
正确的实现思路
服务器端处理玩家资源的周期性增长,核心是不要在用户请求里跑持续任务,而是用以下两种思路:
- 基于用户交互触发计算(最常用,适合Web游戏):记录最后一次资源更新的时间,每次用户打开页面/发起请求时,计算这段时间应该增长的资源量,一次性补上。
- 用独立的定时任务/常驻进程:如果需要玩家离线时也增长资源,就用系统定时任务或者异步进程来批量更新。
具体解决方案
方案一:基于用户交互的延迟计算(推荐)
这个方案不需要后台一直跑进程,安全又高效,而且所有计算都在服务器端,客户端没法篡改。
实现步骤:
- 在玩家数据库表中添加一个字段,比如
ultima_atualizacao_madeira,用来记录最后一次更新木材的时间戳。 - 每次用户打开游戏页面、发起资源相关请求时,执行以下代码:
function atualizarMadeira($pdo, $playerId) { // 获取当前时间戳 $agora = time(); // 从数据库获取玩家的当前木材和最后更新时间 $stmt = $pdo->prepare("SELECT madeira, ultima_atualizacao_madeira FROM jogadores WHERE id = ?"); $stmt->execute([$playerId]); $player = $stmt->fetch(PDO::FETCH_ASSOC); if (!$player) { return; // 玩家不存在,直接返回 } // 计算从上次更新到现在过去了多少秒 $tempoDecorrido = $agora - $player['ultima_atualizacao_madeira']; // 每10秒加500木材,计算有多少个完整的10秒周期 $periodosValidos = floor($tempoDecorrido / 10); if ($periodosValidos > 0) { // 计算应该添加的木材总量 $madeiraAdicional = $periodosValidos * 500; // 更新数据库:添加木材,同时把最后更新时间设为当前时间 $updateStmt = $pdo->prepare("UPDATE jogadores SET madeira = madeira + ?, ultima_atualizacao_madeira = ? WHERE id = ?"); $updateStmt->execute([$madeiraAdicional, $agora, $playerId]); } } // 调用示例(假设已经有PDO连接实例$pdo,玩家ID是$a[1]) atualizarMadeira($pdo, $a[1]);
这样不管玩家隔多久再来,都会自动补上这段时间应该获得的木材,完全不需要后台持续运行任务。
方案二:系统定时任务(Crontab)
如果需要玩家离线时也持续增长木材,可以写一个独立的PHP脚本,用系统的Crontab定时执行。
步骤:
- 写一个批量更新所有玩家木材的脚本(比如
update_madeira.php):
<?php // 连接数据库 try { $pdo = new PDO('mysql:host=localhost;dbname=seu_banco_de_dados', 'seu_usuario', 'sua_senha'); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // 给所有玩家添加500木材 $sql = "UPDATE jogadores SET madeira = madeira + 500"; $pdo->exec($sql); echo "木材更新完成\n"; } catch (PDOException $e) { die("数据库错误: " . $e->getMessage()); } ?>
- 配置Crontab,每10秒执行一次这个脚本:
因为Crontab最小的分钟级间隔是1分钟,要实现10秒一次,需要添加6条任务(每分钟内每10秒执行一次):
* * * * * php /caminho/completo/para/update_madeira.php * * * * * sleep 10; php /caminho/completo/para/update_madeira.php * * * * * sleep 20; php /caminho/completo/para/update_madeira.php * * * * * sleep 30; php /caminho/completo/para/update_madeira.php * * * * * sleep 40; php /caminho/completo/para/update_madeira.php * * * * * sleep 50; php /caminho/completo/para/update_madeira.php
方案三:用Swoole实现常驻进程定时器
如果你的服务器支持Swoole(PHP的异步扩展),可以写一个常驻进程,用定时器每隔10秒更新一次木材。这个方案适合需要实时更新的场景,但需要额外的进程管理。
示例代码:
<?php // 创建Swoole服务器(这里只是用来启动定时器,不需要监听端口也可以) $server = new Swoole\Server('127.0.0.1', 9501); // 进程启动时初始化定时器 $server->on('WorkerStart', function ($server) { // 每隔10秒(10000毫秒)执行一次更新任务 Swoole\Timer::tick(10000, function () { try { $pdo = new PDO('mysql:host=localhost;dbname=seu_banco_de_dados', 'seu_usuario', 'sua_senha'); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $sql = "UPDATE jogadores SET madeira = madeira + 500"; $pdo->exec($sql); echo date('Y-m-d H:i:s') . " - 木材更新完成\n"; } catch (PDOException $e) { echo date('Y-m-d H:i:s') . " - 错误: " . $e->getMessage() . "\n"; } }); }); // 启动服务器 $server->start();
启动这个脚本后,它会一直运行,每隔10秒自动更新所有玩家的木材。你可以用nohup或者守护进程工具来保证它持续运行。
总结
- 绝对不要在用户请求里用
sleep()和递归,这完全不符合Web环境的运行逻辑。 - 优先用方案一,实现简单、安全,不需要额外的服务器配置,适合绝大多数Web游戏。
- 如果需要离线增长资源,再考虑方案二或三。
内容的提问来源于stack exchange,提问作者VilmaH
相关产品推荐
相关产品推荐

