Hello Stack Overflow,
我正在构建一个基于浏览器的文本只多人RPG写在PHP棘轮为骨干。
我目前所拥有的:它工作得很好。我已经实现了一个简单而有效的命令解释器,它可以很好地在客户机和服务器之间传输数据。我可以很容易地执行数据库操作,并在我的服务器类中实例化外部类,以便将信息传递回客户端。
我被困的地方:出于某种原因,我的大脑在尝试执行滴答声时崩溃了,这在我的游戏环境中是指每45秒发生一次的一系列事件。它基本上是游戏的心跳,如果没有可靠而优雅的执行,我就无法前进。tick需要做很多事情,包括(但不限于):向玩家发送消息、更新玩家回复、内存处理等等。一般来说,所有这些操作都可以被编码并放置在Update类中。
但是我不知道如何让蜱虫真正发生。滴答本身,只是一个在我的反应循环中每45秒发生一次的函数,它应该在服务器启动时启动。它绝对需要在服务器端。从技术上讲,我可以在客户端实现它,并与数据库中的值同步,但我不想走那条路。
我觉得这应该比我的大脑想象的要容易。
我试过了:我尝试运行一个简单的递归函数,使用sleep(45)在计时器上构造我的更新类,但是,这需要在服务器启动时启动,如果我在服务器类的构造中抛出无限循环函数,启动脚本永远不会通过,游戏永远不会启动。
我试过使用onPeriodicTimer函数,与反应,但我不知道如何实现它。
我尝试过一些疯狂的事情,比如使用node js每45秒向我的服务器发送一条消息,我的解释器捕捉到这个特定的消息并启动tick进程。这是我最接近成功实现的,但我真的希望能够做到这一点,而不需要客户端连接并与服务器通信,这似乎很糟糕。
我已经尝试过ZeroMQ来实现与上面相同的目标(一个客户端发送消息到我的服务器,触发更新),但再次,我不想有一个客户端监听器不断连接游戏运行,而且,ZeroMQ是很多处理这么小的事情。我运气不好。
一定有更好的方法来实现这一点。如有任何帮助,不胜感激。
作为参考,这里是我的套接字应用程序工作的基本轮廓。首先,我使用了Ratchet网站上的"Hello World"教程。
所以我有一个startup.php脚本,我运行它来初始化Server类,它接受来自连接的客户端的消息。onMessage,一个解释器类被实例化,它解析消息并查找客户端在数据库表中传递的命令,该数据库表为该命令加载相应的类和方法,该数据基于onMessage函数,命令的类和方法被调用,结果被传递回客户端。
TLDR:我如何在棘轮websocket服务器上添加一个重复的功能,可以每45秒向连接的客户端发送消息?
下面是Server类: class Server implements MessageComponentInterface
{
public $clients;
public function __construct()
{
$this->clients = new SplObjectStorage;
//exec("nodejs ../bin/java.js", $output);
}
public function onOpen(ConnectionInterface $conn)
{
$conn->connected_state = 0;
$this->clients->attach($conn);
// Initiate login
$login = new Login('CONN_GETNAME');
if($login->success)
{
$conn->send($login->output);
$conn->connected_state = $login->new_state;
$conn->chData = new Character();
}
echo "New connection! ({$conn->resourceId})n";
}
public function onMessage(ConnectionInterface $from, $msg)
{
if($msg == 'do_tick')
{
echo "a tick happened <br>";
}
else
{
if($from->connected_state == 'CONN_CONNECTED' || $msg == 'chardump')
{
$interpretor = new Interpret($msg);
if($interpretor->success)
{
$action_class_var = $interpretor->class;
$action_method_var = $interpretor->function;
$action_class = new $action_class_var($this->clients, $from, $interpretor->msg);
$action = $action_class->{$action_method_var}();
foreach($this->clients as $client)
{
if($action->to_room)
{
if($from != $client)
{
$client->send($action->to_room);
}
}
if($action->to_global)
{
if($from != $client)
{
$client->send($action->to_global);
}
}
if($action->to_char)
{
$client->send($action->to_char);
}
}
}
else
{
$from->send('Huh?');
}
}
else
{
$login = new Login($from->connected_state, $msg, $from);
$from->connected_state = $login->new_state;
if($login->char_data && count($login->char_data)>0)
{
foreach($login->char_data as $key=>$val)
{
$from->chData->{$key} = $val;
}
}
$from->send($login->output);
}
}
}
public function onClose(ConnectionInterface $conn) {
$this->clients->detach($conn);
echo "Connection {$conn->resourceId} has disconnectedn";
}
public function onError(ConnectionInterface $conn, Exception $e) {
echo "An error has occurred: {$e->getMessage()}n";
$conn->close();
}
也许一个onTick函数添加到这个类得到调用每X秒?这可能吗?
要以45秒的间隔(或任何其他数字)向每个人广播消息,您必须控制Ratchet使用的事件循环。
你需要添加一个定时事件,不同的供应商称其为定时事件,计时器事件,可重复事件,但它的行为总是相同的-一个函数在X时间后触发。
你要找的类在这个链接
或者,你可以用冰柱代替棘轮。我个人更喜欢它,我没有什么特别的理由——在我看来,这两个库都很出色,有一个选择总是好的。
有趣的是,你尝试使用ZeroMQ——它是一个传输层,绝对是我用过的最好的库/项目之一。它与事件循环配合得很好,对于开发分布式系统、作业队列和类似的东西来说,它绝对很有趣。
祝你游戏顺利!如果您对WS、扩展到多台机器或类似的问题有任何其他问题,请随时在此答案下方的评论中与我联系。
谢谢你,N.B.!
对于任何可能陷入类似情况的人,我希望这能帮助到别人。我甚至不知道应该搜索哪些术语来找到问题的根源,正如我最初问题下面的评论所证明的那样,我因为不够"具体"而受到批评。有时候,如果你不完全确定自己在找什么,就很难提出问题!
这是游戏的启动脚本现在的样子,我已经测试了一个实现的"tick"循环。
<?php
use RatchetMessageComponentInterface;
use RatchetConnectionInterface;
use RatchetServerIoServer;
use RatchetHttpHttpServer;
use RatchetWebSocketWsServer;
use ReactSocketServer as Reactor;
use ReactEventLoopFactory as LoopFactory;;
require dirname(__DIR__) . '/vendor/autoload.php';
foreach(new DirectoryIterator(dirname(__DIR__) .'/src/') as $fileInfo)
{
if($fileInfo->isDot() || $fileInfo->isDir())
{
continue;
}
require_once(dirname(__DIR__) . '/src/' . $fileInfo->getFilename());
}
$clients = null;
class Server implements MessageComponentInterface
{
public function __construct(ReactEventLoopLoopInterface $loop)
{
global $clients;
$clients = new SplObjectStorage;
// Breathe life into the game
$loop->addPeriodicTimer(40, function()
{
$this->doTick();
});
}
public function onOpen(ConnectionInterface $ch)
{
global $clients;
$clients->attach($ch);
$controller = new Controller($ch);
$controller->login();
}
public function onMessage(ConnectionInterface $ch, $args)
{
$controller = new Controller($ch, $args);
if($controller->isLoggedIn())
{
$controller->interpret();
}
else
{
$controller->login();
}
}
public function onClose(ConnectionInterface $conn)
{
global $clients;
$clients->detach($conn);
echo "Connection {$conn->resourceId} has disconnectedn";
}
public function onError(ConnectionInterface $conn, Exception $e)
{
echo "An error has occurred: {$e->getMessage()}n";
$conn->close();
}
public function doTick()
{
global $clients;
$update = new Update($clients);
}
}
$loop = LoopFactory::create();
$socket = new Reactor($loop);
$socket->listen(9000, 'xx.xx.xx.xxx');
$server = new IoServer(new HttpServer(new WsServer(new Server($loop))), $socket, $loop);
$server->run();