Подтвердить что ты не робот

Длительный опрос PHP возвращает 2 результата вместо одного

Я пытаюсь создать систему проводки точно так же, как Facebook. Поэтому я немного поработал над тем, как Facebook это делает, Facebook использует длительный опрос, поэтому я искал вокруг, как его реализовать, я его реализую. И я, наконец, закончил, я открыл и Firefox, и Chrome, чтобы проверить его. После 2 или 3 сообщений он работал, но затем он будет дублировать результаты. Как вы можете видеть ниже:

Duplicate results

Это первый пост, кстати.

И вот моя вкладка в сети. Во время этого процесса: It makes 3 requests instead of two

Он делает 3 запроса вместо одного.

И, наконец, вот мой код:

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 скрипт
        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.'
            ));
    }
}

Если вы этого не понимаете, просто спросите меня. Я знаю этот грязный код, и я много повторил. Я сделал это для тестирования, так что это не имеет большого значения.

Спасибо!

4b9b3361

Ответ 1

Честно говоря, я не понимаю, почему вы боксируете с такой оптимизацией, если только вы не планируете обрабатывать тысячи сообщений. Вы также можете извлечь всю лоту каждый раз, когда страница будет обновлена.

Забивание сервера с запросом от каждого клиента каждую секунду будет генерировать огромное количество трафика в любом случае, поэтому оптимизация должна начинаться с определения более разумного периода опроса или более умного, адаптивного механизма обновления, IMHO.

Теперь, если вы действительно хотите пойти на это, вам нужно будет сделать правильную синхронизацию. Если вы испортили отметки времени, вы можете пропустить сообщение, которое было добавлено кем-то другим, так как другой клиент активировал автоматическое обновление или дважды получил указанное сообщение.

Вся ваша обработка тайм-аута просто не нужна. Запрос сервера через Ajax приведет к возникновению ошибки, если что-то пойдет не так, что будет означать, что соединение или ваш сервер опустились, или ваш код на стороне PHP вызвал некоторую истерику и нуждается в исправлении.

Для представления структуры приложения:

  • запрос на обновление передаст временную метку PHP, попросив получить все сообщения, более новые, чем указанная временная метка. Начальное значение будет 1/1/1970 или что угодно, так что исходная выборка извлекает все существующие сообщения. Новая метка времени будет включена в ответ, так что инкрементные запросы будут пропускать уже извлеченные данные.
  • Javascript будет периодически генерировать такие запросы (я предпочел бы установить период до 30 секунд или около того, чтобы избежать чрезмерной нагрузки на сервер - если ваш средний пользователь может справиться с разочарованием в ожидании, которое будет длиться для следующей партии псевдо-твитов)
  • отправка нового сообщения просто добавит его в БД, но поскольку все сделано на стороне сервера, вам не нужно беспокоиться о условиях гонки.

Все ваши коды "time_wasted" и "cur_time" должны перейти в корзину. Единственная требуемая ссылка времени - это дата последнего запроса на чтение от этого конкретного клиента.
Все, что вам нужно на стороне сервера (в вашем "поточном" PHP файле) - это запрос БД для получения сообщений, более новых, чем предоставленная клиентом временная метка, которая вернет (возможно, пустой) список сообщений и обновленное значение той же метки времени.

Откровенно говоря, вместо этих потенциально запутывающих временных меток вы могли бы также использовать уникальный идентификатор последнего отправленного сообщения (используя 0 или любое обычное значение для первоначального запроса).

Ответ 2

Я знаю, что это точно не отвечает на ваш вопрос, но то, что вы делаете, в любом случае не сработает - длительный опрос с PHP приведет к краху вашего сервера, когда будут хотя бы еще несколько пользователей. Вы используете sleep, поэтому процесс PHP "висит". Количество сотрудников PHP (как для Apache, nginx, так и для любого сервера с PHP) ограничено. Как только вы достигнете этого счета, новые соединения будут отклонены. PHP предназначен для быстрого ответа.

Для такого типа решений я бы предложил использовать некоторое промежуточное программное обеспечение, предназначенное для него. Например, посмотрите Socket.IO.

Он написан в Javascript и предназначен как для клиентской стороны (JS-библиотека), так и для серверной части (Node.js). Ваш сервер Node.js может принимать события, как это происходит с PHP, используя REST API, очереди (например, ZeroMQ, RabbitMQ и т.д.) Или любой другой транспорт, например сокет. Клиент IO. Таким образом, вы не проводите опрос своей базы данных на 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, вы можете попробовать ReactPHP с помощью Ratchet, используя WebSockets на стороне клиента. Это работает как один php-процесс (выполняется из командной строки), а не apache-way.

