长轮询PHP返回2个结果而不是一个

时间:2014-10-07 09:45:48

标签: javascript php ajax long-polling

我正在尝试创建一个像Facebook一样的发布系统。所以我做了一些关于Facebook如何做的研究,Facebook使用长轮询,所以我搜索了如何实现它,我实现它。我终于完成了它,我打开了Firefox和Chrome来测试它。在2或3个帖子之后它起作用,但之后它将复制结果。如下所示:

Duplicate results

顺便说一句,这是第一篇文章。

这是我的网络标签,在此过程中: It makes 3 requests instead of two

它发出3个请求而不是1个。

最后这是我的代码:

init.js ,其中包含我的所有JavaScript代码

function getNewPosts(timestamp) {
  var t;
  $.ajax({
    url: 'stream.php',
    data: 'timestamp=' + timestamp,
    dataType: 'JSON',
})
  .done(function(data) {
    clearInterval( t );
    // If there was results or no results
    // In both cases we start another AJAX request for long polling after 1 second
    if (data.message_content == 'results' || data.message_content == 'no-results') {
        t = setTimeout( function() {
            getNewPosts(data.timestamp);
        }, 1000);
        // If there was results we will append it to the post div
        if (data.message_content ==  'results') {
            // Loop through each post and output it to the screen
            $.each(data.posts, function(index, val) {
                $("<div class='post'>" + val.post_content + "<div class='post_datePosted'>"+ val.posted_date +"</div> <br>" + "</div>").prependTo('.posts');
            });
        }
    }
})
}

$(document).ready(function(){

    // Start the autosize function
    $('textarea').autosize();

    // Create an AJAX request to the server for the first time to get the posts
    $.ajax({
        async: false,
        url: 'stream.php?full_page_reload=1',
        type: 'GET',
        dataType: 'JSON',
    })
    .done(function(data) {
        // Assign the this variable to the server timestamp
        // that was given by the PHP script
        serverTimestamp = data.timestamp;
        $.each(data.posts, function(index, val) {
            $("<div class='post'>" + val.post_content + "<div class='post_datePosted'>"+ val.posted_date +"</div>" + "</div>").prependTo('.posts');
        });
    })
    .fail(function() {
        alert('There was an error!');
    })
    // When the form is submitted
    $('#post_form').on('submit', function(event) {
        $.ajax({
            url: 'ajax/post.php',
            type: 'POST',
            dataType: 'JSON',
            data: $('#post_form').serialize()
        })
        .done(function(data) {
            // Reset the form values
            $('#post_form')[0].reset();
        })
        .fail(function() {
            // When there was an error
            alert('An error occured');
        })
        // Prevent the default action
        event.preventDefault();
    });
    // Start the actual long polling when DOM is ready
    getNewPosts(serverTimestamp);
});

我的 stream.php

