PHP:游戏循环(线程或排序)
我正在编写 PHP 代码作为游戏客户端。它使用套接字; socket_create,然后是socket_connect,然后是socket_read。它工作正常,但问题是服务器可以随时发送数据包,这意味着 socket_read 需要在“游戏循环”中不断发生。所以像这样的事情:
<?php
$reply = "";
do {
$recv = "";
$recv = socket_read($socket, '1400');
if($recv != "") {
$reply .= $recv;
}
} while($recv != "");
echo($reply);
?>
不起作用,因为它陷入了循环(服务器不会终止连接,直到客户端退出游戏)并且 PHP 代码需要处理传入的数据包内容。
所以 PHP 并不真正有线程。处理这个问题的最佳方法是什么?
I am writing PHP code to be a game client. It uses socket; socket_create followed by socket_connect and then socket_read. It works fine, but the issue is that the server can send a packet at any time which means socket_read needs to be happening constantly in a "game loop". So something like this:
<?php
$reply = "";
do {
$recv = "";
$recv = socket_read($socket, '1400');
if($recv != "") {
$reply .= $recv;
}
} while($recv != "");
echo($reply);
?>
Doesn't work because it's stuck in the loop (server doesn't terminate connection until game is quit by client) and the PHP code needs to handle the packet stuff as it comes in.
So PHP doesn't really have threading. What's the best way of handling this?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
不要编写这个臭气熏天的阻塞轮询循环,而是查看一些基于反应器模式的事件系统,例如 Python Twisted 或 Ruby EventMachine。
我相信 PHP 风格称为 PHP-MIO: http://thethoughtlab.blogspot.com/2007/04/non-blocking-io-with-php-mio.html
Instead of writing this smelly blocking polling loop, check out some event system based around the reactor pattern like Python Twisted or Ruby EventMachine.
I believe the PHP flavor is call PHP-MIO: http://thethoughtlab.blogspot.com/2007/04/non-blocking-io-with-php-mio.html
您所要做的就是使用
socket_select()
函数:http://php.net/manual/en/function.socket-select.php
它会让你的脚本进入睡眠状态,并在套接字上有数据要读取时唤醒它。它比定期睡眠/读取、cron 脚本和所有其他建议的解决方案更有效。
@aib 提出了一个有效的观点。服务器可能会发送一个完整的“游戏消息”,分为几个数据包。不要期望在
socket_select()
返回后在一次代码块执行中获取所有数据。All you have to do is to use
socket_select()
function:http://php.net/manual/en/function.socket-select.php
It will put your script to sleep and wake it up when there is data on the socket to be read. It's waaay more efficient that periodical sleep/read, cron scripts and all other proposed solutions.
@aib made a valid point. The server might sent a complete "game message" divided into several packets. Dont expect to get all your data in a singe exceution of code block after
socket_select()
returns.PHP 没有多线程,因此您应该真正考虑使用更合适的语言(就像 Andrey 在其评论中提到的那样)。
PHP has no multithreading, so you should really consider to use a more suitable language (like Andrey mentioned in its comment).
TCP 实现倾向于对消息进行分段和连接;无法得知套接字接收到的数据量或消息片段将返回多少。您需要知道消息在哪里结束以及新消息在哪里开始(这可能在单次读取返回的数据中多次发生)。一些简单的解决方案:
开始,结束消息。
PHP 的 XML 解析器不喜欢不完整的 XML,因此除非您想手动匹配开始和结束标记,否则第三个选项就不再适用。如果协议基于 ASCII,则使用第一个选项;如果协议是二进制的,则使用第二个选项;如果协议已经是 XML,则使用第三个选项。
现在,请记住每个数据包可以获取任意数量的消息。在最复杂的情况下,您可能会在单个数据包中看到较早消息的结尾,后跟许多完整消息以及另一条消息的开头。
完整的解决方案将遵循以下原则:
...尽管这是一个异步消息循环。我将把“如果有可用消息,则返回它;否则,等待消息”同步实现作为练习。 (提示:您需要一个类来构建和缓冲消息。)
TCP implementations tend to fragment and join messages; there's no telling how much data or how many message fragments a socket receive will return. You need to know where a message ends and a new one begins (which may happen multiple times in data returned by a single read). Some simple solutions:
<message>
starts and</message>
ends a message.PHP's XML parser doesn't like incomplete XMLs, though so the third option is out unless you want to match the start and end tags manually. Use the first option if the protocol is based on ASCII, second if it's binary, third if it's already XML.
Now, remember you can get any number of messages per packet. In the most complex case, you might have the end of an earlier message followed by a number of full messages and the beginning of yet another message in a single packet.
A full solution would be along these lines:
...though this is an asynchronous message loop. I'll leave the "if there's a message available, return it; else, wait for one" synchronous implementation as an exercise. (Hint: You'll need a class to build and buffer messages.)
可以做到,但我同意@Andrey和@DampeS8N,不是最好的选择。如果您决心这样做,请查看这本书:您想要做什么 /em> 使用 PHP?
Can be done, but I agree with @Andrey and @DampeS8N, not the best choice. If you are dead set on doing this, check out this book: You want to do WHAT with PHP?
如果你真的想这样做,你必须休眠一段时间,检查套接字,再次休眠,检查套接字...
要在不阻塞的情况下检查套接字,你需要使用非阻塞 I /O 您可以使用
socket_set_nonblock()
或socket_recv()
它有一个 DONTWAIT 标志。If you really want to do this, you have to sleep for some time, check the socket, sleep again, check the socket...
To check the socket without blocking you need to use non-blocking I/O which you can achieve with the
socket_set_nonblock()
orsocket_recv()
which has a DONTWAIT flag.基本上任何软件平台都会遇到这个问题。正如您所发现的,大多数情况都是通过线程来解决的。虽然 PHP 中可以使用线程。它需要 MAJORHAXXX。例如从 php.ini 中启动命令行 php 线程。
最终结果确实并不理想。
然而,还有其他方法可以解决这个问题。
但您需要先检查此列表中的所有标记:
[] - 我的游戏不需要不断检查服务器,例如玩家位置或复杂的动作。超出聊天室级别的数据传输和更新速率的任何内容都不应选中此框。
[] - 我的游戏不需要服务器告诉我任何事情。客户提出任何需要的请求是完全可以接受的,也许每秒一次,或者更好的是每分钟一次。
[] - 我的游戏不需要在服务器上持续模拟复杂的世界,其运行时间不需要超过完成请求所需的时间。跟踪聊天是一回事,进行物理和图形修改是另一回事。
如果您选中了所有这些框,那么 PHP 仍然在游戏中!否则。别打扰。
基本上,我在这里想说的是,PHP 非常适合非多人游戏、回合制游戏或至少互动性不强的游戏。但一旦你不得不在没有播放器的情况下让事情继续进行,PHP 就会崩溃。
VOODOO LEVEL
但如果你只是必须这样做。有一些方法可以解决这个问题。
A - 创建一个运行您的世界的 PHP 守护进程,将所有其他流量传输到与数据库交互的 getter 或 setter 请求文件。因此,您可能请求获取游戏世界状态,或设置玩家执行的值。所有其他与游戏世界相关的事情都可以由守护进程处理,并且游戏本身发生在数据库中。
B - 使用 cron,而不是守护进程。 (危险,但我们已经将您确定为冒险者,对吧?)
C - 仅尝试使用守护进程并侦听套接字,然后发送线程(通过 exec())进行响应。有点像上面 AndreKR 的想法,只是你不需要睡觉。这里的问题是你几乎总是会丢失东西或以其他方式被切断。如果守护进程以某种方式运行两次,整个事情可能会爆炸。
Basically any software platform is going to butt up against this problem. Most, as you've figured out, solve it with threading. While threading IS possible in PHP. It requires MAJORHAXXX. Such as launching a commandline php thread from within php.
It really doesn't end up being ideal.
However, there are other ways to get around this.
But you need to check ALL the marks on this list first:
[] - My game doesn't need to constantly keep checking the server, such as for player locations or complex movements. Anything beyond a chat-room level of data transfer and update rates should leave this box un-checked.
[] - My game doesn't need to be told BY THE SERVER anything. It is perfectly acceptable for the client to ask for anything it needs, perhaps once a second or better off once a minute.
[] - My game doesn't need to keep a constant simulation of a complex world running on the server for longer than it takes to complete a request. Tracking chat is one thing, doing physics and graphics modifications is another.
If you checked all of these boxes, then PHP is STILL IN THE GAME! Otherwise. Don't bother.
Basically, what I am saying here is that PHP is great for games that aren't really multiplayer, and that are turn-based or at least not very interactive. But once you have to keep things going without the player, PHP falls on its face.
VOODOO LEVEL
But if you simply MUST do this. There ARE ways to get around it.
A - Create a PHP Daemon that runs your world, pipe all other traffic to either a getter or setter request file that interacts with the database. So, you might request a getting of the game world state, or set a value that the player performed. All other game-world related things can be handled by the daemon and the game itself takes place in the database.
B - Use cron, not a Daemon. (dangerous, but we already established you as a risk taker, right?)
C - TRY only a Daemon and listening to sockets, then sending out threads (via exec()) to respond. Kind of like AndreKR's idea above, only you don't need to sleep. Problem here is you will almost always end up missing stuff or otherwise getting cut off. And the whole thing might explode if the Daemon get's run twice somehow..