【问题标题】:Pushing data to user browser from server将数据从服务器推送到用户浏览器
【发布时间】:2015-09-15 03:43:26
【问题描述】:

我想将数据从服务器推送到浏览器。我已经知道发送输出缓冲区的php函数ob_flush()。我需要一些逻辑方面的帮助。我正在使用 Facebook 实时 API,所以我想在每次 Facebook 访问我的网站时向用户推送数据。

这是我试图将数据推送到浏览器但它不起作用的代码。

<?php
header('Access-Control-Allow-Origin: *');
header('Content-Type: text/event-stream');
ini_set("log_errors", 1);
ini_set("error_log", "php-error.log");
error_log( "LOGS STARTS FROM HERE" );
if(isset($_GET['hub_challenge'])){
    echo $_GET['hub_challenge'];    
}
if($_SERVER['REQUEST_METHOD'] == "POST"){
    $updates = json_decode(file_get_contents("php://input"), true); 
    // Replace with your own code here to handle the update 
    // Note the request must complete within 15 seconds.
    // Otherwise Facebook server will consider it a timeout and 
    // resend the push notification again.
    print_r($updates);
    ob_flush();
    flush();
    //file_put_contents('fb.log', print_r($updates,true), FILE_APPEND);     
    //error_log('updates = ' . print_r($updates, true));              
}
?>

【问题讨论】:

  • 你研究过 websockets 吗?像socketo.me 这样的东西可能就足够了。不过一般来说,从客户端按时间间隔运行请求通常可以解决问题。
  • 我想使用官方的php socket库php.net/manual/en/book.sockets.php但是如何用浏览器实现它
  • 我认为您正在寻找的是关于 PHP 中 websockets 的教程。为什么不搜索它,然后根据您的发现提出问题?
  • 因为我在第三方库上找到了教程。我想要官方图书馆的教程。

标签: php json facebook push-notification


【解决方案1】:

正如@som 建议的那样,您可以简单地使用请求之间的间隔,您不需要使用套接字。

但问题是,您正在尝试从 API 接收数据并将其直接传递给浏览器,一次完成。最好将这两个步骤分开。

在从 Facebook 接收数据的脚本中,将该数据存储在数据库或其他地方:

if($_SERVER['REQUEST_METHOD'] == "POST"){
    $updates = json_decode(file_get_contents("php://input"), true); 

    insertDataToDatabase($updates); // you'll have to implement this.
}

然后设置监控页面:

monitor.php

<script>
lastId = 0;

$(document).ready(function() {
    getNewDataAtInterval();
});

function getNewDataAtInterval() {
    $.ajax({
        dataType: "json",
        url: "getData.php",
        data: {lastId: lastId}
    }).done(function(data) {
        for(i=0; i<data.messages.length; i++) {
            $("#messages").append("<p>" + data.messages[i]['id'] + ": " + data.messages[i]['message'] + "</p>");
            if (data.messages[i]['id'] > lastId) lastId = data.messages[i]['id'];
        }

        setTimeout(getNewDataAtInterval, 10000);
    }).fail(function( jqXHR, textStatus ) {
        alert( "Request failed: " + jqXHR.responseText );
    });
}
</script>

<div id="messages"></div>

最后,创建一个服务器端脚本以返回一个 JSON,其中包含从数据库加载的新消息。

getData.php

$lastId = $_GET['lastId'];
$newMessages = getUpdatesFromDatabase($lastId);

exit(json_encode(array("messages"=>$newMessages)));

function getUpdatesFromDatabase($lastId) {
    // I'm using this array just as an example, so you can see it working.
    $myData = array(
        array("id"=>1,"message"=>"Hi"),
        array("id"=>2,"message"=>"Hello"),
        array("id"=>3,"message"=>"How are you?"),
        array("id"=>4,"message"=>"I'm fine, thanks")
    );

    $newMessages = array();
    foreach($myData as $item) {
        if ($item["id"] > $lastId) {
            $newMessages[] = $item;
            $newLastId = $item["id"];
        }
    }

    return $newMessages;
}

【讨论】:

  • 我不想存储任何数据。
  • 你能有任何存储吗?像一个临时文件?在这种情况下,insertDataToDatabase 将改为写入服务器中的文本文件。每当有人访问monitor.php 时,函数getUpdatesFromDatabase 就会读取它,将内容发送回客户端并删除文件。
  • 我可以访问存储数据库和临时文件。但我不想使用它。因为有时 facebook 数据会延迟一分钟,并且存储和获取数据会增加更多的处理时间。我想立即将数据推送给用户。
  • 您希望这个应用程序有多少用户以及从 Facebook 和您的用户那里获得多少数据?
  • 数据量约为 10-20 个用户(可以更改)单个请求中每个用户的 460 字节数据。然后我会将这些数据推送给单个用户(就是我)以进一步处理数据并显示在大屏幕上。