<?php
header('Content-type: application/json');
// If it was a full page reload
$lastId = isset($_GET['lastId']) && !empty($_GET['lastId']) ? $_GET['lastId'] : 0;
if (isset($_GET['full_page_reload']) && $_GET['full_page_reload'] == 1) {
    $first_ajax_call = (int)$_GET['full_page_reload'];

    // Create a database connection
    $pdo = new PDO('mysql:host=localhost;dbname=test', 'akar', 'raparen');
    $sql = "SELECT * FROM `posts`";
    $stmt = $pdo->prepare($sql);
    $stmt->execute();
    $posts = $stmt->fetchAll(PDO::FETCH_ASSOC);
    // Output the timestamp since its full page reload
    echo json_encode(array(
        'fullPageReload' => 'true',
        'timestamp' => time(),
        'posts' => $posts
        ));
} else if (isset($_GET['timestamp'])) {
    // The wasted time
    $time_wasted = 0;
    // Database connection
    $pdo = new PDO('mysql:host=localhost;dbname=test', 'akar', 'raparen');
    $timestamp = $_GET['timestamp'];
    // Format the timestamp to SQL format
    $curr_time = date('Y-m-d H:i:s', $timestamp);
    $sql = "SELECT * FROM `posts` WHERE posted_date >= :curr_time";
    $stmt = $pdo->prepare($sql);
    $stmt->bindValue(':curr_time', $curr_time);
    $stmt->execute();
    // Fetch the results as an Associative array
    $posts = $stmt->fetchAll(PDO::FETCH_ASSOC);
    // If there wasn't any results
    if ( $stmt->rowCount() <= 0 ) {
        // Create the main loop
        while ($stmt->rowCount() <= 0) {
            // If there is still no results or new posts
            if ($stmt->rowCount() <= 0) {
                // If we waited 60 seconds and still no results
                if ($time_wasted >= 60) {
                    die(json_encode(array(
                        'message_type' => 'error',
                        'message_content' => 'no-results',
                        'timestamp' => time()
                        )));
                }
                // Helps the server a little bit
                sleep(1);
                $sql = "SELECT * FROM `posts` WHERE posted_date >= :curr_time";
                $stmt = $pdo->prepare($sql);
                $stmt->bindParam(':curr_time', $curr_time);
                $stmt->execute();
                $posts = $stmt->fetchAll(PDO::FETCH_ASSOC);
                // Increment the time_wasted variable by one
                $time_wasted += 1;
            }
        }
    }
    // If there was results then we output it.
    if ($stmt->rowCount() > 0) {
        die( json_encode( array(
            'message_content' => 'results',
            'timestamp' => time(),
            'posts' => $posts,
            )));
        exit();
    }
}

这是我的ajax/post.php

<?php
if ( isset($_POST['post_content']) ) {
    $post_content = strip_tags(trim($_POST['post_content']));
    if ( empty($post_content) ) {

        /* If the user doesn't enter anything */
        echo json_encode(array(
            'message_type' => 'error',
            'message_content' => 'It seems like your post is empty'
            ));
    } else {
        $pdo = new PDO('mysql:host=localhost;dbname=test', 'akar', 'raparen');
        $sql = "INSERT INTO `posts` (`post_id`, `post_content`, `posted_date`) VALUES (NULL, :post_content, NOW());";
        $stmt = $pdo->prepare($sql);
        $stmt->bindValue(':post_content', $post_content);
        $stmt->execute();
        echo json_encode(array(
            'message_type' => 'message',
            'message_content' => 'Your post has been posted successfully.'
            ));
    }
}

如果你不明白它就问我。我知道这是脏代码,我重复了很多。我这样做是为了测试,所以它并不重要。

谢谢!

12 个答案:

答案 0 :(得分:1)

我知道这并不能完全回答你的问题,但是你所做的事情无论如何都不会起作用 - 使用PHP进行长时间轮询会在至少有一些用户时崩溃你的服务器。您使用sleep,因此PHP进程“挂起”。 PHP工作者计数(Apache,nginx或任何带PHP的服务器)都是有限的。一旦达到该计数,新连接将被拒绝。 PHP旨在快速给出响应。

对于这种类型的解决方案,我建议使用一些专为此设计的中间软件。例如,请查看Socket.IO

它是用Javascript编写的,适用于客户端(JS库)和服务器端(Node.js)。您的Node.js服务器可以使用REST API,队列(如ZeroMQ,RabbitMQ等)或任何其他传输(如socket.IO client itself)从PHP中获取事件。这样您就不会在PHP中轮询数据库,只需将新帖子添加到Node.js服务器,该服务器将此信息传递给客户端JS代码。

$pdo->prepare('INSERT INTO ..')->execute();
$dispatcher->dispatch('new_post', new PostEvent(array('id' => 123, 'text' => 'Post text')));

长轮询只是受支持的Socket.IO协议之一,到目前为止并不是最有效的协议。

如果你想避开Node.js,你可以使用Ratchet尝试ReactPHP,在客户端使用WebSockets。这可以作为单个php进程(从命令行运行),因此不是apache-way。

答案 1 :(得分:1)

坦率地说,除非你打算处理成千上万条消息,否则我不明白你为什么要进行这种优化。每次刷新页面时,您都可以获取整个批次。

