109

全部,

HTML5 Rocks 有一个关于服务器发送事件 (SSE) 的不错的初学者教程:

http://www.html5rocks.com/en/tutorials/eventsource/basics/

但是,我不明白一个重要的概念 - 是什么触发了服务器上导致发送消息的事件?

换句话说 - 在 HTML5 示例中 - 服务器只发送一次时间戳

<?php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache'); // recommended to prevent caching of event data.
function sendMsg($id, $msg) {
  echo "id: $id" . PHP_EOL;
  echo "data: $msg" . PHP_EOL;
  echo PHP_EOL;
  ob_flush();
  flush();
}
$serverTime = time();
sendMsg($serverTime, 'server time: ' . date("h:i:s", time()));

如果我正在构建一个实际示例——例如,Facebook 风格的“墙”或股票行情,其中服务器会在每次数据更改时向客户端“推送”一条新消息,它是如何工作的?

换句话说... PHP 脚本是否有一个连续运行的循环,检查数据的变化,然后在每次找到时发送一条消息?如果是这样 - 你怎么知道什么时候结束这个过程?

或者 - PHP 脚本是否只是发送消息,然后结束(在 HTML5Rocks 示例中似乎就是这种情况)?如果是这样 - 您如何获得持续更新?浏览器是否只是定期轮询 PHP 页面?如果是这样 - 那是一个“服务器发送的事件”吗?这与在 JavaScript 中使用 AJAX 定期调用 PHP 页面的 setInterval 函数有何不同?

抱歉 - 这可能是一个非常幼稚的问题。但是我找不到的例子都没有说明这一点。

[更新]

我认为我的问题措辞不佳,所以这里有一些澄清。

假设我有一个网页应该显示 Apple 股票的最新价格。

当用户第一次打开页面时,页面会使用我的“流”的 URL 创建一个 EventSource。

var source = new EventSource('stream.php');

我的问题是——“stream.php”应该如何工作?

像这样?(伪代码):

<?php
    header('Content-Type: text/event-stream');
    header('Cache-Control: no-cache'); // recommended to prevent caching of event data.
    function sendMsg($msg) {
        echo "data: $msg" . PHP_EOL;
        echo PHP_EOL;
        flush();
    }

    while (some condition) {
        // check whether Apple's stock price has changed
        // e.g., by querying a database, or calling a web service
        // if it HAS changed, sendMsg with new price to client
        // otherwise, do nothing (until next loop)
        sleep (n) // wait n seconds until checking again
    }
?>

换句话说 - 只要客户端“连接”到它,“stream.php”就保持打开状态吗?

如果是这样 - 这是否意味着您运行stream.php的线程数与并发用户数一样多?如果是这样 - 这是远程可行的,还是构建应用程序的适当方式?你怎么知道什么时候可以结束一个实例stream.php

我的天真印象是,如果是这样的话,PHP适合这种服务器。但是到目前为止我看到的所有演示都暗示 PHP 对此很好,这就是为什么我如此困惑......

4

5 回答 5

35

服务器发送的事件用于从服务器端到客户端的实时更新。在第一个示例中,来自服务器的连接没有保留,客户端每 3 秒尝试再次连接,使服务器发送的事件与 ajax 轮询没有区别。

因此,要使连接持续存在,您需要将代码包装在一个循环中并不断检查更新。

PHP 是基于线程的,更多的连接用户会使服务器耗尽资源。这可以通过控制脚本执行时间并在脚本超过一定时间(即 10 分钟)时结束脚本来解决。API将EventSource自动再次连接,因此延迟在可接受的范围内。

另外,查看我的服务器发送事件的 PHP 库,您可以了解更多关于如何在 PHP 中执行服务器发送事件并使其更容易编码。

于 2013-02-12T01:50:54.990 回答
33

“......只要客户端“连接”到它,“stream.php”是否保持打开状态?

是的,您的伪代码是一种合理的方法。

“你怎么知道什么时候可以结束 stream.php 的实例?”

