Web 通讯 之 长链接、长轮询(long polling)

基于HTTP的长链接,是一种经过长轮询方式实现"服务器推"的技术,它弥补了HTTP简单的请求应答模式的不足,极大地加强了程序的实时性和交互性。javascript

 

 

1、什么是长链接、长轮询?html

用通俗易懂的话来讲,就是客户端不停的向服务器发送请求以获取最新的数据信息。这里的“不停”实际上是有中止的,只是咱们人眼没法分辨是否中止,它只是一种快速的停下而后又当即开始链接而已。html5

 

 

2、长链接、长轮询的应用场景java

长链接、长轮询通常应用与WebIM、ChatRoom和一些须要及时交互的网站应用中。其真实案例有:WebQQ、Hi网页版、Facebook IM等。jquery

若是你对服务器端的反向Ajax感兴趣,能够参考这篇文章 DWR 反向Ajax 服务器端推的方式:http://www.cnblogs.com/hoojo/category/276235.htmlweb

欢迎你们继续支持和关注个人博客:ajax

http://hoojo.cnblogs.com算法

http://blog.csdn.net/IBM_hoojo后端

也欢迎你们和我交流、探讨IT方面的知识。跨域

email:hoojo_@126.com

 

 

3、优缺点

轮询:客户端定时向服务器发送Ajax请求,服务器接到请求后立刻返回响应信息并关闭链接。
优势:后端程序编写比较容易。
缺点:请求中有大半是无用,浪费带宽和服务器资源。
实例:适于小型应用。


长轮询:客户端向服务器发送Ajax请求,服务器接到请求后hold住链接,直到有新消息才返回响应信息并关闭链接,客户端处理完响应信息后再向服务器发送新的请求。
优势:在无消息的状况下不会频繁的请求,耗费资源小。
缺点:服务器hold链接会消耗资源,返回数据顺序无保证,难于管理维护。
实例:WebQQ、Hi网页版、Facebook IM。

长链接:在页面里嵌入一个隐蔵iframe,将这个隐蔵iframe的src属性设为对一个长链接的请求或是采用xhr请求,服务器端就能源源不断地往客户端输入数据。
优势:消息即时到达,不发无用请求;管理起来也相对方便。
缺点:服务器维护一个长链接会增长开销。
实例:Gmail聊天


Flash Socket:在页面中内嵌入一个使用了Socket类的 Flash 程序JavaScript经过调用此Flash程序提供的Socket接口与服务器端的Socket接口进行通讯,JavaScript在收到服务器端传送的信息后控制页面的显示。
优势:实现真正的即时通讯,而不是伪即时。
缺点:客户端必须安装Flash插件;非HTTP协议,没法自动穿越防火墙。
实例:网络互动游戏。

 

 

4、实现原理

所谓长链接,就是要在客户端与服务器之间建立和保持稳定可靠的链接。其实它是一种很早就存在的技术,可是因为浏览器技术的发展比较缓慢,没有为这种 机制的实现提供很好的支持。因此要达到这种效果,须要客户端和服务器的程序共同配合来完成。一般的作法是,在服务器的程序中加入一个死循环,在循环中监测 数据的变更。当发现新数据时,当即将其输出给浏览器并断开链接,浏览器在收到数据后,再次发起请求以进入下一个周期,这就是常说的长轮询(long- polling)方式。以下图所示,它一般包含如下几个关键过程:

image

1. 轮询的创建
创建轮询的过程很简单,浏览器发起请求后进入循环等待状态,此时因为服务器还未作出应答,因此HTTP也一直处于链接状态中。
2. 数据的推送
在循环过程当中,服务器程序对数据变更进行监控,如发现更新,将该信息输出给浏览器,随即断开链接,完成应答过程,实现“服务器推”。
3. 轮询的终止
轮询可能在如下3种状况时终止:
  3.1. 有新数据推送
   当循环过程当中服务器向浏览器推送信息后,应该主动结束程序运行从而让链接断开,这样浏览器才能及时收到数据。
  3.2. 没有新数据推送
   循环不能一直持续下去,应该设定一个最长时限,避免WEB服务器超时(Timeout),若一直没有新信息,服务器应主动向浏览器发送本次轮询无新信息的正常响应,并断开链接,这也被称为“心跳”信息。
  3.3. 网络故障或异常
   因为网络故障等因素形成的请求超时或出错也可能致使轮询的意外中断,此时浏览器将收到错误信息。