【解决方案2】:

这里最好使用 Comet 或 Prototype。 Ajax 会增加服务器负载。它会经常轮询服务器。以下是有关使用 comet 的一些必要信息。

Here is the example of how to implement comet with php to send real-time data.

【讨论】:

    【解决方案3】:

    使用传统的 HTTP 协议,网络浏览器总是请求您的服务器做出响应。发送响应后,服务器将关闭连接。

    真正的持久连接技术(如 WebSocket)是浏览器与服务器建立特定类型连接的例外之一,并且在了解意图后,服务器将保持连接打开。这样,只要有数据要“推送”到浏览器,连接就始终准备就绪。使用这种方法,无需临时保存数据,因为您只需“传递”即可。

    轮询技术(包括长轮询,其中服务器不断向客户端发送“心跳”,就好像一个无意义的响应正在缓慢滴流一样)可以用作解决方法,但是连接总是有一些时间间隔不再打开,直到下一个循环发生。当连接不存在时,您唯一的选择是临时保存数据,这样当您的浏览器返回时,您可以推送待处理的数据。如果您正在考虑将其存储在一个临时变量中,请考虑到 PHP 脚本在执行完成后,在该范围内关联的内存中分配的所有数据都会被垃圾收集。

    【讨论】:

      【解决方案4】:

      在寻找最佳解决方案时,我找到了 Pusher 和 Redis 用于将数据从服务器推送到浏览器。

      推动者

      您可以使用 Pusher 的 JavaScript SDK 使用 Pusher 驱动程序方便地消费事件广播。

      this.pusher = new Pusher('pusher-key');
      
      this.pusherChannel = this.pusher.subscribe('reference_id');
      
      this.pusherChannel.bind('SomeEvent', function(message) {
          console.log(message.user);
      });
      

      Redis

      如果您使用的是 Redis 广播器,则需要编写自己的 Redis 发布/订阅消费者来接收消息并使用您选择的 websocket 技术广播它们。例如,您可以选择使用流行的 Socket.io 库,它是用 Node 编写的。

      使用 socket.io 和 ioredis 节点库,您可以快速编写一个事件广播器来发布您的应用程序广播的所有事件:

      var app = require('http').createServer(handler);
      var io = require('socket.io')(app);
      
      var Redis = require('ioredis');
      var redis = new Redis();
      
      app.listen(6001, function() {
          console.log('Server is running!');
      });
      
      function handler(req, res) {
          res.writeHead(200);
          res.end('');
      }
      
      io.on('connection', function(socket) {
          //
      });
      
      redis.psubscribe('*', function(err, count) {
          //
      });
      
      redis.on('pmessage', function(method, channel, message) {
          message = JSON.parse(message);
          io.emit(channel + ':' + message.event, message.data);
      });
      

      【讨论】:

        【解决方案5】:

        为什么需要推送?也许我在这里错过了一个线索,但否则它是解决这个问题的唯一方法吗?您希望能够设置一个我们称为 statusUpdate 的 div 的文本,它在发布时显示来自 facebook 的新状态?那么你可以:

        将进程拆分为一个状态收集线程,该线程作为守护进程运行,不断尝试从 FB API 获取(不知道任何规范或对 FB API 有任何了解,但我只能想象那里如果有新状态,则调用查找)。

        API 是流式传输还是我们需要每隔 X 秒连接一次都没有关系,因为我们可以考虑到这一点?我会在 php 中设置一个守护程序,然后使用 SSH 使用以下命令运行它:nohup php daemon.php 以启动一个具有这样一个永无止境的循环的脚本:

        Define('SLEEP', 1);  // loop every second
        
        while (true) {  
        
           // do your thing, dont exit
        
           if( $fbMonkey->checkNewStatus() ){
                $fbMonkey->toDatabase(new_statuses);
          }
        
           if( some_reason_to_exit() == TRUE ){
              exit;
           }
        
            sleep(SLEEP);  
        }
        // While ends with break
        

        然后可能在目标用户(进程的浏览器端)的 HTML 中包含一个 JavaScript 函数,该函数从表中读取守护程序填充的状态,然后读取那些未被标记为已查看的状态(由用户或类似的东西) ) 并将未读状态返回给浏览器。如果我们在浏览器中为它创建一个永无止境的循环,让它更新 div statusUpdate 和新内容(html 或 tekst 无关紧要?)。我会让这样的电话徘徊,每 20 秒左右检查一次并更新 div。

        http://www.w3schools.com/ajax/tryit.asp?filename=tryajax_xml2

        function loadXMLDoc(url)
        {
        var xmlhttp;
        var txt,xx,x,i;
        if (window.XMLHttpRequest)
          {// code for IE7+, Firefox, Chrome, Opera, Safari
          xmlhttp=new XMLHttpRequest();
          }
        else
          {// code for IE6, IE5
          xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
          }
        xmlhttp.onreadystatechange=function()
          {
          if (xmlhttp.readyState==4 && xmlhttp.status==200)
            {
            txt="<table border='1'><tr><th>Title</th><th>Artist</th></tr>";
            x=xmlhttp.responseXML.documentElement.getElementsByTagName("CD");
            for (i=0;i<x.length;i++)
              {
              txt=txt + "<tr>";
              xx=x[i].getElementsByTagName("TITLE");
                {
                try
                  {
                  txt=txt + "<td>" + xx[0].firstChild.nodeValue + "</td>";
                  }
                catch (er)
                  {
                  txt=txt + "<td>&nbsp;</td>";
                  }
                }
            xx=x[i].getElementsByTagName("ARTIST");
              {
                try
                  {
                  txt=txt + "<td>" + xx[0].firstChild.nodeValue + "</td>";
                  }
                catch (er)
                  {
                  txt=txt + "<td>&nbsp;</td>";
                  }
                }
              txt=txt + "</tr>";
              }
            txt=txt + "</table>";
            document.getElementById('txtCDInfo').innerHTML=txt;
            }
          }
        xmlhttp.open("GET",url,true);
        xmlhttp.send();
        }
        

        或者我完全符合这里的标准?

        【讨论】:

          【解决方案6】:

          选择 1(如果您想要一个可靠的系统): 队列服务器(如 AMQP 或 MQTT 等)和 websocket 客户端(如http://www.hivemq.com/full-featured-mqtt-client-browser/)的组合将是健壮的。几乎每种语言都支持 AMQP,例如在 PHP (https://pecl.php.net/package/amqp) 中。

          选择 2: 使用 ajax 并告诉浏览器定期从服务器获取更新(如 cmets 中已经提到的)

          【讨论】:

            【解决方案7】:

            很好的讨论。我喜欢:D @andy:太好了,对我来说,第一次有人可以真正准确地解释差异,我明白了:D @Marcos Dimitrio 我同意

            我自己运行了一个非常讨厌的 Twitter API 守护程序线程池,如果我理解正确的话,除了来自 facebook 的 $_POST 推送之外,它们完全可以做到这一点。它通过流 api Firehose 实时监控数百/数千个关键字集群数组的推文。这是要走的路,否则会遭受可怕的失败:当然,恕我直言。这是 getTweets 和 parseTweets 两个守护进程的一半。

            <?php
            ob_start();
            require_once('config/phirehose-config.php');
            require_once('lib.php');
            $oDB = new db;
            
            // run as a daemon aka background process
            while (true) {
            
              // Process all statuses
              $query = 'SELECT cache_id, raw_tweet ' .
                'FROM json_cache';
              $result = $oDB->select($query);
              while($row = mysqli_fetch_assoc($result)) {
            
                $cache_id = $row['cache_id'];
            //    $status = unserialize(base64_decode($row['raw_tweet']));
                $tweet_object = json_decode($row['raw_tweet'],false);
            
            
                // JSON payload for statuses stored in the database  
                // serialized base64 raw data
            
                  // Delete cached copy of tweet
                  //    $oDB->select("DELETE FROM json_cache WHERE cache_id = $cache_id");
            
                    // Limit tweets to a single language,
                    // such as 'en' for English
                    //if ($tweet_object->lang <> 'nl') {continue;}
            
                // Test status update before inserting
                $tweet_id = $tweet_object->id_str;
            
                if ($oDB->in_table('tweets','tweet_id=' . $tweet_id )) {continue;}
            
                $tweet_text = $oDB->escape($tweet_object->text);    
                $created_at = $oDB->date($tweet_object->created_at);
                if (isset($tweet_object->geo)) {
                  $geo_lat = $tweet_object->geo->coordinates[0];
                  $geo_long = $tweet_object->geo->coordinates[1];
                } else {
                  $geo_lat = $geo_long = 0;
                } 
                $user_object = $tweet_object->user;
                $user_id = $user_object->id_str;
                $screen_name = $oDB->escape($user_object->screen_name);
                $name = $oDB->escape($user_object->name);
                $profile_image_url = $user_object->profile_image_url;
            
            
                // Add a new user row or update an existing one
                $field_values = 'screen_name = "' . $screen_name . '", ' .
                  'profile_image_url = "' . $profile_image_url . '", ' .
                  'user_id = ' . $user_id . ', ' .
                  'name = "' . $name . '", ' .
                  'location = "' . $oDB->escape($user_object->location) . '", ' . 
                  'url = "' . $user_object->url . '", ' .
                  'description = "' . $oDB->escape($user_object->description) . '", ' .
                  'created_at = "' . $oDB->date($user_object->created_at) . '", ' .
                  'followers_count = ' . $user_object->followers_count . ', ' .
                  'friends_count = ' . $user_object->friends_count . ', ' .
                  'statuses_count = ' . $user_object->statuses_count . ', ' . 
                  'time_zone = "' . $user_object->time_zone . '", ' .
                  'last_update = "' . $oDB->date($tweet_object->created_at) . '"' ;     
            
                if ($oDB->in_table('users','user_id="' . $user_id . '"')) {
                  $oDB->update('users',$field_values,'user_id = "' .$user_id . '"');
                } else {            
                  $oDB->insert('users',$field_values);
                }
            
                // percist status to database
            
                $field_values = 'tweet_id = ' . $tweet_id . ', ' ....
            
            
                //... Somethings are to be for da cook alone, its hard work          
            
                        foreach ($entities->hashtags as $hashtag) {
            
                  $where = 'tweet_id=' . $tweet_id . ' ' .
                    'AND tag="' . $hashtag->text . '"';     
            
                  if(! $oDB->in_table('tweet_tags',$where)) {
            
                    $field_values = 'tweet_id=' . $tweet_id . ', ' .
                      'tag="' . $hashtag->text . '"';   
            
                    $oDB->insert('tweet_tags',$field_values);
                  }
                }
                foreach ($entities->urls as $url) {
            
                  if (empty($url->expanded_url)) {
                    $url = $url->url;
                  } else {
                    $url = $url->expanded_url;
                  }
            
                  $where = 'tweet_id=' . $tweet_id . ' ' .
                    'AND url="' . $url . '"';       
            
                  if(! $oDB->in_table('tweet_urls',$where)) {
                    $field_values = 'tweet_id=' . $tweet_id . ', ' .
                      'url="' . $url . '"'; 
            
                    $oDB->insert('tweet_urls',$field_values);
                  }
                }       
              } 
            
              if(DEBUG){ 
                 echo ob_get_contents();
                 ob_clean();
              }else{
                 ob_clean();
              }
            
              // Longer sleep equals lower server load
              sleep(1);
            }
            ?>
            

            对于有我自己的工作人员的蜘蛛和爬虫也很有效。告诉我一个更好的方法来做到这一点,所有的东西都被认为是资源和可扩展性,作为一个用于 FB 状态更新的持久连接的网站小部件,真的就像再次使用 Echelon 作为电视遥控器恕我直言)。

            【讨论】:

              【解决方案8】:

              如果您只需要一个简单的解决方案并且不担心旧版浏览器的兼容性并且您的流量较低,server-sent events 可以解决问题。

              如果您的推送消息生成脚本在同一台服务器上,则用一行实例化它。

              var evtSource = new EventSource("messages.php");
              

              然后是处理传入消息的函数。

                  evtSource.onmessage = function(e) {
              
                     console.log(e.data);
                  }
              

              messages.php 需要将 header 设置为

              header("Content-Type: text/event-stream\n\n");
              

              然后根据需要进行无限循环设置为间隔。

              示例:

              header("Content-Type: text/event-stream\n\n");
              date_default_timezone_set("Pacific/Auckland"); // as required
              $sleepTime = 8; // # of seconds delayed between notifications
              
              while (1) 
              {   
              
                 // Send a message at $sleepTime second intervals.
                 echo 'data: The time is ' . date("H:i:s") . "\n\n";
              
                 ob_end_flush();
                 flush();
                 sleep($sleepTime);
              }
              

              显然,您需要从某个地方读取消息并根据需要显示,但至少这个示例让您了解如何创建事件流,而无需了解 WebSocket 的相对复杂性。

              免责声明:我对 PHP 不是很有经验,但这个解决方案似乎 为我工作。如果有任何问题,我很乐意听到。

              【讨论】:

                【解决方案9】:

                仅当您从服务器取回数据时才发送请求。然后在服务器休眠,直到你发现有新的东西要发送并发送回响应。

                请继续执行上述操作,直到您的页面处于活动状态并且您将看到数据被推送。 这样可以避免定期 ping 服务器。

                我不确定他们是否在这里指的是这个, https://en.wikipedia.org/wiki/Comet_(programming)

                【讨论】:

                  猜你喜欢
                  • 2010-12-29
                  • 1970-01-01
                  • 1970-01-01
                  • 2021-07-12
                  • 2014-05-16
                  • 2021-10-22
                  • 2010-09-06
                  • 2013-10-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多