17

我正在尝试使用 PHP 实现实时聊天应用程序。是否可以在不使用数据库或文件等持久数据存储的情况下做到这一点。基本上我需要的是一个用 PHP 编写的中介,他

  1. 接受来自客户端浏览器的消息
  2. 将消息广播给其他客户端
  3. 忘记消息
4

14 回答 14

14

您应该查看html5 的Web Sockets。它使用两种方式连接,因此您不需要任何数据库或文件。任何到达服务器的聊天消息都将直接发送到其他用户的浏览器,无需任何 Ajax 调用。但您还需要设置 Web 套接字服务器。

Web 套接字也用于许多实时应用程序中。我很快就打算写一个完整的教程。我会通知你的。

于 2012-07-19T13:41:13.477 回答
7

刚刚尝试了一些我以前从未做过的事情来回答这个问题。似乎有效,但我只测试过一次。我没有使用 Socket,而是想到了使用共享 Session 变量。基本上,无论用户如何,我都强制 Session_id 为相同的值,因此它们都共享相同的数据。从快速测试来看,它似乎有效。这是我所做的:

session_id('12345');
session_start();
$session_id = session_id();
$_SESSION['test'] = $_SESSION['test'] + 1;
echo "session: {$session_id} test: {$_SESSION['test']} <br />";

所以我的想法是,您可以简单地将聊天信息存储在 Session 变量中,并强制所有人(无论他们是谁)使用共享会话。然后你可以简单地使用ajax不断地重新加载当前的Session变量,并在添加消息时使用ajax编辑会话变量。此外,您可能希望将 Session 设置为永不过期或拥有非常长的 maxlifetime。

正如我所说,我只是玩了几分钟,看看它是否可行。

于 2012-07-20T14:02:16.377 回答
6

你会想要使用Sockets. 本文将准确介绍您想要做什么: http: //devzone.zend.com/209/writing-socket-servers-in-php/

于 2012-07-19T14:36:09.477 回答
3

当我试图解决同样的问题时,我选择了 Nginx 的Push Module。我之所以选择这种方式,是因为我必须支持较旧的浏览器(通常不支持 WebSockets),并且对在TCP 代理后面设置像Socket.io这样的适当解决方案没有信心。

工作流程是这样的:

  1. 客户端通过长轮询连接到我的/subscriber位置,该位置对所有人开放。
  2. /publisher位置仅接受来自我自己的服务器的连接
  3. 当客户端订阅和交谈时,它基本上只是要求一个 PHP 脚本来处理发送的任何数据。
  4. 该脚本可以进行验证、授权等操作,然后(通过 curl)将 JSON 格式的消息转发到/publisher.
  5. Nginx 的 Push Module 处理将消息发送回订阅者,并且客户端建立一个新的长轮询连接。

如果我不得不重新做一遍,那么我肯定会选择 Socket.io 路线,因为它具有对 Comet 式长轮询的适当回退,并且对于客户端和服务器脚本都有很好的文档。

希望这可以帮助。

于 2012-07-22T01:38:47.753 回答
2

您需要使用某种存储作为缓冲区。不使用文件或数据库(也使用文件)是合理的。您可以尝试使用 php 的共享内存函数,但我不知道任何可行的解决方案,因此您必须从头开始。

于 2012-07-17T06:48:51.227 回答
2

是否可以在不使用数据库或文件等持久数据存储的情况下做到这一点?

这是可能的,但你不应该使用。基于数据库或文件不会减慢聊天速度。它将为您的聊天应用程序提供额外的安全性。您可以使用ajax套接字进行基于 Web 的聊天,而无需持久数据。

您应该看到以下帖子:

  1. 基于数据库的聊天室是个坏主意吗?
  2. 从 SQL 数据库而不是文件中轮询聊天应用程序会提高性能吗?
  3. 使用 memcached 作为聊天消息的数据库缓冲区
  4. php问题中的持久数据
  5. https://stackoverflow.com/questions/6569754/how-can-i-develop-social-network-chat-without-using-a-database-for-storing-the-c
  6. 文件与数据库在聊天应用程序中的存储效率
于 2012-07-20T07:02:58.167 回答
2

