服务器发送事件和PHP – 什么触发事件在服务器上?
所有,
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()));
如果我正在构build一个实际的例子 – 例如Facebook风格的“墙”或者股票代码,在每一次数据改变的时候服务器都会向客户端“推送”一条新的消息,那么这是如何工作的呢?
换句话说,PHP脚本是否有一个连续运行的循环,检查数据的变化,然后每次发现一条消息时发送一条消息? 如果是这样 – 你怎么知道什么时候结束这个过程?
或者 – PHP脚本是否简单地发送消息,然后结束(在HTML5Rocks示例中就是这种情况)? 如果是这样 – 你如何获得持续更新? 浏览器是否定期轮询PHP页面? 如果是这样 – 这是一个“服务器发送的事件”? 这与在JavaScript中编写一个使用AJAX定期调用PHP页面的setInterval函数有什么不同?
对不起 – 这可能是一个令人难以置信的天真的问题。 但是,我所能find的例子中没有一个清楚地表明了这一点。
[UPDATE]
我想我的问题措辞不好,所以这里有一些澄清。
比方说,我有一个网页,应该显示苹果股票的最新价格。
当用户第一次打开页面时,页面会创build一个带有我的“stream”的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 // eg, 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
你有并发用户? 如果是这样 – 是远程可行还是合适的方式来build立一个应用程序? 你怎么知道什么时候可以结束一个stream.php
的实例?
我天真的印象是,如果是这样的话,PHP 不适合这种服务器。 但是到目前为止,我所见过的所有演示都暗示PHP对此很好,这就是为什么我很困惑…
提前致谢。
服务器发送的事件用于从服务器端到客户端的实时更新。 在第一个例子中,服务器的连接不被保留,客户端每3秒尝试连接一次,并使服务器发送的事件与Ajax轮询没有区别。
所以,为了使连接持续下去,你需要把你的代码封装在一个循环中,不断检查更新。
PHP是基于线程的,更多的连接用户将使服务器资源耗尽。 这可以通过控制脚本执行时间来解决,并在超过一定时间(即10分钟)时结束脚本。 EventSource
API会自动再次连接,所以延迟是在可接受的范围内。
另外,请查看我的PHP库以获取服务器发送的事件 ,您可以更好地了解如何在PHP中执行服务器发送的事件,并使代码更容易。
“…只要客户端”连接“到”stream.php“保持打开状态?
是的,你的伪代码是一个合理的方法。
“你怎么知道什么时候可以结束一个stream.php的实例?
在最典型的情况下,这发生在用户离开您的网站时。 (Apache认识到封闭的套接字,并杀死PHP实例。)你可能closures从服务器端的套接字的主要时间是,如果你知道有一段时间没有数据, 你发给客户的最后一条消息是告诉他们在某个时间回来。 例如,在股票stream动的情况下,您可以在晚上8点closures连接,并告诉客户在8小时内回来(假设纳斯达克从上午4点到晚上8点开放报价)。 周五晚上,你告诉他们星期一上午回来。 (我有一本关于上交所的即将出版的书,并且在这个主题上专门写了几节。)
“…如果是这样的话,PHP并不是一种适合这种服务器的技术,但是到目前为止我看到的所有的演示都意味着PHP对此很好,这就是我为什么这么做的原因困惑…”
好吧,人们争辩说,PHP不是一个适合普通网站的技术,而且他们是正确的:如果用C ++replace整个LAMP栈,可以用更less的内存和CPU周期来实现。 但是,尽pipe如此,PHP的权力大部分网站就好了。 对于Web工作来说,这是一种非常高效的语言,由于熟悉的C语言语法和许多库的结合,对于pipe理员来说,让大量的PHP程序员雇用,大量的书籍和其他资源以及一些大的用例(如Facebook和维基百科)。 这些基本上与您selectPHP作为您的stream媒体技术相同的原因。
典型的设置不会是每个PHP实例与NASDAQ的一个连接。 相反,你将有另一个与NASDAQ连接的进程,或者从你的集群中的每台机器到NASDAQ的单个连接。 然后将价格推入SQL / NoSQL服务器或共享内存。 然后PHP只是轮询共享内存(或数据库),并推出数据。 或者,有一个数据收集服务器,每个PHP实例打开一个到该服务器的套接字连接。 数据收集服务器在每个PHP客户端接收到更新时推送更新,然后将这些数据推送到客户端。
使用Apache + PHP进行stream式传输的主要可扩展性问题是每个Apache进程的内存。 当达到硬件的内存限制时,做出业务决定,将另一台机器添加到集群,或将Apache从循环中删除,然后编写专用的HTTP服务器。 后者可以用PHP来完成,所有你现有的知识和代码都可以被重用,或者你可以用另一种语言来重写整个应用程序。 我的纯开发人员会用C ++写一个专门的,精简的HTTP服务器。 在我的经理会添加另一个框。
我注意到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我开始一个会话,并使用会话variables! 克服这个问题。
每当我想“更新”消息时,我也通过Ajax调用sseServer.php(发布和设置variable message
值)。
现在在jQuery(javascript)我做这样的事情:1)我声明一个全局variablesvar timeStamp = 0; 第二)我使用下一个algorithm:
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 Table来通过POST传递“消息”,因为sseServer.php执行类似“无限循环”的操作。
我当时的问题是,上面的代码不会发送到所有客户端的“消息”,但只有一对(称为sseServer.php客户端作为个人对每一对),所以我会改变技术,并为数据库更新从我想触发“消息”,然后sseServer.php页面,而不是通过POST获取消息,它会从数据库表中得到它。
我希望我有帮助!
这实际上是关于你的应用程序的结构性问题。 实时事件从一开始就是您想要考虑的事情,所以您可以围绕它来devise您的应用程序。 如果你已经写了一个应用程序,只是运行一堆随机mysql(i)_query
方法使用string查询,并没有通过任何中介传递他们,那么很多时候,你不会有select,但要么重写大部分你的应用程序,或不断的服务器端轮询。
但是,如果你将你的实体作为对象进行pipe理,并通过某种中介类来传递它们,那么你可以把这个过程挂钩。 看看这个例子:
<?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);
这不是最优雅的例子,但它应该是一个体面的积木。 您可以挂钩到您的实际持久层来处理触发这些事件。 然后,您立即获得它们(尽可能实时获取),而不用敲打您的服务器(因为您不需要不断查询数据库并查看是否发生了变化)。
你显然不会用这种方式捕获数据库的手动更改 – 但是如果你正在以任何频率手动对数据库进行任何操作,你应该:
- 解决需要您手动更改的问题
- build立一个工具来加速这个过程,并且开始这些事件
基本上,PHP是不适合这种事情的技术。 是的,你可以使它工作,但它将是一个高负担的灾难。 我们运行库存服务器,通过websocket发送库存变化信号给几十个用户 – 如果我们使用php的话…那么,我们可以,但是这些自制的循环 – 只是一场噩梦。 每一个连接都会在服务器上build立一个独立的进程,或者你必须处理来自某种数据库的连接。
只需使用nodejs和socket.io。 它可以让你轻松启动,并在几天内运行服务器。 Nodejs也有自己的限制,但是对于websockets(和SSE)连接现在是最强大的技术。
而且 – 上证所看起来不太好。 websockets的唯一优点是数据包本身被压缩(ws不被压缩),但是缺点是SSE是单向连接。 你的用户,如果他想给subscripton添加另一个股票代码,将不得不做出ajax请求(包括所有的起源控制的麻烦,并且请求会很慢)。 在websockets中,客户端和服务器在一个单独的连接中进行通信,所以如果用户发送交易信号或者订阅报价,他只是在已经打开的连接中发送一个string。 而且速度很快。