我读过一些关于这个话题的帖子,答案是comet,反向ajax, http流,服务器推送等。

Gmail上的邮件接收通知是如何工作的?

GMail聊天如何能够在没有客户端交互的情况下进行AJAX请求?

我想知道是否有任何代码参考,我可以遵循写一个非常简单的例子。许多帖子或网站只是谈论这项技术。很难找到完整的示例代码。此外,似乎有很多方法可以用来实现comet,例如Hidden IFrame, XMLHttpRequest。在我看来,使用XMLHttpRequest是更好的选择。你对不同方法的优缺点有什么看法?Gmail用哪个?

我知道它需要在服务器端和客户端都这样做。 是否有任何PHP和Javascript示例代码?


当前回答

长轮询的一个重要问题是错误处理。 错误有两种类型:

请求可能会超时,在这种情况下客户端应该立即重新建立连接。这是长轮询中没有消息到达时的正常事件。 网络错误或执行错误。这是一个实际的错误,客户端应该优雅地接受并等待服务器重新联机。

主要的问题是,如果您的错误处理程序也立即为类型2错误重新建立连接,客户机将DOS服务器。

使用代码示例的两个答案都遗漏了这一点。

function longPoll() { 
        var shouldDelay = false;

        $.ajax({
            url: 'poll.php',
            async: true,            // by default, it's async, but...
            dataType: 'json',       // or the dataType you are working with
            timeout: 10000,          // IMPORTANT! this is a 10 seconds timeout
            cache: false

        }).done(function (data, textStatus, jqXHR) {
             // do something with data...

        }).fail(function (jqXHR, textStatus, errorThrown ) {
            shouldDelay = textStatus !== "timeout";

        }).always(function() {
            // in case of network error. throttle otherwise we DOS ourselves. If it was a timeout, its normal operation. go again.
            var delay = shouldDelay ? 10000: 0;
            window.setTimeout(longPoll, delay);
        });
}
longPoll(); //fire first handler

其他回答

Facebook使用MQTT而不是HTTP。推送比轮询更好。 通过HTTP,我们需要连续轮询服务器,但通过MQTT服务器将消息推送到客户端。

MQTT和HTTP之间的比较:http://www.youtube.com/watch?v=-KNPXPmx88E

注:我的答案最适合移动设备。

根据Facebook消息系统的幻灯片显示,Facebook使用comet技术向网络浏览器“推送”消息。Facebook的comet服务器是建立在开源Erlang web服务器mochiweb上的。

在下面的图片中,短语“通道集群”意味着“彗星服务器”。

许多其他大型网站都建立了自己的comet服务器,因为每个公司的需求都有所不同。但是在开源comet服务器上构建自己的comet服务器是一个很好的方法。

你可以试试icomet,一个用libevent构建的C1000K c++ comet服务器。icomet还提供了一个JavaScript库,使用起来简单如:

var comet = new iComet({
    sign_url: 'http://' + app_host + '/sign?obj=' + obj,
    sub_url: 'http://' + icomet_host + '/sub',
    callback: function(msg){
        // on server push
        alert(msg.content);
    }
});

icomet支持多种浏览器和操作系统,包括Safari(iOS, Mac), ie (Windows), Firefox, Chrome等。

更新

随着我不断收到关于这个问题的赞,我认为有理由记住这个答案是4年前的了。网络的发展速度非常快,所以请注意这个答案。


我最近也有同样的问题,并研究了这个主题。

给出的解决方案称为长轮询,要正确使用它,必须确保AJAX请求有一个“大”超时,并且总是在当前结束(超时、错误或成功)后发出此请求。

长轮询-客户端

在这里,为了保持代码简短,我将使用jQuery:

function pollTask() { 

    $.ajax({

        url: '/api/Polling',
        async: true,            // by default, it's async, but...
        dataType: 'json',       // or the dataType you are working with
        timeout: 10000,          // IMPORTANT! this is a 10 seconds timeout
        cache: false

    }).done(function (eventList) {  

       // Handle your data here
       var data;
       for (var eventName in eventList) {

            data = eventList[eventName];
            dispatcher.handle(eventName, data); // handle the `eventName` with `data`

       }

    }).always(pollTask);

}

重要的是要记住(来自jQuery文档):

在jQuery 1.4中。x及以下,XMLHttpRequest对象将在 如果请求超时,则为无效状态;访问任何对象成员 可能引发异常。仅在Firefox 3.0+中,脚本和JSONP 请求不能被超时取消;脚本将运行,即使 它在超时时间之后到达。

长轮询-服务器

它没有任何特定的语言,但它会是这样的:

function handleRequest () {  

     while (!anythingHappened() || hasTimedOut()) { sleep(2); }

     return events();

} 

