利用AJAX模拟HTTP长连接(LongPoll)来实现”服务器推”技术 一级精华

本文探讨了Comet技术在服务器推送中的核心原理、实现方式及关键组件,包括如何通过延长HTTP连接寿命来实现推送,以及心跳机制、事件队列管理等关键策略,以减少带宽消耗和服务器压力。同时,提供了简单的聊天室实现案例,涉及前端和后端代码。文章旨在帮助开发者深入了解Comet技术,提升网站实时性和用户体验。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近comet很流行,偶也来一下Comet,总结的不好别拍砖

Comet(彗星:某人给 服务器推送 技术起的名字)
核心图解:
最近在看“服务器推送技术”,在B/S结构中,通过某种magic使得客户端不需要通过轮询即可以得到服务端的最新信息(比如股票价格,聊天室,webQQ、开心网、白社会),这样可以节省大量的带宽。

传统的轮询技术对服务器的压力很大,并且造成带宽的极大浪费。如果改用ajax轮询,可以降低带宽的负荷(因为服务器返回的不是完整 页面),但是对服务器的压力并不会有明显的减少。

而推技术(push)可以改善这种情况。但因为HTTP连接的特性(短暂,必须由客户端发起),使得推技术的实现比较困难,常见的做法是通过延长http连接的寿命,来实现push。
基本实现原理:
接下来自然该讨论如何延长http连接的寿命,最简单的自然是死循环法,如果使用观察者模式则可以进一步提高性能。

但是这种做法的缺点在于客户端请求了这个servlet后,web服务器会开启一个线程执行servlet的 代码,而servlet由迟迟不肯结束,造成该线程也无法被释放。于是乎,一个客户端一个线程,当客户端数量增加时,服务器依然会承受很大的负担。

要从根本上改变这个现象比较复杂,目前的趋势是从web服务器内部入手,用nio(JDK 1.4提出的java.nio包)改写request/response的实现,再利用线程池增强服务器的资源利用率,从而 解决这个问题,目前支持这一非J2EE官方技术的服务器有Glassfish和Jetty。目前也有一些 框架/工具可以帮助你实现推功能,比如pushlets。不过没有深入研究。还有就是通过 设置超时来解决。
在客户和服务器之间保持“心跳”信息 -----无事件导致超时处理:
因为服务器为了保持请求(阻塞请求),必须有一个无限循环,循环的结束条件就是获取到了返回结果,如果客户端关闭了(客户端浏览器的关闭不会发消息给服务器),服务器无法知道客户端已经关了,这个请求没必要处理下去了,最终会造成资源过度浪费。还有服务器中间可能存在各式各样配置怪异的网关和代理,它们上面可能有各式各样的超时规则,因此Comet最好设计为定期重连。只要用一个折中的 办法,限制超时 时间。一般情况下,如果30秒没有任何事件发生,服务器端就应该通知客户端确实没有事件发生,结束掉本次请求,然后重新开始一次新的请求以便继续等待。这里可以不必设置客户端ajax的超时时间,但进行请求的时候传递一个超时值给服务器,服务器在处理的时候,如果超时时间到了的话,还没有客户端需要的结果,这时传递一个超时信息给客户端,客户端接收到了此信息,根据情况重新进行ajax请求,也就是进入下一个轮询..........当服务器处理信息出现异常情况,需要发送错误信息通知客户端,同时释放资源、关闭连接。
服务器端事件队列管理以及如何保持可靠的消息队列:
由于ajax的LongPoll是拉的方式(不同的客户端拉取的参数可以根据客户端不同而不同),服务器端根据客户选择的方式在读取事件队列(fetchEvents)时进行不同的处理,会把“heartbeat”与“refresh”事件一起传给客户端,通知客户端重新发出请求、建立连接。拉的同时也解决了发送目标的返回值。

在这里我们可以想象一个可能发生的情况,服务器端向你发送一个消息,你没有成功接收,但是服务器端认为发送了就成功了,消息从队列删除了,然后这个消息就永久丢失掉了。可能有人会强调TCP多么可靠,服务器端发送的消息如果在TCP的层面发生问题了,肯定会引发Socket级别的Exception,这个Exception冒泡上来,服务器端就能截获,从而得知发送失败,然后先不删除队首消息。可是别忘了,中间是可能存在代理的,如果代理成功把消息收回去了,可是代理发送到客户端这一步失败了,服务器端就不一定会发生异常了。