在最典型的情况下,这发生在用户离开您的网站时。(Apache 识别出关闭的套接字,并杀死 PHP 实例。)您可能从服务器端关闭套接字的主要时间是如果您知道一段时间内将没有数据;您发送给客户的最后一条消息是告诉他们在某个时间回来。例如,在您的股票流媒体案例中,您可以在晚上 8 点关闭连接,并告诉客户在 8 小时内回来(假设纳斯达克从凌晨 4 点到晚上 8 点开放报价)。星期五晚上你告诉他们星期一早上回来。(我有一本即将出版的关于 SSE 的书,并专门针对这个主题进行了几节。)

“...如果是这样的话,PHP 不是一种适合这种服务器的技术。但是到目前为止我所看到的所有演示都暗示 PHP 对此很好,这就是为什么我如此使困惑...”

好吧,人们争辩说 PHP 不是适合普通网站的技术,他们是对的:如果用 C++ 替换整个 LAMP 堆栈,则可以用更少的内存和 CPU 周期来做到这一点。然而,尽管如此,PHP 为大多数网站提供了良好的支持。它是一种非常高效的 Web 工作语言,因为它结合了熟悉的类 C 语法和许多库,并且对于管理人员来说是一种安慰,因为有大量的 PHP 程序员可以雇用,大量的书籍和其他资源,以及一些大型的用例(例如 Facebook 和维基百科)。这些基本上与您可能选择 PHP 作为您的流技术的原因相同。

典型的设置不会是每个 PHP 实例与 NASDAQ 的一个连接。相反,您将有另一个进程与 NASDAQ 的单一连接,或者集群中的每台机器到 NASDAQ 的单一连接。然后将价格推入 SQL/NoSQL 服务器或共享内存。然后 PHP 只是轮询共享内存(或数据库),并将数据推出。或者,有一个数据收集服务器,每个 PHP 实例打开一个到该服务器的套接字连接。数据收集服务器在接收到更新时向其每个 PHP 客户端推送更新,然后它们又将这些数据推送到其客户端。

使用 Apache+PHP 进行流式传输的主要可扩展性问题是每个 Apache 进程的内存。当您达到硬件的内存限制时,做出业务决策,将另一台机器添加到集群中,或者将 Apache 排除在循环之外,并编写一个专用的 HTTP 服务器。后者可以在 PHP 中完成,因此您现有的所有知识和代码都可以重复使用,或者您可以用另一种语言重写整个应用程序。我这个纯粹的开发人员会用 C++ 编写一个专用的、流线型的 HTTP 服务器。我的经理会添加另一个盒子。

于 2013-11-01T00:44:16.050 回答
4

我注意到 sse techink 向客户端发送每两个延迟数据(类似于从客户端页面前 Ajax 池数据中反转池数据 techink。)所以为了克服这个问题,我在 sseServer.php 页面上做了这个:

<?php
        session_start();
        header('Content-Type: text/event-stream');
        header('Cache-Control: no-cache'); // recommended to prevent caching of event data
        require 'sse.php';
        if ($_POST['message'] != ""){
                $_SESSION['message'] = $_POST['message'];
                $_SESSION['serverTime'] = time();
        }
        sendMsg($_SESSION['serverTime'], $_SESSION['message'] );
?>

sse.php 是:

<?php
function sendMsg($id, $msg) {
  echo "id: $id" . PHP_EOL;
  echo "data: $msg" . PHP_EOL;
  echo PHP_EOL;
  ob_flush();
  flush();
}
?>

请注意,在 sseSerer.php 我开始一个会话并使用一个会话变量!来克服这个问题。

variable message此外,每次我想“更新”消息时,我都会通过 Ajax 调用 sseServer.php(发布并将值设置为)。

现在在 jQuery (javascript) 我做这样的事情: 1)我声明一个全局变量var timeStamp=0; 2nd)我使用下一个算法:

if(typeof(EventSource)!=="undefined"){
        var source=new EventSource("sseServer.php");
        source.onmessage=function(event)
        if ((timeStamp!=event.lastEventId) && (timeStamp!=0)){
                /* this is initialization */
                timeStamp=event.lastEventId;
                $.notify("Please refresh "+event.data, "info");
        } else {
                if (timeStamp==0){
                         timeStamp=event.lastEventId;
                }
        } /* fi */

} else {
        document.getElementById("result").innerHTML="Sorry, your browser does not support server-sent events...";
} /* fi */

在 : 行$.notify("Please refresh "+event.data, "info"); 是否可以处理消息。

对于我的情况,我曾经发送一个 jQuery 通知。

您可以使用 POSIX PIPES 或数据库表来通过 POST 传递“消息”,因为 sseServer.php 执行类似于“无限循环”的操作。

我当时的问题是,上面的代码不会将“消息”发送给所有客户端,而只会发送给一对(调用 sseServer.php 的客户端对每一对都是单独的)所以我将更改技术并改为从我想触发“消息”的页面更新数据库,然后是 sseServer.php,而不是通过 POST 获取消息,它将从数据库表中获取。

我希望我有帮助!

于 2014-02-13T12:28:09.727 回答
3

这确实是关于您的应用程序的结构性问题。实时事件是您想从一开始就考虑的事情,因此您可以围绕它设计您的应用程序。如果您编写的应用程序只使用字符串查询运行一堆随机mysql(i)_query方法并且不通过任何类型的中介传递它们,那么很多时候您将别无选择,只能重写大部分应用程序,或者持续的服务器端轮询。

但是,如果您将实体作为对象进行管理并通过某种中间类传递它们,则可以挂钩该过程。看这个例子:

<?php
class MyQueryManager {
    public function find($myObject, $objectId) {
        // Issue a select query against the database to get this object
    }

    public function save($myObject) {
        // Issue a query that saves the object to the database
        // Fire a new "save" event for the type of object passed to this method
    }

    public function delete($myObject) {
        // Fire a "delete" event for the type of object
    }
}

在您的应用程序中,当您准备好保存时:

<?php
$someObject = $queryManager->find("MyObjectName", 1);
$someObject->setDateTimeUpdated(time());
$queryManager->save($someObject);

这不是最优雅的例子,但它应该作为一个体面的构建块。您可以连接到您的实际持久层来处理触发这些事件。然后您立即获得它们(尽可能实时),而无需敲击您的服务器(因为您无需不断地查询您的数据库并查看事情是否发生了变化)。

您显然不会以这种方式捕获对数据库的手动更改 - 但如果您以任何频率手动对数据库执行任何操作,您应该:

  • 修复需要手动更改的问题
  • 构建一个工具来加快进程,并触发这些事件
于 2013-01-28T15:12:46.847 回答
-7

基本上,PHP 不适合这类事情。是的,你可以让它工作,但在高负载下这将是一场灾难。我们运行股票服务器,通过 websockets 向数十万用户发送股票变化信号——如果我们为此使用 php……好吧,我们可以,但是那些自制的周期——只是一场噩梦。每个连接都会在服务器上创建一个单独的进程,或者您必须处理来自某种数据库的连接。

只需使用 nodejs 和 socket.io。它将让您在几天内轻松启动并拥有一个正在运行的服务器。Nodejs 也有自己的局限性,但对于 websockets(和 SSE)连接来说,它现在是最强大的技术。

而且 - SSE 并不像看起来那么好。websockets 的唯一优点是数据包被本地压缩(ws 不是压缩的),但缺点是 SSE 是单向连接。您的用户,如果他想在下标中添加另一个股票代码,则必须发出 ajax 请求(包括所有与源控制有关的问题,并且请求会很慢)。在 websockets 中,客户端和服务器在一个打开的连接中进行双向通信,因此如果用户发送交易信号或订阅报价,他只需在已打开的连接中发送一个字符串。而且速度很快。

于 2015-04-19T17:47:13.057 回答