4. 轮询的重建
浏览器收到回复并进行相应处理后,应立刻从新发起请求,开始一个新的轮询周期。

 

5、程序设计

一、普通轮询 Ajax方式

 

客户端代码片断

 1 <%@ page language="java" import="java.util.*" pageEncoding="UTF-8" isELIgnored="false" %>
 2 
 3 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
 4 
 5 <html>
 6 
 7     <head>
 8 
 9         <meta http-equiv="pragma" content="no-cache">
10 
11         <meta http-equiv="cache-control" content="no-cache">
12 
13         <meta http-equiv="author" content="hoojo & http://hoojo.cnblogs.com">
14 
15         <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
16 
17         <%@ include file="/tags/jquery-lib.jsp"%>
18 
19         
20 
21         <script type="text/javascript">
22 
23             $(function () {
24 
25             
26 
27                 window.setInterval(function () {
28 
29                 
30 
31                     $.get("${pageContext.request.contextPath}/communication/user/ajax.mvc", 
32 
33                         {"timed": new Date().getTime()}, 
34 
35                         function (data) {
36 
37                             $("#logs").append("[data: " + data + " ]<br/>");
38 
39                     });
40 
41                 }, 3000);
42 
43                 
44 
45             });
46 
47         </script>
48 
49     </head>
50 
51     
52 
53     <body>
54 
55         <div id="logs"></div>
56 
57     </body>
58 
59 </html>

客户端实现的就是用一种普通轮询的结果,比较简单。利用setInterval不间断的刷新来获取服务器的资源,这种方式的优势就是简单、及时。缺 点是连接多数是无效重复的;响应的结果没有顺序(由于是异步请求,当发送的请求没有返回结果的时候,后面的请求又被发送。而此时若是后面的请求比前面的请 求要先返回结果,那么当前面的请求返回结果数据时已是过期无效的数据了);请求多,难于维护、浪费服务器和网络资源。

 

服务器端代码

 1 @RequestMapping("/ajax")
 2 
 3 public void ajax(long timed, HttpServletResponse response) throws Exception {
 4 
 5      PrintWriter writer = response.getWriter();
 6 
 7      
 8 
 9      Random rand = new Random();
10 
11      // 死循环 查询有无数据变化
12 
13      while (true) {
14 
15          Thread.sleep(300); // 休眠300毫秒,模拟处理业务等
16 
17          int i = rand.nextInt(100); // 产生一个0-100之间的随机数
18 
19          if (i > 20 && i < 56) { // 若是随机数在20-56之间就视为有效数据,模拟数据发生变化
20 
21              long responseTime = System.currentTimeMillis();
22 
23              // 返回数据信息,请求时间、返回数据时间、耗时
24 
25              writer.print("result: " + i + ", response time: " + responseTime + ", request time: " + timed + ", use time: "26                           + (responseTime - timed)  );
27              break; // 跳出循环,返回数据
28 
29          } else { // 模拟没有数据变化,将休眠 hold住链接
30 
31              Thread.sleep(1300);
32 
33          }
34 
35      }
36 
37      
38 
39 }

服务器端实现,这里就模拟下程序监控数据的变化。上面代码属于SpringMVC 中controller中的一个方法,至关于Servlet中的一个doPost/doGet方法。若是没有程序环境适应servlet便可,将方法体中 的代码copy到servlet的doGet/doPost中便可。

服务器端在进行长链接的程序设计时,要注意如下几点:
1. 服务器程序对轮询的可控性

因为轮询是用死循环的方式实现的,因此在算法上要保证程序对什么时候退出循环有彻底的控制能力,避免进入死循环而耗尽服务器资源。
2. 合理选择“心跳”频率
从图1能够看出,长链接必须由客户端不停地进行请求来维持,因此在客户端和服务器间保持正常的“心跳”至为关键,参数POLLING_LIFE应小于WEB服务器的超时时间,通常建议在10~20秒左右。
3. 网络因素的影响
在实际应用时,从服务器作出应答,到下一次循环的创建,是有时间延迟的,延迟时间的长短受网络传输等多种因素影响,在这段时间内,长连 接处于暂时断开的空档,若是刚好有数据在这段时间内发生变更,服务器是没法当即进行推送的,因此,在算法设计上要注意解决因为延迟可能形成的数据丢失问 题。
4. 服务器的性能
在长链接应用中,服务器与每一个客户端实例都保持一个持久的链接,这将消耗大量服务器资源,特别是在一些大型应用系统中更是如此,大量并发的长链接有可能导 致新的请求被阻塞甚至系统崩溃,因此,在进行程序设计时应特别注意算法的优化和改进,必要时还须要考虑服务器的负载均衡和集群技术。