每秒钟从每个客户端请求对服务器进行锤击将产生大量流量,因此优化应从定义更合理的轮询周期或更智能,适应性更新机制开始,恕我直言。

现在,如果你真的想要它,你必须进行适当的同步。如果你搞砸了时间戳,你可以跳过其他人添加的消息,就像另一个客户端触发自动刷新一样,或者获取所述消息两次。

所有超时处理都是不必要的。如果出现问题,通过Ajax发出的服务器请求将产生错误事件,这意味着连接或服务器发生故障,或者您的PHP端代码发出异议并需要修复。

了解应用程序结构:

  • 更新请求会将时间戳传递给PHP,要求检索比所述时间戳更新的所有帖子。初始值将是1/1/1970或其他任何值,以便初始提取检索所有现有帖子。响应中将包含一个新的时间戳,以便增量请求将跳过已获取的数据。
  • Javascript会定期生成此类请求(我宁愿将时间段设置为30秒左右,以避免过多的服务器负载 - 假设您的普通用户可以处理等待下一批伪推文的挫折感)< / LI>
  • 提交一个新帖子只会将其添加到数据库中,但由于所有这些都是在服务器端完成的,因此您不需要为竞争条件而烦恼。

你所有的&#34; time_wasted&#34;和&#34; cur_time&#34;代码应该转到bin。 唯一需要的时间是该特定客户的最后一次读取请求的日期 所有你需要的服务器端(在你的&#34;流&#34; PHP文件中)是一个数据库请求来获取比客户端提供的时间戳更新的帖子,这将返回一个(可能是空的)帖子列表和更新的值相同的时间戳。

坦率地说,除了这些可能令人困惑的时间戳之外,您还可以使用上次提取的帖子的唯一标识符(使用0或初始请求的任何常规值)。

答案 2 :(得分:1)

你可以用这个:

$pdo->prepare('INSERT INTO ..')->execute();
$dispatcher->dispatch('new_post', new PostEvent(array('id' => 123, 'text' => 'Post text')));

答案 3 :(得分:0)

我认为这里有不必要的代码。你需要的只是。 定义2个部分。 1-是你的表格。 2-是您的留言查看器。

好的,所以第一次加载表单时,转到数据库反向信息(可能是JSON)并填充你的viwer。 要做到这一点,在ajax完成之内,将php JSON转换为数组,创建一个循环。 对于每个元素,使用追加jquery来添加每个帖子。 http://api.jquery.com/append/

点击(提交)为evenemnt做同样的事情。 您必须先清除htm内容才能填充新帖子。

答案 4 :(得分:0)

正如您在评论中指出的那样,存在大量冗余代码,这使得难以诊断问题。这是值得整理的,只是为了让其他阅读代码的人能够更好地诊断问题。

审查了代码之后,我就能看到这一点。

  1. Dom Ready函数启动满载的ajax请求
  2. Dom Ready函数以默认服务器时间
  3. 启动getNewPosts()
  4. 完整的ajax加载返回
  5. getNewPosts()返回
  6. 您可以通过向各种功能添加console.log()命令来验证此顺序。确切的顺序可能会有所不同,具体取决于服务器响应的速度。但是,基本问题是在步骤2开始时未设置serverTimestamp值。

    分辨率很容易,serverTimestamp变量需要正确设置。为此,请将getNewPosts()函数调用移至.done()处理程序以获取完整加载ajax请求。此时,服务器返回了可用于进一步轮询的初始时间戳值。

    // Create an AJAX request to the server for the first time to get the posts
    $.ajax({
        async: false,
        url: 'stream.php?full_page_reload=1',
        type: 'GET',
        dataType: 'JSON',
    })
    .done(function(data) {
        // Assign the this variable to the server timestamp
        // that was given by the PHP script
        serverTimestamp = data.timestamp;
        $.each(data.posts, function(index, val) {
            $("<div class='post'>" + val.post_content + "<div class='post_datePosted'>"+ val.posted_date +"</div>" + "</div>").prependTo('.posts');
        });
    
        // Start the long poll loop
        getNewPosts(serverTimestamp);
    })
    