PHP 不适合您的要求(在 apache-php、fastcgi 等正常设置中),因为 PHP 脚本会针对每个请求从上到下执行,并且如果不使用外部,则无法在请求之间保持任何状态服务或数据库/文件(例如http://php.net/manual/de/book.apc.php除外,但它不适用于实现聊天并且不会扩展到多个服务器。)

您绝对应该查看 Node.js,尤其是 Node.js 模块 Socket.IO(一个 Websocket 库)。它非常易于使用和摇滚。Socket.IO 还可以通过可选的 redis 后端扩展到多个聊天服务器,这意味着它更容易扩展。

顺便说一下,尝试使用$_SESSION静态会话 ID 作为通信通道并不是解决方案,因为 PHP 将会话数据保存到文件中。

于 2012-07-21T01:29:17.467 回答
2

如果您对 PHP 有业务需求,那么将另一种语言添加到混合中只会意味着您有两个问题。

运行一个永久的、持续运行的守护程序 PHP IRCd 服务器是完全可能的:我知道,因为我已经做到了,所以可以制作一个运行多年的在线游戏。

我使用的 IRC 服务器部分是 WaveIRCd 的修改版本:http: //sourceforge.net/projects/waveircd/

我使用我在此处提供的代码对其进行了守护: http ://www.thudgame.com/node/254

这段代码可能有点矫枉过正:我把它写得尽可能坚固,所以它尝试使用 PHP 的 pcntl_fork() 进行守护进程,然后回退到在后台递归调用自身,然后回退到 perl,依此类推:它还处理 PHP 安全模式的安全限制,以防有人打开它,以及通过 cron 调用所施加的安全限制。

您可能可以将其简化为几行:带有注释“守护程序规则...”的位 - 遵循这些规则,您将很好地守护您的进程。

为了处理任何意外的守护进程死亡等,然后我每分钟通过 cron 运行该守护进程,在那里它检查守护进程是否已经在运行,如果是这样,要么安静地死掉,要么守护进程没有响应,杀死它并取而代之。

由于 IRC 的整体分布式特性,它非常坚固耐用,并为我提供了一个多人浏览器游戏,好几年都没有停机,直到几个月前 bit-rot 吃掉了这个网站。我应该尝试在 Flash 中重写前端并在有一天我有时间的时候重新恢复它......

(然后我为 PHP 机器人运行了另一个守护程序来管理游戏本身,然后让我的游戏作为 java 小程序连接到它,并与机器人对话以玩游戏,但这在这里无关紧要)。

由于 WaveIRCd 不再维护,因此可能值得四处寻找是否有其他人分叉了该项目并支持它。

[2012 年编辑:也就是说,如果您希望前端是 HTML5/Javascript,或者如果您想通过 HTTP 连接的同一端口进行连接,那么您的选择比使用 Flash 或 Java 时更有限。在这种情况下,请听取其他人的建议,并使用“WebSockets”(在大多数当前浏览器中支持不佳)或“Socket.io”项目(使用 WebSockets,但回退到 Flash 或各种其他方法,具体取决于浏览器可用)。

以上是针对您的主机允许您在另一个端口上运行服务的情况。特别是,许多人在他们的 ToS 中有明确的规则反对运行 IRCd。]

[2019 年编辑:WebSockets 现在得到广泛支持,您应该可以使用它们。作为一个相关的案例研究,Slack 是用 PHP 编写的(根据https://slack.engineering/taking-php-seriously-cf7a60065329),并且在一段时间内支持 IRC 协议,尽管我相信它已经被淘汰了。作为其主要协议,它使用基于 JSON over WebSockets 的 API ( https://api.slack.com/rtm )。这一切都表明 PHP IRCd 可以提供企业级的性能和质量,即使在 IRC 协议与另一个协议相互转换的情况下,您会期望它提供更差的性能。]

于 2012-07-26T23:37:19.007 回答
1

实现此目的的一种解决方案是编写 PHP 套接字服务器。

<?php

// Set time limit to indefinite execution

set_time_limit (0);

// Set the ip and port we will listen on

$address = '192.168.0.100';

$port = 9000;

$max_clients = 10;

// Array that will hold client information

$clients = Array();

// Create a TCP Stream socket

$sock = socket_create(AF_INET, SOCK_STREAM, 0);

// Bind the socket to an address/port

socket_bind($sock, $address, $port) or die('Could not bind to address');

// Start listening for connections

socket_listen($sock);

// Loop continuously

while (true) {

    // Setup clients listen socket for reading

    $read[0] = $sock;

    for ($i = 0; $i < $max_clients; $i++)

    {

        if ($client[$i]['sock']  != null)

            $read[$i + 1] = $client[$i]['sock'] ;

    }

    // Set up a blocking call to socket_select()

    $ready = socket_select($read,null,null,null);

    /* if a new connection is being made add it to the client array */

    if (in_array($sock, $read)) {

        for ($i = 0; $i < $max_clients; $i++)

        {

            if ($client[$i]['sock'] == null) {

                $client[$i]['sock'] = socket_accept($sock);

                break;

            }

            elseif ($i == $max_clients - 1)

                print ("too many clients")

        }

        if (--$ready <= 0)

            continue;

    } // end if in_array



    // If a client is trying to write - handle it now

    for ($i = 0; $i < $max_clients; $i++) // for each client

    {

        if (in_array($client[$i]['sock'] , $read))

        {

            $input = socket_read($client[$i]['sock'] , 1024);

            if ($input == null) {

                // Zero length string meaning disconnected

                unset($client[$i]);

            }

            $n = trim($input);

            if ($input == 'exit') {

                // requested disconnect

                socket_close($client[$i]['sock']);

            } elseif ($input) {

                // strip white spaces and write back to user

                $output = ereg_replace("[ \t\n\r]","",$input).chr(0);

                socket_write($client[$i]['sock'],$output);

            }

        } else {

            // Close the socket

            socket_close($client[$i]['sock']);

            unset($client[$i]);

        }

    }

} // end while

// Close the master sockets

socket_close($sock);

?>

您将通过命令行运行它来执行此操作,并且始终必须运行您的 PHP 客户端才能连接到它。然后,您可以编写一个连接到套接字的 PHP 客户端。

<?php
$fp = fsockopen("www.example.com", 80, $errno, $errstr, 30);
if (!$fp) {
    echo "$errstr ($errno)<br />\n";
} else {
    $out = "GET / HTTP/1.1\r\n";
    $out .= "Host: www.example.com\r\n";
    $out .= "Connection: Close\r\n\r\n";
    fwrite($fp, $out);
    while (!feof($fp)) {
        echo fgets($fp, 128);
    }
    fclose($fp);
}
?>

您必须使用某种类型的 ajax 来调用 jQuery,将消息发布到这个 PHP 客户端。

http://devzone.zend.com/209/writing-socket-servers-in-php/ http://php.net/manual/en/function.fsockopen.php

于 2012-07-20T13:57:17.543 回答
1

最好为此使用 node.js 服务器。WebSockets 现在不是跨浏览器(除了 socket.io 的 node.js 可以完美运行)

于 2012-07-21T22:56:04.603 回答
0

简而言之,你不能。当前的 HTTP/HTML 实现不支持,pushstate因此您的聊天应用程序的算法应遵循:

  1. 答:发消息
  2. B,C,D:在发送新消息时获取此消息。

所以接收者总是必须提出一个新的请求并检查是否有新的消息已经发送。(AJAX 调用或类似的东西)所以发送事件和接收事件之间总是存在延迟。

  • 这意味着数据必须保存在全局的东西中,比如数据库或文件系统。

看看: http ://today.java.net/article/2010/03/31/html5-server-push-technologies-part-1

于 2012-07-17T06:30:20.870 回答
0

你没有说它必须全部写成 PHP :)

安装 RabbitMQ,然后使用这个建立在 websockets 和 RabbitMQ 之上的聊天实现。

您的 PHP 几乎就是“聊天室 chrome”。您的大部分网站可能都适合 5 兆的离线 HTML5 内容限制,并且您拥有一个非常灵活(并且可能比您自己做的更强大)的聊天系统。

如果您离开房间,它甚至还有 20 条聊天记录。

https://github.com/videlalvaro/rabbitmq-chat

于 2012-07-25T02:13:44.447 回答
-1

如果你只需要使用 PHP,那么你可以将聊天消息存储在会话变量中,会话可以像对象一样,存储大量信息。如果您可以使用 jQuery,那么您可以在发送消息后将段落附加到 div,但是如果刷新站点,消息将消失。或者组合,将消息存储在会话中并使用 jQuery 和 ajax 进行更新。

于 2012-07-17T06:06:01.173 回答
-1

尝试查看像ZeroMQ这样的套接字库,它们允许即时传输消息,并且比 TCP 更快,并且是实时的。他们的基础设施允许在 A 点和 B 点之间发送即时数据,而无需先将数据存储在任何地方(尽管您仍然可以选择)。这是ZeroMQ 中聊天客户端的教程

于 2012-07-19T06:44:29.697 回答