image

上图是返回的结果,能够看到先发出请求,不必定会最早返回结果。这样就不能保证顺序,形成脏数据或无用的链接请求。可见对服务器或网络的资源浪费。

二、普通轮询 iframe方式

 1     <%@ page language="java" import="java.util.*" pageEncoding="UTF-8" isELIgnored="false" %>
 2 
 3     <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
 4 
 5     <html>
 6 
 7         <head>
 8 
 9             <meta http-equiv="pragma" content="no-cache">
10 
11             <meta http-equiv="cache-control" content="no-cache">
12 
13             <meta http-equiv="expires" content="0">
14 
15             <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
16 
17             <%@ include file="/tags/jquery-lib.jsp"%>
18 
19             
20 
21             <script type="text/javascript">
22 
23                 $(function () {
24 
25                 
26 
27                     window.setInterval(function () {
28 
29                         $("#logs").append("[data: " + $($("#frame").get(0).contentDocument).find("body").text() + " ]<br/>");
30 
31                         $("#frame").attr("src", "${pageContext.request.contextPath}/communication/user/ajax.mvc?timed=" + new Date().getTime());
32 
33                         // 延迟1秒再从新请求
34 
35                         window.setTimeout(function () {
36 
37                             window.frames["polling"].location.reload();
38 
39                         }, 1000);
40 
41                     }, 5000);
42 
43                     
44 
45                 });
46 
47             </script>
48 
49         </head>
50 
51         
52 
53         <body>
54 
55             <iframe id="frame" name="polling" style="display: none;"></iframe>
56 
57             <div id="logs"></div>
58 
59         </body>
60 
61     </html>

这里的客户端程序是利用隐藏的iframe向服务器端不停的拉取数据,将iframe获取后的数据填充到页面中便可。同ajax实现的基本原理一 样,惟一不一样的是当一个请求没有响应返回数据的状况下,下一个请求也将开始,这时候前面的请求将被中止。若是要使程序和上面的ajax请求同样也能够办 到,那就是给每一个请求分配一个独立的iframe便可。下面是返回的结果:

image

其中红色是没有成功返回请求就被中止(后面请求开始)掉的请求,黑色是成功返回数据的请求。

三、长链接iframe方式

 1 <%@ page language="java" import="java.util.*" pageEncoding="UTF-8" isELIgnored="false" %>
 2 
 3 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
 4 
 5 <html>
 6 
 7     <head>
 8 
 9         <meta http-equiv="pragma" content="no-cache">
10 
11         <meta http-equiv="cache-control" content="no-cache">
12 
13         <meta http-equiv="author" content="hoojo & http://hoojo.cnblogs.com">
14 
15         <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
16 
17         <%@ include file="/tags/jquery-lib.jsp"%>
18 
19         
20 
21         <script type="text/javascript">
22 
23             $(function () {
24 
25             
26 
27                 window.setInterval(function () {
28 
29                     var url = "${pageContext.request.contextPath}/communication/user/ajax.mvc?timed=" + new Date().getTime();
30 
31                     var $iframe = $('<iframe id="frame" name="polling" style="display: none;" src="' + url + '"></iframe>');
32 
33                     $("body").append($iframe);
34 
35                 
36 
37                     $iframe.load(function () {
38 
39                         $("#logs").append("[data: " + $($iframe.get(0).contentDocument).find("body").text() + " ]<br/>");
40 
41                         $iframe.remove();
42 
43                     });
44 
45                 }, 5000);
46 
47                 
48 
49             });
50 
51         </script>
52 
53     </head>
54 
55     
56 
57     <body>
58 
59         
60 
61         <div id="logs"></div>
62 
63     </body>
64 
65 </html>

这个轮询方式就是把刚才上面的稍微改下,每一个请求都有本身独立的一个iframe,当这个iframe获得响应的数据后就把数据push到当前页面 上。使用此方法已经相似于ajax的异步交互了,这种方法也是不能保证顺序的、比较耗费资源、并且老是有一个加载的条在地址栏或状态栏附件(固然要解决可 以利用htmlfile,Google的攻城师们已经作到了,网上也有封装好的lib库),但客户端实现起来比较简单。

 image