Ответ 3

вы можете использовать это:

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

Ответ 4

Я думаю, что здесь есть ненужный код. Все, что вам нужно. Определите 2 части. 1- Это ваша форма. 2- Является ли ваш просмотрщик сообщений.

Итак, сначала загрузите форму, перейдите к информации извлечения базы данных (это может быть как JSON) и заполните ваш viwer. Чтобы сделать это, внутри сделанного ajax, преобразуйте php JSON в массив, создайте цикл. Для каждого элемента используйте append jquery для добавления каждого сообщения. http://api.jquery.com/append/

Сделайте то же самое для равномерного щелчка (отправить). Прежде чем заполнять новые сообщения, вы должны очистить содержимое htm.

Ответ 5

Как вы указали в своих комментариях, имеется избыточный код, что затрудняет диагностику проблемы. Было бы полезно убирать, чтобы другие люди, читающие код, находились в лучшем положении для диагностики проблемы.

Проанализировав код, я вижу, что это происходит.

  • Функции Dom Ready запускают запрос ajax для полной загрузки.
  • Функции Dom Ready запускают getNewPosts() с сервером по умолчанию
  • Полная загрузка ajax возвращает
  • getNewPosts() возвращает

Вы можете проверить этот порядок, добавив команды console.log() к различным функциям. Точный порядок может зависеть от того, как быстро реагирует сервер. Однако основная проблема заключается в том, что значение serverTimestamp не устанавливается при запуске шага 2.

Разрешение достаточно просто, переменная 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 скрипт
    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);
})

Ответ 6

Вы устанавливаете таймаут, например:

setTimeout()

но вы используете

clearInterval()

чтобы очистить его, используйте clearTimeout() вместо этого.

Ответ 7

На вашей сетевой вкладке, если вы воспроизведете проблему, проверьте запрос, который дважды показал запись, и проверьте продолжительность, и вы увидите, что ответ занял более 1 секунды, что является тайм-аутом "1000" в вашем javascript, поэтому я не вижу необходимости использовать этот тайм-аут.

Все запросы, которые отлично работали и отображали запись только один раз, должны были получить ответ сервера чуть раньше "1000" (1 секунду), вы можете проверить это на своей вкладке в сети, наведя атрибут временной шкалы:

enter image description here

Или нажав на конкретный запрос и переключившись на вкладку "Timing":

enter image description here

Итак, на основе вашего кода ниже приведен сценарий, который приводит к отображению записи дважды:

  • запрос ajax
  • Ответ сервера превышает 1 секунду.
  • Таймер javascript перезапускает тот же запрос.
  • сервер отвечает на запрос (1), javascript останавливает таймер.
  • сервер отвечает на запрос (2)

Ответ 8

В настоящий момент я не могу проверить это, но самые большие проблемы, которые я вижу, это

  • область, в которой вы определяете переменную интервала t
  • способ передачи метки времени
  • момент времени, когда вы устанавливаете и очищаете свой интервал
  • ваше непоследовательное использование setTimeout и clearInterval

Я собираюсь написать сокращенный код, в основном, чтобы он был концептуальным. Самый большой комментарий, который я могу сделать, - это не использовать интервалы, поскольку вызов 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();
    });
}());

Ответ 9

Используйте Java- script метку времени, скорее верните ее из PHP.

потому что вы устанавливаете временную метку, когда PHP выполняется не при завершении javascript setTimeOut.

таким образом, может быть, что время и время отправки этого сообщения внутри файла PHP одинаковы, и отправка этой временной отметки позволит вам снова извлечь эту запись, поскольку она подходит к данному условию.

установка временной метки после setTimeOut даст вам свежую метку времени, которая не может быть такой же, как ваше время публикации.

Ответ 10

Я считаю, что для исправления этого вам потребуется unbind ваше предыдущее представление формы. Я видел похожие проблемы с созданным script.

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

Ответ 11

Он может выполняться дважды.

Может быть вставлен в другое место Может быть, другая функция вызывает то же самое.

Ответ 12

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

Я думаю, что перед добавлением данных в ( ".post" ) вы должны очистить предыдущие данные.

например. первый раз: результат ajax - post1

второй раз: результат ajax - post1 + post2

- > результат .prependTo('. posts') - post1 + post1 + post2