【问题标题】:How to implement event listening in PHP如何在 PHP 中实现事件监听
【发布时间】:2011-11-20 03:05:27
【问题描述】:

这是我的问题:我有一个 AJAX 客户端脚本要求的脚本(我们称之为 comet.php),并等待发生这样的更改:

while(no_changes){
    usleep(100000);
    //check for changes
}

我不太喜欢这个,它的可扩展性不是很好,而且它是(恕我直言)“不好的做法” 我想用信号量(?)或无论如何并发编程来改善这种行为 技术。你能给我一些关于如何处理这个问题的提示吗? (我知道,这不是一个简短的答案,但一个起点就足够了。)

编辑LibEvent呢?

【问题讨论】:

  • 你想达到什么目的?通常的方法是让客户端定期调用脚本以检查更改。你有什么理由要在服务器端做吗?
  • PHP 并不是您真正应该用于 COMET 的语言。使用 Node.js 或其他异步工作的东西(例如 python tornado 或 greenlets)。通过使用在基于线程/进程的网络服务器上运行的 PHP,您会产生巨大的开销。
  • @Juhana,原因是avoid 定期检查更改并有reverse-ajax 解决方案。 @thiefMaster我知道那里有一些COMET服务器解决方案,但我真的认为php后端是可能的,只要我用PHP编写我的业务登录名,最好不要用另一种语言重写以避免代码复制。你能解释一下为什么 PHP comet 后端会有那么高的开销吗?
  • 我可以看到,但是有没有理由避免定期检查?
  • 是的,它会产生大量网络开销,具体取决于您检查新更新的频率

标签: php concurrency comet semaphore reverse-ajax


【解决方案1】:

这实际上取决于您在服务器端脚本中执行的操作。在某些情况下,您别无选择,只能执行上述操作。

但是,如果您正在做的事情涉及到调用将阻塞直到发生某些事情的函数,您可以使用它来避免赛车而不是 usleep() 调用(恕我直言,这部分会被认为是“坏的”练习”)。

假设您正在等待来自文件或其他类型的阻塞流的数据。你可以这样做:

while (($str = fgets($fp)) === FALSE) continue;
// Handle the event here

真的,PHP 不适合做这样的事情。但是有些情况(我知道,因为我自己处理过)PHP 是唯一的选择。

【讨论】:

  • 实际上,我想通过删除 noop while 来减少 CPU 开销(这就是为什么会有 usleep() 调用)
  • @ArtoAle 如果没有 PHP 中的某种循环,您将无法等待事件 - 它没有事件处理程序,例如javascript。但是通过按照上面概述的方式进行操作,您将让 fgets() 调用进行等待,而不是 check-sleep-check-sleep-check... 在您的示例中,如果事件发生在 usleep() 期间,您将拥有等待睡眠结束,然后才能处理它。通过使用(阻塞)检查作为循环条件,fgets() 在事件发生后立即返回,您可以立即处理。
  • 好的,你是对的。关键是System V IPC 我可以使用阻塞调用来获取信号量(这会导致您提出的相同解决方案)问题是我真的不知道如何使用信号量来获得“类似事件”的应用程序行为
  • 恐怕帮不了你,信号量是我从未尝试过实现的东西。你到底想做什么?你在等什么活动?
  • 嗯...比如说一条要通过聊天频道发送的新消息(当然,重新实现聊天引擎不是最好的选择,只是举例)
【解决方案2】:

尽管我很喜欢 PHP,但我必须说 PHP 不是这项任务的最佳选择。 Node.js 对于这种事情要好得多,而且它的扩展性非常好。如果你有 JS 知识,实现起来也很简单。

现在,如果您不想浪费 CPU 周期,则必须创建一个 PHP 脚本,该脚本将连接到某个端口上的某种服务器。指定的服务器应该侦听所选端口上的连接,并且每隔 X 时间检查您想要检查的任何内容(例如,新帖子的 db 条目),然后它将消息发送到每个连接的客户端,表明新条目已准备好。

现在,在 PHP 中实现这个事件队列架构并没有那么难,但是用 Node.js 和 Socket.IO 实现这个需要 5 分钟,而且不用担心它是否适用于大多数浏览器.

