1

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

4

7 回答 7

1

可以,但我同意@Andrey 和@DampeS8N,不是最佳选择。如果你下定决心要这样做,请查看这本书: 你想用 PHP做什么?

于 2010-11-30T15:48:26.603 回答
1

如果你真的要这样做,你得睡一段时间,检查插座,再睡,检查插座......

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

于 2010-11-30T15:52:07.060 回答
1

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

它最终并不理想。

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

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

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

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

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

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

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

巫毒等级

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

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

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

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

于 2010-11-30T15:59:44.190 回答
1

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

  • 使用某种分隔符。以 '\0' 结束每条消息。
  • 随消息一起发送消息大小。以“内容长度:42\n”或两个大小字节 (0x00 0x42) 开始每条消息。
  • 使用 XML。<message>开始和</message>结束一条消息。

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;
    }
}

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

于 2010-11-30T16:33:12.867 回答
0

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

于 2010-11-30T15:48:13.170 回答
0

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

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

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

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

于 2010-11-30T17:21:38.573 回答
0

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

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

于 2010-11-30T19:03:35.480 回答