Facebook和Gmail如何发送实时通知?

280

我已经阅读了一些有关这个话题的帖子,其中包括comet、反向AJAX、HTTP流、服务器推送等等的答案。

Gmail如何实现收件箱通知?

Gmail聊天是如何在没有客户端交互的情况下发起AJAX请求的?

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

我知道需要在服务器端和客户端都进行操作。有PHP和Javascript的样例代码吗?

5个回答

441
Facebook的实现方法非常有趣。
一种常见的通知方法是在给定的时间间隔(也许每几秒钟)轮询服务器上的脚本(使用AJAX),以检查是否发生了什么。然而,这可能会消耗大量网络资源,因为您经常发送无意义的请求,因为没有发生任何事情。
Facebook的做法是使用彗星(comet)方法,而不是按时间间隔轮询,一旦一个轮询完成,就发出另一个轮询。但是,服务器上对脚本的每个请求都设置了极长的超时时间,只有当发生某些事情时,服务器才会响应该请求。如果在Facebook上打开Firebug的控制台选项卡,您可以看到这种情况的发生,其中请求脚本可能需要几分钟。这实际上相当巧妙,因为这种方法立即削减了请求的数量和发送请求的频率。您现在实际上拥有了一个允许服务器“触发”事件的事件框架。
在实际内容方面,从这些轮询返回的是JSON响应,其中包含似乎是事件列表及其信息。它被压缩了,所以有点难以阅读。
在技术方面,AJAX是此处的最佳选择,因为您可以控制请求的超时时间和许多其他内容。我建议(Stack Overflow 的老生常谈)使用 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
});

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


45
将PHP标签化为一个不具备良好可扩展性的语言/平台并不完全正确。它可以用于开发非常大规模的系统,就像Facebook一样。如果开发者做得对,那么它就会具有可扩展性;如果做得不好,那么它就不会。使用特定的Web平台并不能保证可扩展性。哦,还有,问题确实要求提到PHP。 - Alistair Evans
5
“Facebook使用PHP”这句话有点误导人,因为据我最后听到的信息是,他们开发了HipHop,专门将PHP转换为C++,因为 PHP 的性能不够好。 - cHao
14
@cHao:这是一个公正的观点,然而这篇回答是在2009年写的,在Facebook开始使用HipHop之前。当时,Facebook仍然是一个使用自己的PHP语言的大型系统。 - Alistair Evans
7
所以技术的关键是保持一个持续打开的连接,这将使服务器处于不断的紧张状态。一个普通Web服务器的典型并发连接数约为200,但Facebook在线用户数量要大得多。他们是如何做到的? - Paul
2
@Paul - 首先要考虑的是,实际上保持连接并不一定会使服务器处于持续压力之下。我想Facebook服务器会将来自客户端的挂起请求存储在某个地方,然后忘记它,直到一个线程唤醒,获取挂起请求并完成它。因此,内存消耗高,但不一定是高CPU。唯一真正的处理开销可能在于任何需要TCP保持活动的连接,这将在网络层处理。 - Alistair Evans
显示剩余12条评论

48

更新

由于我在此回答上不断收到点赞,我认为有必要提醒大家这篇回答已经四年历史。网络发展速度非常快,请注意这一点。


最近我遇到了同样的问题并进行了研究。

所给出的解决方案被称为长轮询(long polling),为了正确使用它,您必须确保您的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文档):

在jQuery1.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)。

希望我有所帮助。


你认为1能否替代另一个,还是同一项目中两种技术都需要? - t q
如果你指的是APE和NodeJS,你可以选择其中一个。如果你指的是定期的AJAX请求和我建议的那个,当缺乏socket支持时,我的解决方案可能会回退到ajax方式(请参考socket.io文档)。在这两种情况下,你只需要一个解决方案。 - Walter Macambira
1
你可以实现它。Node 让它变得非常简单。 - Walter Macambira
@MobasherFasihy,由于您在这里有一个线程休眠,请在进入循环之前启动计时器,并检查是否已经超过了超时值。 - Walter Macambira
@Walter的Socket解决方案似乎更有意义。如果提供了一个实际的例子就更好了。例如,当一个人打开(启动)与朋友的聊天框时会发生什么?Facebook如何调整到这个特定的对话并将消息推送给双方?(只是猜测:我只能想象应用程序打开一个套接字并绑定两个客户端地址,然后只需保持侦听和写入每当在框中写入消息时) - edam
显示剩余5条评论

19
根据Facebook消息系统幻灯片,Facebook使用彗星技术将消息“推送”到Web浏览器。 Facebook的彗星服务器是基于开源Erlang Web服务器mochiweb构建的。
在下面的图片中,“频道集群”一词意味着“彗星服务器”。

System overview

许多其他大型网站都会构建自己的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等。


这张图片很好地描述了情景。如果能提供一个实际的例子就更好了,例如当一个人打开(启动)与朋友的聊天框时会发生什么?Facebook如何调整到这个特定的对话并向两端推送消息?(只是猜测:我只能想象应用程序打开一个套接字并绑定两个客户端地址,然后只需保持监听和写入每当在框中写入消息时) - edam

5
长轮询的一个重要问题是错误处理。存在两种类型的错误:
  1. 请求可能会超时,在这种情况下,客户端应立即重新建立连接。当没有消息到达时,这在长轮询中是一种正常事件。

  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

5

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

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

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


3
此外,谷歌在安卓系统中使用GCM服务,开发者可以利用它来实现推送消息服务。http://developer.android.com/google/gcm/index.html如果您觉得这个答案有用,请接受。 - abhi

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