【讨论】:

    【解决方案3】:

    我同意这里的共识,即 PHP 不是这里的最佳解决方案。您确实需要查看专门的realtime technologies 来解决将数据从服务器传输到客户端的异步问题。听起来您正在尝试实现 HTTP-Long Polling,这不是一件容易解决跨浏览器的事情。 Comet 产品的开发人员已多次解决此问题,因此我建议您查看 Comet 解决方案,或者更好的 WebSocket 解决方案,该解决方案具有对旧浏览器的后备支持。

    我建议您让 PHP 完成它擅长的 Web 应用程序功能,并为您的实时、事件、异步功能选择专用解决方案。

    【讨论】:

      【解决方案4】:

      您可以使用ZeroMQ 解决此问题。

      ZeroMQ 是一个库,它提供用于将事物(线程、进程甚至单独的机器)连接在一起的增压套接字。

      我假设您正在尝试将数据从服务器推送到客户端。好吧,一个很好的方法是使用EventSource API (polyfills available)。

      client.js

      通过 EventSource 连接到 stream.php。

      var stream = new EventSource('stream.php');
      
      stream.addEventListener('debug', function (event) {
          var data = JSON.parse(event.data);
          console.log([event.type, data]);
      });
      
      stream.addEventListener('message', function (event) {
          var data = JSON.parse(event.data);
          console.log([event.type, data]);
      });
      

      router.php

      这是一个长时间运行的过程,它会侦听传入的消息并将其发送给正在侦听的任何人。

      <?php
      
      $context = new ZMQContext();
      
      $pull = $context->getSocket(ZMQ::SOCKET_PULL);
      $pull->bind("tcp://*:5555");
      
      $pub = $context->getSocket(ZMQ::SOCKET_PUB);
      $pub->bind("tcp://*:5556");
      
      while (true) {
          $msg = $pull->recv();
          echo "publishing received message $msg\n";
          $pub->send($msg);
      }
      

      stream.php

      每个连接到该站点的用户都有自己的 stream.php。该脚本运行时间很长,并等待来自路由器的任何消息。一旦它收到一条新消息,它将以 EventSource 格式输出这条消息。

      <?php
      
      $context = new ZMQContext();
      
      $sock = $context->getSocket(ZMQ::SOCKET_SUB);
      $sock->setSockOpt(ZMQ::SOCKOPT_SUBSCRIBE, "");
      $sock->connect("tcp://127.0.0.1:5556");
      
      set_time_limit(0);
      ini_set('memory_limit', '512M');
      
      header("Content-Type: text/event-stream");
      header("Cache-Control: no-cache");
      
      while (true) {
          $msg = $sock->recv();
          $event = json_decode($msg, true);
          if (isset($event['type'])) {
              echo "event: {$event['type']}\n";
          }
          $data = json_encode($event['data']);
          echo "data: $data\n\n";
          ob_flush();
          flush();
      }
      

      要向所有用户发送消息,只需将它们发送到路由器即可。然后路由器将该消息分发到所有侦听流。这是一个例子:

      <?php
      
      $context = new ZMQContext();
      
      $sock = $context->getSocket(ZMQ::SOCKET_PUSH);
      $sock->connect("tcp://127.0.0.1:5555");
      
      $msg = json_encode(array('type' => 'debug', 'data' => array('foo', 'bar', 'baz')));
      $sock->send($msg);
      
      $msg = json_encode(array('data' => array('foo', 'bar', 'baz')));
      $sock->send($msg);
      

      这应该证明你不需要 node.js 来做实时编程。 PHP 可以处理得很好。

      除此之外,socket.io 是一种非常好的方法。您可以通过 ZeroMQ 轻松地将 socket.io 连接到您的 PHP 代码。

      另请参阅

      【讨论】:

      • 请注意:我最近做了一些基准测试。在某些时候,这不能很好地扩展。我尝试在短时间内与几百个连接的客户端发送几千条消息。如果您使用的是 Apache,它会由于线程之间的上下文切换量而变慢。在某些时候 node 或 nginx-push-stream 模块将是执行此操作的更好工具。尽管如此,使用 PHP 是可能的,而且它可以工作。
      • 只是喜欢它的简单性和描述性的例子。 6 年下来,仍然是一个最新的简单答案。谢谢!
      【解决方案5】:

      您需要一个实时库。

      一个例子是棘轮http://socketo.me/

      处理 pub sub 的部分在 http://socketo.me/docs/wamp 讨论

      这里的限制是 PHP 也需要是启动可变数据的那个。

      换句话说,这不会神奇地让您在 MySQL 更新时订阅。但是,如果您可以编辑 MySQL 设置代码,那么您可以在那里添加发布部分。

      【讨论】:

        猜你喜欢
        • 2020-07-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-05-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多