若是要保证有序,能够不使用setInterval,将建立iframe的方法放在load事件中便可,即便用递归方式。调整后的代码片断以下:

 1 <script type="text/javascript">
 2 
 3     $(function () {
 4 
 5         (function iframePolling() {
 6 
 7             var url = "${pageContext.request.contextPath}/communication/user/ajax.mvc?timed=" + new Date().getTime();
 8 
 9             var $iframe = $('<iframe id="frame" name="polling" style="display: none;" src="' + url + '"></iframe>');
10 
11             $("body").append($iframe);
12 
13         
14 
15             $iframe.load(function () {
16 
17                 $("#logs").append("[data: " + $($iframe.get(0).contentDocument).find("body").text() + " ]<br/>");
18 
19                 $iframe.remove();
20 
21                 
22 
23                 // 递归
24 
25                 iframePolling();
26 
27             });
28 
29         })();    
30 
31     });
32 
33 </script>

这种方式虽然保证了请求的顺序,可是它不会处理请求延时的错误或是说很长时间没有返回结果的请求,它会一直等到返回请求后才能建立下一个iframe请求,总会和服务器保持一个链接。和以上轮询比较,缺点就是消息不及时,但保证了请求的顺序。

四、ajax实现长链接

 1     <%@ page language="java" import="java.util.*" pageEncoding="UTF-8" isELIgnored="false" %>
 2 
 3     <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
 4 
 5     <html>
 6 
 7         <head>
 8 
 9             <meta http-equiv="pragma" content="no-cache">
10 
11             <meta http-equiv="cache-control" content="no-cache">
12 
13             <meta http-equiv="expires" content="0">
14 
15             <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
16 
17             <%@ include file="/tags/jquery-lib.jsp"%>
18 
19             
20 
21             <script type="text/javascript">
22 
23                 $(function () {
24 
25                 
26 
27                     (function longPolling() {
28 
29                     
30 
31                         $.ajax({
32 
33                             url: "${pageContext.request.contextPath}/communication/user/ajax.mvc",
34 
35                             data: {"timed": new Date().getTime()},
36 
37                             dataType: "text",
38 
39                             timeout: 5000,
40 
41                             error: function (XMLHttpRequest, textStatus, errorThrown) {
42 
43                                 $("#state").append("[state: " + textStatus + ", error: " + errorThrown + " ]<br/>");
44 
45                                 if (textStatus == "timeout") { // 请求超时
46 
47                                         longPolling(); // 递归调用
48 
49                                     
50 
51                                     // 其余错误,如网络错误等
52 
53                                     } else { 
54 
55                                         longPolling();
56 
57                                     }
58 
59                                 },
60 
61                             success: function (data, textStatus) {
62 
63                                 $("#state").append("[state: " + textStatus + ", data: { " + data + "} ]<br/>");
64 
65                                 
66 
67                                 if (textStatus == "success") { // 请求成功
68 
69                                     longPolling();
70 
71                                 }
72 
73                             }
74 
75                         });
76 
77                     })();
78 
79                     
80 
81                 });
82 
83             </script>
84 
85         </head>
86      <body>
87   </html>      
88 
89        

上面这段代码就是才有Ajax的方式完成长链接,主要优势就是和服务器始终保持一个链接。若是当前链接请求成功后,将更新数据而且继续建立一个新的 链接和服务器保持联系。若是链接超时或发生异常,这个时候程序也会建立一个新链接继续请求。这样就大大节省了服务器和网络资源,提升了程序的性能,从而也 保证了程序的顺序。

image

 

6、总结

现代的浏览器都支持跨域资源共享(Cross-Origin Resource Share,CORS)规范,该规范容许XHR执行跨域请求,所以基于脚本的和基于iframe的技术已成为了一种过期的须要。

把Comet作为反向Ajax的实现和使用的最好方式是经过XMLHttpRequest对象,该作法提供了一个真正的链接句柄和错误处理。固然你 选择经由HTTP长轮询使用XMLHttpRequest对象(在服务器端挂起的一个简单的Ajax请求)的Comet模式,全部支持Ajax的浏览器也 都支持该种作法。

基于HTTP的长链接技术,是目前在纯浏览器环境下进行即时交互类应用开发的理想选择,随着浏览器的快速发展,html5将为其提供更好的支持和更 普遍的应用。在html5中有一个websocket 能够很友好的完成长链接这一技术,网上也有相关方面的资料,这里也就再也不作过多介绍。

相关文章
相关标签/搜索