在这里,hasTimedOut将确保您的代码不会永远等待,而anythingHappened将检查是否发生了任何事件。sleep是用来释放线程去做其他事情,而什么也没有发生。事件将返回JSON格式(或任何其他您喜欢的格式)的事件字典(或任何其他您喜欢的数据结构)。

它确实解决了问题,但是,如果您像我在研究时一样关心可伸缩性和性能,那么您可能会考虑我发现的另一个解决方案。

解决方案

使用套接字!

在客户端,为了避免任何兼容性问题,请使用socket.io。它尝试直接使用套接字,并在套接字不可用时回退到其他解决方案。

在服务器端,使用NodeJS创建一个服务器(示例如下)。客户端将订阅与服务器一起创建的通道(观察者)。无论何时必须发送通知,都将在此通道中发布,并通知订阅者(客户端)。

如果您不喜欢这个解决方案,可以尝试APE (Ajax Push Engine)。

希望我能帮上忙。

Facebook的做法非常有趣。

执行此类通知的一种常用方法是在给定的时间间隔(可能是每隔几秒)轮询服务器上的脚本,以检查是否发生了什么事情。然而,这可能是相当密集的网络,你经常提出毫无意义的请求,因为什么都没有发生。

The way Facebook does it is using the comet approach, rather than polling on an interval, as soon as one poll completes, it issues another one. However, each request to the script on the server has an extremely long timeout, and the server only responds to the request once something has happened. You can see this happening if you bring up Firebug's Console tab while on Facebook, with requests to a script possibly taking minutes. It is quite ingenious really, since this method cuts down immediately on both the number of requests, and how often you have to send them. You effectively now have an event framework that allows the server to 'fire' events.

在此之后,就这些投票返回的实际内容而言,它是一个JSON响应,其中包含事件列表和有关事件的信息。但是它被缩小了,所以读起来有点困难。

就实际技术而言,AJAX是解决方法,因为您可以控制请求超时和许多其他事情。我推荐(堆栈溢出陈词滥调这里)使用jQuery来做AJAX,它将采取许多交叉兼容性的问题。在PHP方面,您可以简单地轮询PHP脚本中的事件日志数据库表,并且只在发生事情时返回到客户机?我认为,有很多方法可以实现这一点。

实现:

服务器端:

PHP中似乎有一些comet库的实现,但说实话,它真的非常简单,可能就像下面的伪代码:

while(!has_event_happened()) {
   sleep(5);
}

echo json_encode(get_events());

has_event_happens函数只检查事件表中是否发生了任何事情,然后get_events函数将返回表中的新行列表?这取决于问题的背景。 不要忘记更改PHP的最大执行时间,否则会提前超时!

客户端:

看看jQuery插件做Comet交互:

项目主页:http://plugins.jquery.com/project/Comet 谷歌代码:https://code.google.com/archive/p/jquerycomet/ -在subversion存储库中似乎有某种示例用法。

也就是说,这个插件似乎增加了一些复杂性,它在客户端上真的很简单,也许(使用jQuery)是这样的:

function doPoll() {
   $.get("events.php", {}, function(result) {
      $.each(result.events, function(event) { //iterate over the events
          //do something with your event
      });
      doPoll(); 
      //this effectively causes the poll to run again as
      //soon as the response comes back
   }, 'json'); 
}

$(document).ready(function() {
    $.ajaxSetup({
       timeout: 1000*60//set a global AJAX timeout of a minute
    });
    doPoll(); // do the first poll
});

整个事情在很大程度上取决于现有架构如何组合在一起。

长轮询的一个重要问题是错误处理。 错误有两种类型:

请求可能会超时,在这种情况下客户端应该立即重新建立连接。这是长轮询中没有消息到达时的正常事件。 网络错误或执行错误。这是一个实际的错误,客户端应该优雅地接受并等待服务器重新联机。

主要的问题是,如果您的错误处理程序也立即为类型2错误重新建立连接,客户机将DOS服务器。

使用代码示例的两个答案都遗漏了这一点。

function longPoll() { 
        var shouldDelay = false;

        $.ajax({
            url: 'poll.php',
            async: true,            // by default, it's async, but...
            dataType: 'json',       // or the dataType you are working with
            timeout: 10000,          // IMPORTANT! this is a 10 seconds timeout
            cache: false

        }).done(function (data, textStatus, jqXHR) {
             // do something with data...

        }).fail(function (jqXHR, textStatus, errorThrown ) {
            shouldDelay = textStatus !== "timeout";

        }).always(function() {
            // in case of network error. throttle otherwise we DOS ourselves. If it was a timeout, its normal operation. go again.
            var delay = shouldDelay ? 10000: 0;
            window.setTimeout(longPoll, delay);
        });
}
longPoll(); //fire first handler