长轮询在JavaScript中是如何工作的?

18

你好,我知道在长轮询中,你需要保持与服务器的连接长时间打开,直到从服务器获得响应,然后再次进行轮询并等待下一个响应。但是我不知道如何编写它。下面的代码使用长轮询,但我似乎无法理解它。

(function poll(){
$.ajax({ url: "server", success: function(data){
   //update page based on data

}, dataType: "json", complete: poll, timeout: 30000 });
})();

但是这里的连接是如何保持开放状态的呢?我知道一旦从服务器得到响应,“轮询”功能会再次触发。但是连接是如何保持开放状态的呢?

编辑1:如果有人能解释一下超时在这里实际上会做什么,那就太好了。


可能连接没有保持打开状态.... - rene
你如何防止服务器关闭连接。如果请求发送到服务器,它会响应,然后连接将自动关闭。 - Rasmus
@itamecodes:你可以编写服务器,使其在有数据可响应时才进行响应。 - Eric
可能是简单的“长轮询”示例代码?的重复。 - Joe
我认为这个人从http://techoctave.com/c7/posts/60-simple-long-polling-example-with-javascript-and-jquery获取了示例,这篇文章似乎毫无意义。它几乎两次给出了完全相同的示例,并声称其中一个是长轮询,但实际上并不是! - Luke
7个回答

17

客户端无法强迫服务器保持连接开放。 服务器只是没有关闭连接。服务器需要在某个时候说:"就这样了,这里没有更多的内容了,再见"。在长轮询中,服务器根本不会这样做,并且让客户端等待更多数据,随着更新的到来,服务器会一点点地滴出数据。这就是长轮询。

在客户端上可以定期检查已接收到的数据,同时请求还未完成。这种方式可以使服务器偶尔通过相同的开放连接发送数据。在您的情况下,这并没有被执行,成功回调仅在请求完成时才会触发。这基本上是一种廉价的长轮询形式,其中服务器让客户端等待事件,发送有关此事件的数据,然后关闭连接。客户端将其视为触发器,处理数据,然后重新连接到服务器以等待下一个事件。


我想我现在明白了...你能否分享一些骨架服务器代码以获得更清晰的理解? - Rasmus
@itamecodes 一个重要的事情:对于服务器端,您需要设置配置以避免连接超时关闭。 - VisioN
1
@itame 一个简单的示例可以是 while (!($data = checkDatabaseForData())) sleep(1); echo $data; - 这取决于你在服务器端使用的编程语言,像Node.js这样的事件驱动系统比使用sleep循环更加优雅。 - deceze
@deceze 我有一个关于“长轮询”的问题,服务器只是不会这样做,而是让客户端等待更多的数据... 服务器如何分批发送响应呢?例如,浏览器向服务器发送请求,现在如果服务器需要发送多个响应(R1、R2等)。它必须聚合所有响应并将其发送回客户端,然后关闭连接。服务器不能将部分响应R1发送到浏览器并保持连接以发送进一步的响应R2。不是吗? - emilly
@emilly 如果客户端偶尔检查已经接收到的数据,那么它是可以的。虽然我已经很久没有涉及长轮询的复杂性了,也不确定实际实现方式。但是现代浏览器都支持更好的机制,比如套接字。 - deceze
根据您之前的评论中所引用的代码"@deceze "while (!($data = checkDatabaseForData())) sleep(1); echo $data;",我认为如果没有更多的数据,那么会出现一个无限循环(每隔1秒进行一次检查,如此循环)。如果我理解正确,那么如何防止服务器无限循环(例如,当客户端浏览器关闭或服务器上长时间没有新数据可用时)? - undefined

9
我认为让人们感到困惑的原因是讨论集中在客户端编程上。长轮询不仅仅是一种客户端模式,还需要Web服务器保持连接处于打开状态。
背景:客户端希望在发生或可用时通过Web服务器通知他们,例如,当新电子邮件到达时通知我,而无需每隔几秒钟回去询问。
1.客户端打开到Web服务器上特定URL的连接。 2.服务器接受连接,打开套接字并将控制权分派给处理此连接的任何服务器端代码(例如Java中的Servlet或JSP,RoR中的路由或node/express)。 3.服务器端代码等待事件或信息的到来。例如,当电子邮件到达时,查看是否有任何“等待连接”与特定收件箱相关。如果是,则使用适当的数据进行响应。 4.客户端接收数据,执行其操作,然后开始另一个轮询请求。

3

根据您的要求,以下是一些伪NodeJS代码:

function respond_to_client(res,session,cnt)
{
    //context: res is the object we use to respond to the client
    //session: just some info about the client, irrelevant here
    //cnt: initially 0

    //nothing to tell the client, let's long poll.
    if  (nothing_to_send(res,session)) 
    {
        if (cnt<MAX_LONG_POLL_TIME)
        {
            //call this function in 100 ms, increase the counter
            setTimeout(function(){respond_to_client(request_id,res,session,cnt+1)},100);
        }
        else
        {
            close_connection(res); 
            //Counter too high.
            //we have nothing to send and we kept the connection for too long,
            //close it. The client will open another.
        }
    }
    else 
    {
        send_what_we_have(res);
        close_connection(res);
        //the client will consume the data we sent, 
        //then quickly send another request.
    }

    return;

}

3

我想处理一些交错的数据结果,其中一些会立即返回,但最后几个结果可能会在10-15秒后返回。我创建了一个快速的jQuery hack,但它有点偏离我的初衷(仍然不确定是否应该使用它):

(function($) {
    if (typeof $ !== 'function') return;
    $.longPull = function(args) {
        var opts = $.extend({ method:'GET', onupdate:null, onerror:null, delimiter:'\n', timeout:0}, args || {});
        opts.index = 0;
        var req = $.ajaxSettings.xhr();
        req.open(opts.method, opts.url, true);
        req.timeout = opts.timeout;
        req.onabort = opts.onabort || null;
        req.onerror = opts.onerror || null;
        req.onloadstart = opts.onloadstart || null;
        req.onloadend = opts.onloadend || null;
        req.ontimeout = opts.ontimeout || null;
        req.onprogress = function(e) {
            try {
                var a = new String(e.srcElement.response).split(opts.delimiter);
                for(var i=opts.index; i<a.length; i++) {
                    try {
                        var data = JSON.parse(a[i]); // may not be complete
                        if (typeof opts.onupdate==='function') opts.onupdate(data, i);
                        opts.index = i + 1;
                    } catch(fx){}
                }
            }
            catch(e){}
        };
        req.send(opts.data || null);
    };
})(jQuery);

这个方案基本没有经过测试,但它似乎符合您的要求。尽管如此,我仍然可以想象出许多可能存在的问题;-)

$.longPull({ url: 'http://localhost:61873/Test', onupdate: function(data) { console.log(data); }});

2
你只从代码中看不出它是如何工作的,因为实际上与常规请求的区别是在服务器端完成的。
JavaScript 只是发起了一个常规请求,但服务器不必立即响应该请求。如果服务器没有任何值得返回的东西(即浏览器正在等待的更改尚未发生),则服务器会等待以保持连接。
如果服务器一段时间内什么都没有发生,要么客户端会超时并发起新的请求,要么服务器可以选择返回空结果以保持流程运行。

1
我猜没有人能够很好地解释为什么我们需要在代码中使用超时。来自 jQuery Ajax 文档的描述:

设置请求的超时时间(以毫秒为单位)。这将覆盖使用 $.ajaxSetup() 设置的全局超时时间。超时时间从 $.ajax 调用时开始计算;如果有多个其他请求正在进行并且浏览器没有可用的连接,则可能会在发送请求之前超时。

timeout 选项确实不会延迟 X 秒钟后的下一次执行,它只为当前调用设置了最大超时时间。关于超时问题的好文章 - https://mashupweb.wordpress.com/2013/06/26/you-should-always-add-timeout-to-you-ajax-call-in-jquery/


1
连接并非一直保持打开状态。当从服务器接收到响应并且服务器关闭连接时,它会自动关闭。在长轮询中,服务器不应立即发送数据。在ajax的complete(服务器关闭连接时)后,将向服务器发送新请求,服务器将再次打开新连接并开始等待新响应。

正如所提到的,长轮询过程不仅由客户端处理,而主要由服务器端处理。不仅由服务器脚本(在PHP的情况下),而是由服务器本身处理,它不会按超时时间关闭“挂起”的连接。

顺便说一句,WebSockets使用与服务器端不断打开的连接,这使得可以在不关闭连接的情况下接收和发送数据。


那么这段代码与传统轮询有何不同呢?顺便说一下,我没有对这个答案进行投票。 - Rasmus
@Rasmus 传统轮询 = AJAX 轮询:所请求的网页执行 JavaScript,以在常规间隔(例如0.5秒)内从服务器请求文件。请参见-https://dev59.com/6mgu5IYBdhLWcg3wy518 - Ilya Buziuk

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