答案 5 :(得分:0)

您可以设置超时:

setTimeout()

但你正在使用

clearInterval()

要清除它,请改用clearTimeout()

答案 6 :(得分:0)

如果您重现问题,请在您的网络标签中查看显示该条目两次的请求并检查持续时间,您会看到响应时间超过1秒,即&#39; 1000&#39;因此,您的javascript中超时,我认为没有必要使用该超时。

所有工作正常并且只显示一次条目的请求,他们应该在“1000”之前得到服务器响应。 (1秒)您可以通过将时间轴属性悬停在网络标签中进行检查:

enter image description here

或者点击特定请求并切换到“定时”按钮。标签:

enter image description here

因此,根据您的代码,下面是导致显示两次条目的方案:

  1. ajax request
  2. 服务器响应超过1秒
  3. javascript计时器重新启动相同的请求。
  4. 服务器响应请求(1),javascript停止计时器。
  5. 服务器响应请求(2)

答案 7 :(得分:0)

我现在无法真正测试这一点,但我看到的最大问题是

  1. 定义区间变量t
  2. 的范围
  3. 传递时间戳的方式
  4. 您设置和清除时间间隔的时间点
  5. 您对setTimeoutclearInterval
  6. 的不一致使用

    我将编写缩写代码,主要是为了保持概念性。我可以做的最大的评论是不要使用间隔,因为AJAX调用可能需要比你的间隔更长的时间。你只需在每次完成ajax时设置一个新的超时。

    // create a closure around everything
    (function() {
        var timer,
            lastTimeStamp = <?php echo some_timestamp; ?>;
    
        function getNewPosts() {
            if(timer) clearTimeout(timer);
    
            $.ajax({
                data: lastTimeStamp
                // other values here
            })
            .done(function() {
                if( there_is_data ) {
                    lastTimeStamp = data.timestamp;
                    // render your data here
                }
            })
            .always(function() {
                timer = setTimeout(getNewPosts, 1000);
            });
        }
    
        $(document).ready(function() {
            // don't do your first ajax call, just let getNewPosts do it below
    
            // define your form submit code here
    
            getNewPosts();
        });
    }());
    

答案 8 :(得分:0)

使用Java脚本时间戳而不是从PHP返回。

因为您在执行PHP时设置时间戳,而不是在javascript setTimeOut完成时设置时间戳。

因此可能是获取帖子的时间和时间,PHP文件中的帖子是相同的,并且发回该时间戳将使您再次获取该记录,因为它适合在给定条件下。

在setTimeOut之后设置时间戳将为您提供一个与发布时间不同的新时间戳。

答案 9 :(得分:0)

我认为要解决此问题,您需要unbind之前提交的表单。我在创建的脚本中看到了类似的问题。

// When the form is submitted
$('#post_form').unbind('submit').on('submit', function(event) {
    $.ajax({
        url: 'ajax/post.php',
        type: 'POST',
        dataType: 'JSON',
        data: $('#post_form').serialize()
    })
    .done(function(data) {
        // Reset the form values
        $('#post_form')[0].reset();
    })
    .fail(function() {
        // When there was an error
        alert('An error occured');
    })
    // Prevent the default action
    event.preventDefault();
});

答案 10 :(得分:0)

它可能正在执行两次。

也可以贴在其他地方 可能是另一个函数叫同一个。

答案 11 :(得分:0)

// If there was results we will append it to the post div
    if (data.message_content ==  'results') {
        // Loop through each post and output it to the screen
        $.each(data.posts, function(index, val) {
            $("<div class='post'>" + val.post_content + "<div class='post_datePosted'>"+ val.posted_date +"</div> <br>" + "</div>").prependTo('.posts');
        });
    }

我认为在将数据添加到(&#39; .post&#39;)之前,您必须清除以前的数据。

例如。 第一次:ajax结果是post1

第二次:ajax结果是post1 + post2

- &GT; .prependTo(&#39; .posts&#39;)的结果是post1 + post1 + post2