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

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

Повторяющиеся результаты

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

И вот моя вкладка сети, во время этого процесса:
Это делает 3 запроса вместо двух

Это делает 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 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.'
));
}
}

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

Спасибо!

13

Решение

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

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

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

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

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

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

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

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

1

Другие решения

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

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

Он написан на Javascript и предназначен как для клиентской части (библиотека JS), так и для серверной части (Node.js). Ваш сервер Node.js может принимать события в том виде, как они происходят, из PHP с использованием REST API, очередей (например, ZeroMQ, RabbitMQ и т. Д.) Или любого другого транспорта, например Сам клиент socket.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.

1

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

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

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

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

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

0

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

Изучив код, я вижу, что это происходит.

  1. Функции Dom Ready запускают ajax-запрос для полной загрузки
  2. Функция Dom Ready запускает getNewPosts() со временем сервера по умолчанию
  3. Возврат полной загрузки AJAX
  4. 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 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);
})
0

Вы устанавливаете тайм-аут как:

setTimeout()

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

clearInterval()

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

0

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

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

введите описание изображения здесь

Или нажав на конкретный запрос и перейдя на вкладку «Время»:

введите описание изображения здесь

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

  1. запрос ajax
  2. ответ сервера превышает 1 секунду
  3. Таймер JavaScript повторно запускает тот же запрос.
  4. Сервер отвечает на запрос (1), JavaScript останавливает таймер.
  5. сервер отвечает на запрос (2)
0

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

  1. область, в которой вы определяете свою интервальную переменную t
  2. как вы передаете метку времени
  3. момент времени, когда вы устанавливаете и очищаете свой интервал
  4. ваше непоследовательное использование 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();
});
}());
0
По вопросам рекламы [email protected]