因此,我们需要制定一种策略,来确保下行消息总能发送到客户端。在这里,我们选择了引入逐个ACK的机制,来确认消息的接收。也就是说,服务器端发送给客户端的消息带有一个序号,在客户端收到消息后就将该序号发回给服务器端,已确认它受到了该消息。在下次请求时就将该序号加1的值通过sequence参数传递回去,让服务器知道我们期望下一条消息的编号是这个。例如我们收到Message,其Sequence属性为836,那么下一次调用的时候就传给服务器837。服务器端此时应该保留了编号为836的Message在对首,如果客户端继续请求836号消息,证明它上次没收到,这次仍然发送836号消息给它;如果客户端请求837号消息,证明它成功收到836号消息的,这次就发送837号消息给它。看到这里是不是感觉有点熟悉了,不错,这有点类似经典的TCP三握手原理。如果都不是,那该怎么办?那意味着,这是一个错误的请求,甚至可能是攻击请求,因为正常情况下不应该出现这样的请求的,服务器端可以考虑抛个无关紧要的Exception(不要告诉攻击者你知道他在攻击了),甚至直接给个400 (bad request)的响应代号。

国外一个简单的聊天室例子:

我们需要4个文件:
A file to exchange data (data.txt)
A PHP that will handle the persistent http request (backend.php)
A HTML file that will load Java code and that will show the data coming from the server (index.html)
The prototype library that will help us to write simple JS code

服务端backend.php:
做2件事:
Write into “data.txt” when new messages are sent
Do an infinite loop as long as “data.txt” file is unchanged
view source
print?
01
02
03 $filename = dirname(__FILE__).'/data.txt';
04
05 // store new message in the file
06 $msg = isset($_GET['msg']) ? $_GET['msg'] : '';
07 if ($msg != '')
08 {
09 file_put_contents($filename,$msg);
10 die();
11 }
12
13 // infinite loop until the data file is not modified
14 $lastmodif = isset($_GET['timestamp']) ? $_GET['timestamp'] : 0;
15 $currentmodif = filemtime($filename);
16 while ($currentmodif <= $lastmodif) // check if the data file has been modified
17 {
18 usleep(10000); // sleep 10ms to unload the CPU
19 clearstatcache();
20 $currentmodif = filemtime($filename);
21 }
22
23 // return a json array
24 $response = array();
25 $response['msg'] = file_get_contents($filename);
26 $response['timestamp'] = $currentmodif;
27 echo json_encode($response);
28 flush();
29
30 ?>
客户端index.html:
view source
print?
01 http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
02 <html <="" code="">class=t_tag onclick=tagshow(event) href="tag.php?name=xml">xmlSPAN>ns="http://www.w3.org/1999/xhtml">
03 <head>
04 <title>Comet demotitle>
05
06 <mete http-equiv="Content-Type" content="text/html; charset=utf-8" />
07 < type="text/tag.php?name=java">javaSPAN>" src="prototype.js">>
08 head>
09 <body>
10
11 <<SPAN class=t_tag onclick=tagshow(event) href="tag.php?name=div">divSPAN> id="content">
12 div>
13
14 <p>
15 <form action="" method="get">
16 <input type="text" name="word" id="word" value="" />
17 <input type="submit" name="submit" value="Send" />
18 form>
19 p>
20
21 < type="text/java">
22 var Comet = Class.create();
23 Comet.prototype = {
24
25 timestamp: 0,
26 url: './backend.php',
27 noerror: true,
28
29 initialize: function() { },
30
31 connect: function()
32 {
33 this.ajax = new Ajax.Request(this.url, {
34 method: 'get',
35 parameters: { 'timestamp' : this.timestamp },
36 onSuccess: function(transport) {
37 // handle the server response
38 var response = transport.responseText.evalJSON();
39 this.comet.timestamp = response['timestamp'];
40 this.comet.handleResponse(response);
41 this.comet.noerror = true;
42 },
43 onComplete: function(transport) {
44 // send a new ajax request when this request is finished
45 if (!this.comet.noerror)
46 // if a connection problem occurs, try to reconnect each 5 seconds
47 setTimeout(function(){ comet.connect() }, 5000);
48 else
49 this.comet.connect();
50 this.comet.noerror = false;
51 }
52 });
53 this.ajax.comet = this;
54 },
55
56 disconnect: function()
57 {
58 },
59
60 handleResponse: function(response)
61 {
62 $('content').innerHTML += '<div>' + response['msg'] + 'div>';
63 },
64
65 doRequest: function(request)
66 {
67 new Ajax.Request(this.url, {
68 method: 'get',
69 parameters: { 'msg' : request
70 });
71 }
72 }
73 var comet = new Comet();
74 comet.connect();
75 >
76
77 body>
78 html>
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值