jQuery轮询聊天-重复条目

3
以下是我的情景:
  • 当用户发送新消息时,我将其作为“预览”附加到对话线程中,同时使用HTTP POST请求将其保存到服务器。
  • 通过使用setInterval间隔,我会检查对话中是否有新消息。
  • 如果返回任何新消息,则我会删除消息的预览版本,然后附加来自数据库的任何新消息。
以下是生成聊天内容的脚本:
function refresh_chat(){
    var last = $('.conversation li:not(.fake):last').data('id');
    $.post('includes/router.php', {
        task: 'update_conversation',
        id: '<?=$_GET['conversationid']?>',
        last: last
    }, function (data, response) {
        var recibidas = $(data).find('li');

        /* IF there are new entries */
        if (recibidas.length > 0) {
            /* Remove all fake entries */
            $('.conversation li.fake').remove();

            /* Append new entries */
            $('.conversation').append($(data).filter('.notifications').html());

            /* If this new entries are not unread, 
               remove the unread to the previous ones*/
            if(!$(data).find('li:last').hasClass('unread')) {
                $('.conversation li.unread').removeClass('unread');
            }
        }
    });
}

var t = setInterval(function () {   
    refresh_chat();
}, 3000);

当用户输入时,这是我添加新条目的方法:

$('body').on('submit', '.send_message_form_conversation', function(e) {
    e.preventDefault();
    var id_to = $(this).find('#id_to').val();
    var msj = $(this).find('#msj').val();
    if (msj.length >= 2) {
        $(this).find('#msj').val('');
        $(this).find('.nicEdit-main').html('');
        //alert(id_to);
        $('.conversation').append(
            '<li class="unread fake">' +
             '<div class="avatar">' +
              '<a href="index.php?userid=<?=sesion()?>">' +
               '<img alt="" src="<?=$_SESSION['avatar']?>">' +
              '</a>' +
             '</div>' +
             '<div class="txt">' +
              '<a class="userName" href="index.php?userid=<?=sesion()?>">' + 
               '<?=$_SESSION['alias']?> -- ' +
               '<span class="date">' +
                "<?=get_texto_clave('ahora mismo')?>" +
               '</span>' +
              '</a>
             '<span class="msj">' + msj + '</span>' + 
            '</div>' +
            '<span data-id="47" class="close">X</span>' + 
           '</li>');

        $.post('includes/msj.php?', {
            task  : 'post_message',
            id_to : id_to,
            msj   : msj
        }, function (data, response) {
            $(".conversation").scrollTop($(".conversation")[0].scrollHeight);
        });
    } else {
        $(this).parent().effect("shake", { times:0, distance: 3 }, 200);
    }                       
});

正如您所见,<li> 项可能有两个类:.fake(这意味着该项是用户刚刚提交的预览,并已由js添加)或.unread(这意味着接收者刚刚收到了消息)
我正在努力解决的问题是有时我开始看到一些重复的条目(只显示,但它们在数据库中并不重复)。我猜测我的间隔出了问题?
这可能是什么原因?(我一直在阅读它,但我找不到任何奇怪的地方...)
附注:基本上,一些消息被显示了多次:S
-编辑-
$q = "SELECT * FROM pms " .
     "WHERE ((id_to = $id and id_from = " . sesion() . ") OR " .
     "       (id_from = $id and id_to = " . sesion() . ")) " .
     "AND (id > $from) " .
     "ORDER by fecha ASC " . $limit;

这个查询是在$.post()请求中使用的,其中$from在JavaScript参数中最后一个(表示最后一条向用户显示的消息)


5
使用带有轮询的 AJAX 请求进行聊天并不是最佳实践。 - gdoron
你的 includes/router.php?task=update_conversation 脚本如何确保不会重复发送条目? - Bergi
它不会。因为数据库中没有重复项@Bergi(gdoron),那么你有什么建议? - Toni Michel Caubet
数据库中没有重复项,但是你如何控制在单个请求中向用户发送哪些条目呢?难道你会发送整个数据库吗?你应该只发送那些对用户来说是新的条目... - Bergi
能否给我们分享一些样本输出,以便检查重复项是如何被识别的? - Amit Horakeri
显示剩余2条评论
8个回答

