服务器发送的事件和 php - 啥触发服务器上的事件?

Posted

技术标签:

【中文标题】服务器发送的事件和 php - 啥触发服务器上的事件?【英文标题】:Server-sent events and php - what triggers events on the server?服务器发送的事件和 php - 什么触发服务器上的事件? 【发布时间】:2013-01-11 23:03:33 【问题描述】:

全部,

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 的线程数与并发用户数一样多?如果是这样 - 这是远程可行的,还是构建应用程序的适当方式?你怎么知道什么时候可以ENDstream.php 的实例?

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

【问题讨论】:

这是开发人员必须自己编写代码的部分。获取数据的方法是通过 websockets/长轮询等。但是诀窍是 what 触发事件。就个人而言,我尝试了几种方法,其中一种我喜欢的方法(但它不是故障安全的)是让 mysql 每次在特定表中插入某些内容时触发一个控制台程序.控制台程序将接收有关更改/插入记录的信息,并通过 WebSockets 向相应用户发送通知。基本上我有一个 PHP 守护进程等待发送消息。 这个有一个问题,IE 不支持 SSE :-/ 我也会读这个 prodigyproductionsllc.com/articles/programming/javascript/… 我认为他正在使用端口来避免太多孩子的问题,但总体看起来像他的建议避免 SSE。看起来麻烦多于它的价值,IMO。 IE11或android浏览器目前不支持caniuse.com/eventsource 如果有人需要sse php代码:github.com/shahzadthathal/server-sent-events-php-example 我也有同样的问题,我想我深深理解你所说的什么触发了服务器上的事件...。当您创建EventSource('stream.php') 的对象时,客户端会打开与stream.php 的连接,这就像通过ajax 调用它一样。 连接会触发您的服务器端代码并​​保持连接打开,只要您的服务器端代码有话要说。然后连接关闭,经过短暂的延迟(我认为在 chrome 中为 3 秒)客户端重新打开连接,再次触发您的 stream.php 文件。 【参考方案1】:

这确实是关于您的应用程序的结构性问题。实时事件是您想从一开始就考虑的事情,因此您可以围绕它设计您的应用程序。如果您编写的应用程序仅使用字符串查询运行一堆随机的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);

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

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

解决需要您进行手动更改的问题 构建一个工具来加快进程,并触发这些事件

【讨论】:

科林 - 感谢您的回答。我的错-我的问题不清楚-但这并不是我真正要问的。我的意思要问的是...如果您使用 PHP 作为“服务器” - 您从客户端中的 EventSource 调用的 PHP 脚本是否需要运行整个时间 客户端连接到了吗?这是否意味着,如果您有 1,000 个并发用户,您就有 1,000 个单独的线程运行 1,000 个 PHP 脚本的并发实例?这可行吗?而且,您怎么知道何时结束 php 脚本(假设它正在循环以保持“活动”)?【参考方案2】:

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

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

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

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

【讨论】:

能否详细说明“这可以通过控制脚本执行时间并在超过一定时间时结束脚本来解决”?如果您的用户数量过多,关闭连接是否真的会大大提高资源使用率,因为用户将在 3 秒内再次连接?【参考方案3】:

"...只要客户端“已连接”,“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 服务器。我的经理会添加另一个盒子。

【讨论】:

由于每个Apache连接进程都会消耗内存,用nginx代替会不会更好? @ZhangBuzz 在这里我说的是 Apache+PHP 真正的意思是“web-server + PHP process”,所以使用不同的 web server 基本上没有区别。 也许像这样的服务器? github.com/hoaproject/Eventsource 或 github.com/hhxsv5/php-sse 另请注意,Nginx 可能会更有效,因为使用更少的内存:blog.webfaction.com/2008/12/…【参考方案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 开始了一个会话并使用了一个会话变量!解决问题。

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

现在在 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 或 DB 表代替通过 POST 传递“消息”,因为 sseServer.php 执行类似于“无限循环”的操作。

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

希望对我有帮助!

【讨论】:

【参考方案5】:

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

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

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

【讨论】:

你可以像使用 node.js 中的事件循环一样使用 React.php。 虽然说 PHP 不是最佳选择很好,但我认为您至少应该包含 OP 要求的内容。

以上是关于服务器发送的事件和 php - 啥触发服务器上的事件?的主要内容,如果未能解决你的问题,请参考以下文章

pb 下拉数据窗口getrow变化时触发啥事件

HTTP 流和服务器发送的事件有啥区别?

Push API 和服务器发送事件有啥区别?

无论控件如何,在表单上的任何控件更新后有啥方法可以触发事件?

Web 套接字、长轮询、服务器发送事件和永久帧之间有啥区别?

了解啥是hash路由和history路由