长轮询/HTTP流一般问题

20

我正在尝试使用 制作一个理论上的 web 聊天应用程序。我已经阅读了关于长轮询和 HTTP 流的文章,并成功地应用了其中介绍的大多数原则。但是,还有两个主要问题我无法理解。

使用长轮询

  • 服务器如何知道已发送更新?它需要不断查询数据库吗?还是有更好的方法?

使用HTTP流

  • 在 Ajax 连接仍处于活动状态时,我该如何检查结果?我知道 jQuery 的success函数可以用于 ajax 调用,但我该如何在连接仍在进行时检查数据?

感谢您的任何回答,提前致谢。


我的一些大学同学正在开展一个使用长轮询请求(数小时)的项目。在尝试了几种技术后,他们最终选择了Orbited。我不知道具体细节,只知道应用程序现在非常稳定,他们对此感到满意。 - Jacco
除了我使用PHP,虽然我也很想学习它。 - Madara's Ghost
他们决定使用Orbited作为中间服务。应用程序的主要部分是PHP,具有JavaScript(HTML等)前端。 - Jacco
2个回答

25
是的,类似彗星的技术通常会在开始时让你的大脑爆炸——只是让你以不同的方式思考。另一个问题是 PHP 的资源并不那么丰富,因为每个人都在使用 node.js、Python、Java 等语言来实现 Comet。
我会尽力回答你的问题,希望能为大家解决一些问题。
服务器如何知道何时发送更新?它是否需要不断查询数据库,还是有更好的方法?
答案是:在最一般的情况下,您应该使用消息队列(MQ)。RabbitMQ 或内置于 Redis 存储中的 Pub/Sub 功能可能是不错的选择,尽管市场上有许多竞争解决方案可用,例如 ZeroMQ、Beanstalkd 等等。
因此,您可以订阅 MQ 事件而不是不断查询数据库,并挂起直到其他人发布您订阅的消息时 MQ 会唤醒您并发送一条消息。聊天应用程序是了解此功能的非常好的用例。
此外,我必须提到,如果您在其他语言中搜索 Comet-chat 实现,您可能会注意到一些简单的实现不使用 MQ。那么他们如何交换信息呢?事实上,这种解决方案通常作为独立的单线程异步服务器实现,因此它们可以在线程本地数组(或类似的东西)中存储所有连接,在单个循环中处理许多连接,并选择一个并在需要时通知。这些异步服务器实现是一种适合 Comet 技术的现代方法。但是,您很可能正在使用 mod_php 或 FastCGI 来实现 Comet,在这种情况下,这种简单的方法对您来说不是一个选项,您应该使用 MQ。
这仍然非常有用,可以了解如何实现一个独立的异步Comet服务器来处理单个线程中的多个连接。PHP的最新版本支持Libevent和Socket Streams,因此也可以在PHP中实现这种类型的服务器。PHP文档中还提供了一个example
当Ajax连接仍然活动时,我如何检查结果?我知道jQuery的ajax调用中有成功函数,但是在连接仍在进行时如何检查数据?
如果您使用普通的Ajax技术(例如纯XHR、jQuery Ajax等)进行长时间运行的轮询,则没有简单的方法可以在单个Ajax请求中传输多个响应。正如您提到的,您只有“success”处理程序来处理整个响应,而不是它的一部分。作为一种解决方法,人们每次只发送一个响应,并在“success”处理程序中处理它,然后他们只是打开一个新的长轮询请求。这就是HTTP协议的工作原理。
还应该提到,实际上有一些解决方法可以使用各种技术来实现类似流式功能,例如在隐藏的IFRAME中使用无限长的页面或使用多部分HTTP响应。这两种方法都有一定的缺点(前者被认为是不可靠的,有时可能会产生意外的浏览器行为,例如无限加载指示器,而后者泄露了一致且直接的跨浏览器支持,但某些应用程序仍然成功地依赖于该机制并在浏览器无法正确处理多部分响应时回退到长轮询)。
如果您想以可靠的方式处理单个请求/连接中的多个响应,您应该考虑使用更先进的技术,例如WebSocket,它受到大多数当前浏览器的支持,或在支持原始套接字(例如Flash或者如果您开发移动应用程序)的任何平台上。
“请问你能详细介绍一下消息队列吗?” 消息队列是一个术语,它描述了独立实现(或内置实现)观察者模式(也称为“发布/订阅”或简称PubSub)。如果您开发一个大型应用程序,则拥有一个非常有用 - 它允许您解耦系统的不同部分,实现事件驱动的异步设计,并使您的生活变得更加轻松,特别是在异构系统中。它有许多应用于真实世界的系统,我将提到其中的几个:
任务队列。假设我们正在编写自己的YouTube并需要在后台转换用户的视频文件。我们应该有一个带有UI的Web应用程序来上传电影和一些固定数量的工作进程来转换视频文件(也许我们甚至需要一些专用服务器,其中我们的工作人员将离开)。此外,我们可能需要使用C语言编写我们的工作人员以确保更好的性能。我们所要做的就是设置一个消息队列服务器,从Web应用程序收集和传递视频转换任务到我们的工作人员。当工作人员生成时,它连接到MQ并处于空闲状态等待新任务。当有人上传视频文件时,Web应用程序连接到MQ并发布一个带有新作业的消息。强大的MQ(例如RabbitMQ)可以在连接的工作人员之间平均分配任务,跟踪已完成的任务,确保不会丢失任何内容,并提供故障转移甚至管理UI以浏览当前待处理任务和统计信息。
异步行为。我们的Comet聊天是一个很好的例子。显然,我们不想一直轮询数据库(那么使用Comet有什么用呢? - 周期性Ajax请求没有太大区别)。我们需要有人在新聊天消息出现时通知我们。而消息队列就是这个人。假设我们正在使用Redis键/值存储 - 这是一个真正伟大的工具,它在其数据存储功能中提供了PubSub实现。最简单的场景可能如下所示:
1.有人进入聊天室后会发出新的Ajax长轮询请求。 2.服务器端的请求处理程序发出命令到Redis订阅“newmessage”频道。 3.一旦有人在他的聊天中输入消息,服务器端处理程序就会将消息发布到Redis的“newmessage”主题中。 4.一旦发布了消息,Redis将立即通知所有那些之前订阅了该频道的待处理程序。 5.在通知时,保持长轮询请求打开的PHP代码可以返回带有新聊天消息的请求,以便通知所有用户。他们可以立即从数据库中读取新消息,或者消息可以直接在消息负载中传输。

