从服务器将数据推送到用户浏览器

时间:2015-06-28 17:42:56

标签: php json facebook push-notification

我想将数据从服务器推送到浏览器。我已经知道发送输出缓冲区的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));              
}
?>

9 个答案:

答案 0 :(得分:8)

正如@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;
}

答案 1 :(得分:3)

使用Comet或Prototype将是最佳实践。 Ajax会增加服务器负载。它会经常轮询服务器。这是关于使用彗星的一些必要信息。

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

答案 2 :(得分:2)

  

我找到了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技术进行广播。例如,您可以选择使用在Node中编写的流行的Socket.io库。

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

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);
});

答案 3 :(得分:1)

使用传统的HTTP协议,Web浏览器始终要求您的服务器进行响应。发送响应后,服务器将关闭连接。

诸如WebSocket之类的真正持久连接技术是浏览器与服务器建立特定类型连接的例外之一,并且在理解了意图后,服务器将保持连接打开。这样一旦将数据“推送”到浏览器,连接就会一直准备就绪。使用这种方法,不需要暂时保存数据,因为你只是“传递它”。

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

答案 4 :(得分:1)

为什么需要推?也许我在这里错过了一条线索,但除此之外它是解决这个问题的唯一方法吗?你希望能够设置一个让我们称为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();
}

或者我完全赞成这个标记?

答案 5 :(得分:0)

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

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

答案 6 :(得分:0)

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

我自己运行了一个非常讨厌的Twitter API守护程序线程池,除了来自facebook的$ _POST推送内容之外,如果我理解正确的话。它通过streamin api Firehose实时监控数百/数千个关键字集群阵列的推文。这是走的路或遭受可怕的失败否则:D恕我直言当然。这是两个守护进程的一半,称为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作为电视遥控器再次imho)。

答案 7 :(得分:0)

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

如果推送消息生成脚本位于同一服务器上,则使用一行对其进行实例化。

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

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

    evtSource.onmessage = function(e) {

       console.log(e.data);
    }

messages.php需要将标头设置为

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没有超级经验,但似乎有这个解决方案   为我工作如果有任何问题,我很想听到。

答案 8 :(得分:0)

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

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

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