Facebook的做法非常有趣。

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

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

在此之后,就这些投票返回的实际内容而言,它是一个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交互:

也就是说,这个插件似乎增加了一些复杂性,它在客户端上真的很简单,也许(使用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
});

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

更新

随着我不断收到关于这个问题的赞,我认为有理由记住这个答案是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)。

希望我能帮上忙。

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

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等。

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

  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