我希望我的说明易于理解,但消息队列是一个非常广泛的主题,因此请参考上面提到的资源以获取更多阅读材料。


你能否详细介绍一下消息队列?这是我第一次听说。 - Madara's Ghost
NP。MQ 事实上是一个有点不同的故事(而且很长),尽管我已经添加了另一个段落,介绍了这个主题的基础知识。 - coffeesnake
嗯,我目前正在学习Java以更好地理解这个概念,我会接受你的答案,非常感谢 :) - Madara's Ghost
在“您没有办法在单个Ajax请求中传输多个响应”的问题得到解决之前,我会将投票降低。之后会再次投赞成票。 - leggetter
@coffeesnake HTTP流媒体可以以可靠的跨浏览器方式实现,我知道许多软件公司在其企业软件解决方案中提供了此功能。这些解决方案已在大型金融机构中使用。我不是说它不棘手。因此,你的说法“基本上你不能这样做。如果你使用通常的Ajax技术(如纯XHR、jQuery Ajax等)进行长时间轮询,你就没有办法在单个Ajax请求中传输多个响应。”是不正确的。这就是为什么我希望你的其他方面非常好的答案得到更新,然后我会点赞。 - leggetter
显示剩余3条评论

5
我如何在Ajax连接仍处于活动状态时检查结果?我知道jQuery的ajax调用中有success函数,但是在连接仍在进行时如何检查数据?
实际上,你可以。我已经为上述问题提供了修订答案,但我不知道它是否仍在等待或被忽略。在这里提供更新,以便正确的信息可用。
如果保持客户端和服务器之间的连接开放,则可以通过推送附加到响应中的更新来进行。每次更新到达时,将触发XMLHttpRequest.onreadystatechange事件,XMLHttpRequest.readyState的值将为3。这意味着XMLHttpRequest.responseText继续增长。
你可以在这里看到一个示例: http://www.leggetter.co.uk/stackoverflow/7213549/ 要查看JS代码,只需查看源代码。PHP代码为:
<?php
$updates = $_GET['updates'];
if(!$updates) {
  $updates = 100;
}

header('Content-type: text/plain');
echo str_pad('PADDING', 2048, '|PADDING'); // initial buffer required

$sleep_time = 1;
$count = 0;
$update_suffix = 'Just keep streaming, streaming, streaming. Just keep streaming.';
while($count < 100) {
  $message = $count . ' >> ' . $update_suffix;
  echo($message);
  flush();
  $count = $count + 1;
  sleep($sleep_time);
}
?>

在基于Gecko的浏览器中,例如Firefox,可以使用multipart/x-mixed-replace完全替换responseText。我没有提供此类的示例。
看起来似乎不可能使用jQuery.ajax实现相同类型的功能。每当触发onreadystatechange事件时,success回调函数不会被触发。这令人惊讶,因为文档中指出:

没有提供onreadystatechange机制,但是,由于success、error、complete和statusCode涵盖了所有可预见的要求。

所以除非我理解有误,否则文档可能是错误的?
您可以在此处查看尝试使用jQuery的示例: http://www.leggetter.co.uk/stackoverflow/7213549/jquery.html 如果您在Firebug或Chrome Developer工具中查看网络选项卡,您将看到stream.php文件大小增加,但success回调仍未被触发。

嗯,那很奇怪,我不知道会发生这种情况(实际上我认为不会发生这种情况),你能准备一个例子吗?(它应该在最新的Firefox和Chrome中工作)。 - Madara's Ghost
好的,我更新了我的回复,并附上了一个示例链接。而且似乎jQuery.ajax没有onreadystatechange事件。 - leggetter
如上所述,我不同意HTTP流媒体是不可靠的观点。出于上述原因,它可以以可靠的方式进行开发。轮询(setTimeout/setInterval)XHR对象是一种有趣的方法。我认为我会坚持使用本机XHR对象而不是jQuery。我完全同意您的看法,WebSockets是正确的选择,但人们仍应了解有关替代解决方案的正确信息。 - leggetter
1
你的答案有问题,它只在脚本的结尾输出所有内容。你能修复一下吗? - genesis
1
@leggetter 我的意思是第一个例子应该会增长(http://www.leggetter.co.uk/stackoverflow/7213549/),但实际上没有。 - genesis
显示剩余2条评论

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