3

考虑以下情况:

  1. refresh_chat() - 发送请求到服务器
  2. 3秒钟过去了
  3. refresh_chat() - 发送相同的请求到服务器
  4. 第一个请求的响应已经接收到。新条目已添加。
  5. 第二个请求的响应已经接收到。相同的条目再次添加。

最简单的解决方法是删除setInterval,并在处理响应后添加setTimeout。


1

尝试使用Socket.io吧!即使您将消息发送到服务器,进行解析和转换,然后再发送回来,也不会有任何明显的延迟,因此不需要进行预览。

对于聊天应用程序,使用Ajax和setTimeout只会带来痛苦。


适用于我的问题的任何示例吗? - Toni Michel Caubet

1

当问题出现时,请检查从服务器返回的数据(即ajax响应体)以查看是否还存在“旧”数据。如果是这种情况,您必须调试查询。

此外,您的DB查询非常容易受到SQL注入攻击http://en.wikipedia.org/wiki/SQL_injection(例如,使用值x'; DROP TABLE pms; --来提供最后一个参数)

 $q = "SELECT * FROM pms " .
 "WHERE ((id_to = $id and id_from = " . sesion() . ") OR " .
 "       (id_from = $id and id_to = " . sesion() . ")) " .
 "AND (id > $from) " .
 "ORDER by fecha ASC " . $limit;

最后,轮询似乎不是实现聊天应用的最佳方式。 也许使用套接字和轮询的组合(如果浏览器不支持套接字)会更好。 最好的祝愿, C


0

正如@zch所指出的那样,这个问题可能会在您的实际方法中引起问题。

我建议两种方法:

1.- 在调用refresh_chat()之前,中止最后一次向服务器的请求。通过这样做,如果响应没有到达浏览器(或者服务器根本没有回答)在这3秒钟之后,您就放弃了上一次尝试,并再次发送请求。这不是最好的方法,因为服务器资源可能会被浪费在永远不会被采取的响应上。

2.- 尝试制作一个循环计数器(例如从0到10),每次请求时增加并发送它,并让服务器将其发送回来,然后丢弃任何不相符的响应。类似于这样:

Request with 0 -> Response with 0
Request with 1 -> Response with 1
Request with 2 -> (no response)
Request with 3 -> Response with 2 (Discarded as it doesn't match)
Request with 4 -> (no response yet)
           -> Response with 3 (Discarded as 4 is already sent)
           -> Response with 4 (Accepted, actual Request was with 4)

希望您觉得这个有用。

你好Aitor,我该如何中止最后一次发送到服务器的请求? - Toni Michel Caubet
好的,$.post() 方法应该返回一个 xhr 对象,该对象应该有一个 abort 方法。因此,您可以将其存储为全局变量,如下所示:xhr = $.post(....);(将 xhr 声明为全局变量,即在任何函数之外)。然后在定期执行的函数中调用 xhr.abort(),并再次刷新聊天(refresh_chat())。这应该会中止上一次请求并创建一个新的请求。如果这有帮助,请告诉我,我会编辑我的答案。 - Aitor Calderon

0

有几个要点,'unread'类似乎没有什么作用。你已经自动刷新了,私人消息何时为已读或未读?如果有人关闭聊天窗口并稍后回来,他们应该得到所有的消息还是只有最新的消息?这些都是需要考虑的问题...

你的错误可能是因为你使用了setInterval并重复发送相同的请求,以下是一些可以纠正此问题的代码。请注意使用“last_id”变量停止重复请求,并在处理完成后才调用setTimeout。

我建议你在聊天中使用长轮询或Comet。Comet, long polling with jquery tutorial 如果你想真正走在前沿,可以选择HTML5 WebSockets,但你需要进行一些服务器配置。

function refresh_chat(){
    var last = $('.conversation li:not(.fake):last').data('id');
    if(last_id == last) {
        /* this request was already successfully processed */
        /* wait some more and try again */
        if(auto_refresh) window.setTimeout(refresh_chat, 3000);
        return;
    }
    $.ajax({
    type: 'POST',
    url: 'includes/router.php',
    data: { task: 'update_conversation',id: '<?=$_GET['conversationid']?>',last: last },
    success: function(data) {
        var recibidas = $(data).find('li');
        /* IF there are new entries */
        if (recibidas.length > 0) {
            last_id = last;
            /* Remove all fake entries */
            $('.conversation li.fake').remove();
            /* Append new entries */
            $('.conversation').append($(data).filter('.notifications').html());
        }
        //
        if(auto_refresh) window.setTimeout(refresh_chat, 3000);
    },
    error: function(jqXHR, textStatus, errorThrown) {
        console.log("Ajax Error: " + (textStatus||""));
        //
        if(auto_refresh) window.setTimeout(refresh_chat, 3000);
    }
    });
}
var auto_refresh = true;
var last_id = -1;
refresh_chat();

0
你遇到的问题与 setInterval 相关,它会每隔 3000 毫秒安排一次 refresh_chat 函数的新执行,无论当前是否有正在执行的操作。
相反,你应该自己调用 refresh_chat,然后在内部回调函数的最底部(当有数据从服务器返回时调用的函数)添加一个 setTimeout(refresh_chat, 3000)
这将确保客户端不会尝试安排对 refresh_chat 函数的新调用,直到你知道它已完成其工作。
可以将其视为一种延迟递归调用(虽然从技术上讲,它并不是递归调用,因为调用堆栈不是永远增加的)。

0

我认为这是因为只有在有新条目需要显示时,您才会“隐藏”虚假条目:

if(recibidas.length>0){
    $('.conversation li.fake').remove();

我建议将这个代码块从那个if语句中取出来。看起来你应该在发布新消息后使用remove(),比如在提交函数的function(data,response){}内部。


我不这么认为。实际上,我正在删除虚假元素(不是隐藏!!!我不知道你从哪里得到这个想法)。但是,如果recibidas.length > 0,虚假元素将被视为真实元素接收。因此,我不想保留那个虚假项目。 - Toni Michel Caubet
1
зңӢиө·жқҘдҪ зҡ„<span data-id="47" class="close">жҳҜзЎ¬зј–з Ғзҡ„гҖӮ然еҗҺеңЁдҪ зҡ„refresh_chat()еҮҪж•°дёӯпјҢдҪ дҪҝз”ЁдәҶvar last = $('.conversation li:not(.fake):last').data('id');гҖӮиҝҷдјјд№ҺиЎЁжҳҺдҪ жҖ»жҳҜе°Ҷlast: 47дј йҖ’з»ҷдҪ зҡ„'includes/router.php'йЎөйқўгҖӮиҝҷдёӘdata-idдёҚеә”иҜҘжҳҜеҠЁжҖҒзҡ„пјҢ并еҹәдәҺжңҖеҗҺеҸ‘йҖҒ/жҺҘ收зҡ„ж¶ҲжҒҜзҡ„IDеҗ—пјҹ - davehale23
不是因为最后的消息被附加到父元素中,所以:last应该选择父元素中的最后一个元素,因此是对话中的最后一条消息(已读)。 - Toni Michel Caubet

-1

这个ajax聊天根本不是解决方案。你需要读这本书。如果你没有jabber服务器,我会给你访问我的(用户注册更新等)。先读这本书,然后联系我。它是XMPP + Strophe库聊天(谷歌和Facebook正在使用的)!所以最好重新开始学习新东西,而不是修复ajax聊天中的错误!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接