使用服务器推送事件广播消息

6

我希望实现一个简单的服务器推送事件应用程序,客户端监听像这样的php文件

<script src="eventsource.js"></script>
<script>
  var es = new EventSource('../sse.php');

  es.onmessage = function (event) {
    var data = JSON.parse(event.data);
    console.log('msg: ' + event.data);
  };

  es.onopen = function () {
    console.log('connected');
  }
</script>

但是我需要能够广播一条消息,让所有连接的客户端同时收到。

PHP文件将由另一个请求触发,它将向所有客户端“echo”一些SSE数据。

这种情况可行吗?还有其他方法吗?

提前感谢您的帮助。

4个回答

7

对于这个问题,PHP 不是很适合。如果有许多客户端等待此事件,则需要为每个等待的客户端提供一个 PHP 解释器实例(可能还需要 apache 线程)。

您可以考虑使用事件驱动服务器来处理此问题。在类似情况下,我过去使用过 Node.js 和 Tornado (Python)。如果您使用 Node.js,则可以考虑使用 socket.io -- 它不使用 Server Sent Events,但会使您想要的东西变得非常简单。

您可以将两个 Web 服务器并排放置,一个是当前服务器 (我假设它是 Apache),另一个是由您编写的用于处理等待更新的客户端的服务器。每当 PHP 服务器想要向等待的客户端发送消息时,PHP 服务器会通知其他服务器 (AMQP、ZeroMQ、Redis Pub/Sub 或非公共接口上的简单 HTTP 请求都是不错的选择),然后该服务器将消息传递给任何等待的客户端。

您可以在 GitHub 上查看使用 node.js + socket.io 的非常简单的版本,(它很容易被 SSE 替换)。


1
如果有人感兴趣,我可以在本周晚些时候提供像我之前所说的那样的实现。 - JeffS
我很感兴趣,尽管我现在正在尝试实现它。所以除非你真的想要它,否则不要浪费时间。 :) 谢谢 - kbariotis
1
非常棒,也理解了要点。实现得很好!我还发现了另一个使用PHP作为服务器和Coffeescript作为客户端的出色实现这里。非常感谢@JeffS。 - kbariotis
有没有办法使用服务器发送事件进行多播? - kapil das
不,没有办法进行真正的多播SSE。 SSE需要每个客户端与服务器建立一个TCP连接。您可以通过设置简单的服务器来将相同的消息发送到所有连接的客户端,从而模拟多播。 - JeffS

1

重要编辑:(一天后)

经过测试,我发现这是一个非常糟糕的解决方案。

测试用例:

  • 客户端:在一个浏览器(Chromium,(k)ubuntu),一个标签页,8个iframe中运行8个客户端。
  • 服务器API:Apache 2.0处理程序
  • PHP版本5.3.10-1ubuntu3.4

结果:

打开了6个SSE连接。但是有2个SSE连接仍然处于挂起状态。在关闭SSE客户端之前,服务器上的其他页面无法打开。

但是服务器可以通过ajax处理多个客户端(每秒重复ajax)。


旧答案:

使用共享内存。

sse.php

<?php

header("Content-Type: text/event-stream\n\n");
header('Cache-Control: no-cache');

$key = "987654";
$permissions = 0666;
$size = 1024;

//Create or open the shared memory segment.
$segment = shm_attach($key, $size, $permissions);

$msg = ' null ';
$last_time = 0;
$time = 0;
while (1) {

    if (shm_has_var($segment, 0)) {
        $time = shm_get_var($segment, 0);
    }
    if (shm_has_var($segment, 0)) {
        $msg = shm_get_var($segment, 1);
    }
    $now = time();
    if ($last_time < $time) {
        $last_time = $time;
        echo 'data: msg(' . $msg . ') time(' . $time . ')';
        echo "\n\n";
    }

    ob_flush();
    flush();
    sleep(1);
}

trigger.php

<?php 
if (isset($_GET['msg'])) {

    $key = "987654";// TODO: how to generate suitable key?
    $permissions = 0666;
    $size = 1024;

    $segment = shm_attach($key, $size, $permissions);

    $time = time();
    $msg = $_GET['msg'];

    shm_put_var($segment, 0, $time);
    shm_put_var($segment, 1, $msg);
    echo $time;
}

这种方式的缺点:

  1. 一秒钟的延迟
  2. 长时间开放连接,导致不可扩展性。

在我的实现中,sse.php 每秒只发送最后一条消息。但是您可以改进它并使用数据库。(如果 trigger.php 在一秒钟内被调用了 3 次,则只保存最后一条消息)


我对这个问题很感兴趣,如果有更好的解决方案,我会点赞支持的。 :D - seyed
你的测试结果几乎肯定是由于Apache处理连接的方式,每个连接至少有一个线程,这些线程对此来说太重了。这就是为什么我提出的替代方案实际上避免使用Apache(和PHP)来处理将具有大量长时间开放连接的部分的原因。 - JeffS
1
浏览器还会限制每个域名的打开套接字数量。你可能还记得资产域分片的问题。同样的事情。 - oif_vet

0

关于连接数量的限制,这是由于浏览器对同一服务器的连接数量有限制,您可以通过重新配置来更改。火狐浏览器默认的连接数量也是6个。

另外,我想知道如何在服务器端处理断开连接事件,如果有太多客户端使用SSE,就需要管理这些连接,即使其他资源如套接字用于其他服务。


-1

整个页面不会同时收到相同的消息。

只有当它们连接后才会收到消息。

如果您想要更快速的消息传递,可以考虑使用WebSockets和/或Node.JS。


基本上,我想要一个文件(或结构)来监听POST请求,编辑它们,然后将数据发送给所有连接到服务器事件的客户端。您会建议如何实现这一点? - kbariotis
SSE在服务器->客户端通信中与WebSockets一样快速。由于开销较低和握手更快,实际上甚至可能稍微更快。 - Kornel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接