【问题标题】:PHP: game loop (threads or the sort)PHP:游戏循环(线程或排序)
【发布时间】:2011-05-17 23:08:28
【问题描述】:

我正在编写 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 并没有真正的线程。处理此问题的最佳方法是什么?

【问题讨论】:

  • 我不认为 PHP 是客户端技术的最佳选择。考虑 Python、Java 或 .net。
  • 是的,我知道,我还没准备好。客户端已经用 Java 编写,我正在重写。试图决定一种语言。你在 Python 中有类似的例子吗?

标签: php multithreading sockets game-loop


【解决方案1】:

不要编写这种臭名昭著的阻塞轮询循环,而是查看一些基于反应器模式的事件系统,例如 Python Twisted 或 Ruby EventMachine。

我相信 PHP 风格是调用 PHP-MIO:http://thethoughtlab.blogspot.com/2007/04/non-blocking-io-with-php-mio.html

【讨论】:

    【解决方案2】:

    你所要做的就是使用socket_select()函数:

    http://php.net/manual/en/function.socket-select.php

    当套接字上有要读取的数据时,它将使您的脚本进入睡眠状态并唤醒它。它比定期睡眠/阅读、cron 脚本和所有其他建议的解决方案更有效。

    @aib 提出了一个有效的观点。服务器可能会发送一个完整的“游戏消息”,分为几个数据包。不要期望在socket_select() 返回后,在一次代码块中获取所有数据。

    【讨论】:

      【解决方案3】:

      TCP 实现倾向于分段和连接消息;不知道套接字接收将返回多少数据或多少消息片段。您需要知道一条消息在哪里结束,而一条新消息从哪里开始(这可能在一次读取返回的数据中发生多次)。一些简单的解决方案:

      • 使用某种分隔符。以“\0”结束每条消息。
      • 随消息一起发送消息大小。以“Content-length: 42\n”或两个大小字节 (0x00 0x42) 开始每条消息。
      • 使用 XML。 &lt;message&gt; 开始,&lt;/message&gt; 结束一条消息。

      PHP 的 XML 解析器不喜欢不完整的 XML,因此第三个选项不可用,除非您想手动匹配开始和结束标记。如果协议基于 ASCII,则使用第一个选项,如果是二进制则使用第二个选项,如果它已经是 XML,则使用第三个选项。

      现在,请记住,每个数据包可以接收任意数量的消息。在最复杂的情​​况下,您可能会在一个数据包中包含一个较早消息的结尾,然后是多个完整消息和另一个消息的开头。

      完整的解决方案将遵循以下思路:

      while (connected) {
          while (messages in buffer < 1) {
              read from socket;
              add to buffer;
          }
      
          while (messages in buffer > 0) {
              extract message from buffer;
              process message;
          }
      }
      

      ...虽然这是一个异步消息循环。我将把“如果有消息可用,返回它;否则,等待一个”同步实现作为练习。 (提示:您需要一个类来构建和缓冲消息。)

      【讨论】:

        【解决方案4】:

        可以,但我同意@Andrey 和@DampeS8N,不是最佳选择。如果你下定决心要这样做,看看这本书:You want to do WHAT with PHP?

        【讨论】:

          【解决方案5】:

          基本上任何软件平台都会解决这个问题。大多数,正如你所知道的,用线程解决它。虽然在 PHP 中可以使用线程。它需要 MAJORHAXXX。比如从 php 中启动一个命令行 php 线程。

          结果真的不是很理想。

          但是,还有其他方法可以解决这个问题。

          但您需要先检查此列表中的所有标记:

          [] - 我的游戏不需要不断检查服务器,例如查看玩家位置或复杂动作。超出聊天室级别的数据传输和更新率的任何内容都应取消选中此框。

          [] - 我的游戏不需要服务器告诉我任何事情。客户要求任何它需要的东西是完全可以接受的,也许一秒钟一次,或者一分钟一次更好。

          [] - 我的游戏不需要持续模拟服务器上运行的复杂世界的时间超过完成请求所需的时间。跟踪聊天是一回事,进行物理和图形修改是另一回事。

          如果您选中了所有这些框,那么 PHP 仍然在游戏中!否则。不要打扰。

          基本上,我在这里要说的是,PHP 非常适合非多人游戏、回合制游戏或至少不是非常互动的游戏。但是一旦你不得不在没有播放器的情况下继续运行,PHP 就落伍了。

          巫毒等级

          但如果你只是必须这样做。有办法绕过它。

          A - 创建一个运行您的世界的 PHP 守护程序,将所有其他流量通过管道传输到与数据库交互的 getter 或 setter 请求文件。因此,您可以请求获取游戏世界状态,或设置玩家执行的值。所有其他与游戏世界相关的事情都可以由守护进程处理,并且游戏本身发生在数据库中。

          B - 使用 cron,而不是守护进程。 (很危险,但我们已经确定你是冒险者了,对吧?)

          C - 只尝试一个守护进程并监听套接字,然后发送线程(通过 exec())进行响应。有点像上面 AndreKR 的想法,只是你不需要睡觉。这里的问题是你几乎总是会丢失东西或以其他方式被切断。如果守护进程以某种方式运行两次,整个事情可能会爆炸......

          【讨论】:

            【解决方案6】:

            如果你真的想这样做,你得先睡一段时间,检查一下socket,再睡一次,检查一下socket……

            要在不阻塞的情况下检查套接字,您需要使用非阻塞 I/O,您可以使用具有 DONTWAIT 标志的 socket_set_nonblock()socket_recv() 来实现。

            【讨论】:

              【解决方案7】:

              PHP 没有多线程,所以你真的应该考虑使用更合适的语言(比如 Andrey 在其评论中提到的)。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2014-01-21
                • 1970-01-01
                相关资源
                最近更新 更多