278

我已经阅读了一些关于这个主题的帖子,答案是彗星、反向 ajax、http 流、服务器推送等。

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

GMail Chat 如何能够在没有客户端交互的情况下发出 AJAX 请求?

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

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

4

5 回答 5

438

Facebook 这样做的方式非常有趣。

执行此类通知的常用方法是在给定的时间间隔(可能每隔几秒)轮询服务器上的脚本(使用 AJAX),以检查是否发生了某些事情。但是,这可能会占用大量网络资源,并且您经常会发出毫无意义的请求,因为什么都没有发生。

Facebook 这样做的方式是使用彗星方法,而不是间隔轮询,一旦一个轮询完成,它就会发出另一个轮询。但是,对服务器上脚本的每个请求都有一个非常长的超时时间,服务器只有在发生某些事情时才会响应请求。如果您在 Facebook 上打开 Firebug 的“控制台”选项卡,您可以看到这种情况,对脚本的请求可能需要几分钟。这真的很巧妙,因为这种方法会立即减少请求的数量以及发送请求的频率。您现在有效地拥有一个允许服务器“触发”事件的事件框架。

在这背后,就从这些民意调查返回的实际内容而言,它是一个 JSON 响应,其中包含事件列表以及有关它们的信息。虽然它被缩小了,所以有点难以阅读。

就实际技术而言,AJAX 是要走的路,因为您可以控制请求超时以及许多其他事情。我建议(这里的堆栈溢出陈词滥调)使用 jQuery 来执行 AJAX,它会消除很多交叉兼容性问题。就 PHP 而言,您可以简单地在 PHP 脚本中轮询事件日志数据库表,并且仅在发生某些事情时才返回客户端?我希望有很多方法可以实现这一点。

实施:

服务器端:

在 PHP 中似乎有一些彗星库的实现,但老实说,它确实非常简单,可能类似于以下伪代码:

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

echo json_encode(get_events());
  • has_event_happened 函数只会检查事件表中是否发生了任何事情,然后 get_events 函数会返回表中新行的列表?真的取决于问题的上下文。

  • 不要忘记更改您的 PHP 最大执行时间,否则它会提前超时!

客户端:

看一下用于 Comet 交互的 jQuery 插件:

也就是说,该插件似乎增加了相当多的复杂性,它在客户端上确实非常简单,也许(使用 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
});

整个事情很大程度上取决于您现有架构的组合方式。

于 2009-07-06T10:58:45.760 回答
47

更新

随着我继续收到对此的支持,我认为记住这个答案已有 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是为了在没有任何反应的情况下释放你的线程来做其他事情。events将以 JSON 格式(或您喜欢的任何其他格式)返回事件字典(或您可能喜欢的任何其他数据结构)。

它确实可以解决问题,但是,如果您像我在研究时一样担心可扩展性和性能,您可能会考虑我找到的另一种解决方案。

解决方案

使用插座!

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

在服务器端,使用 NodeJS 创建一个服务器(此处为示例)。客户端将订阅与服务器一起创建的这个频道(观察者)。每当必须发送通知时,它都会在此频道中发布,并且订阅者(客户端)会收到通知。

如果您不喜欢此解决方案,请尝试 APE(Ajax Push Engine)。

希望我有所帮助。

于 2013-04-25T00:11:58.613 回答
19

According to a slideshow about Facebook's Messaging system, Facebook uses the comet technology to "push" message to web browsers. Facebook's comet server is built on the open sourced Erlang web server mochiweb.

In the picture below, the phrase "channel clusters" means "comet servers".

System overview

Many other big web sites build their own comet server, because there are differences between every company's need. But build your own comet server on a open source comet server is a good approach.

You can try icomet, a C1000K C++ comet server built with libevent. icomet also provides a JavaScript library, it is easy to use as simple as:

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 supports a wide range of Browsers and OSes, including Safari(iOS, Mac), IEs(Windows), Firefox, Chrome, etc.

于 2013-10-01T02:38:43.870 回答
5

Facebook 使用 MQTT 而不是 HTTP。推送优于轮询。通过 HTTP,我们需要不断地轮询服务器,但通过 MQTT 服务器将消息推送到客户端。

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

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

于 2013-06-01T16:45:25.700 回答
5

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

  1. 请求可能会超时,在这种情况下客户端应立即重新建立连接。当没有消息到达时,这是长轮询中的正常事件。

  2. 网络错误或执行错误。这是客户端应该优雅地接受并等待服务器重新联机的实际错误。

主要问题是,如果您的错误处理程序也立即为类型 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
于 2016-01-12T20:33:29.530 回答