您的位置:首页 > 其它

Web 通信 之 长连接、长轮询(long polling)

2016-01-08 21:09 337 查看
[code]<%@ page language="java" import="java.util.*" pageEncoding="UTF-8" isELIgnored="false" %>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<html>

<head>

<meta http-equiv="pragma" content="no-cache">

<meta http-equiv="cache-control" content="no-cache">

<meta http-equiv="author" content="hoojo & http://hoojo.cnblogs.com">[/code] 
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

<%@ include file="/tags/jquery-lib.jsp"%>


<script type="text/javascript">

$(function () {


window.setInterval(function () {


$.get("${pageContext.request.contextPath}/communication/user/ajax.mvc", 

{"timed": new Date().getTime()}, 

function (data) {

$("#logs").append("[data: " + data + " ]<br/>");

});

}, 3000);


});

</script>

</head>


<body>

<div id="logs"></div>

</body>

</html>

[/code]

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

服务器端代码

[code]@RequestMapping("/ajax")

public void ajax(long timed, HttpServletResponse response) throws Exception {

 PrintWriter writer = response.getWriter();

 

 Random rand = new Random();

 // 死循环 查询有无数据变化

 while (true) {

 Thread.sleep(300); // 休眠300毫秒,模拟处理业务等

 int i = rand.nextInt(100); // 产生一个0-100之间的随机数

 if (i > 20 && i < 56) { // 如果随机数在20-56之间就视为有效数据,模拟数据发生变化

 long responseTime = System.currentTimeMillis();

 // 返回数据信息,请求时间、返回数据时间、耗时

 writer.print("result: " + i + ", response time: " + responseTime + ", request time: " + timed + ", use time: " + (responseTime - timed));

 break; // 跳出循环,返回数据

 } else { // 模拟没有数据变化,将休眠 hold住连接

 Thread.sleep(1300);

 }

 }

 

}

[/code]

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

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

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





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

[/quote]

2、普通轮询 iframe方式



[code]<%@ page language="java" import="java.util.*" pageEncoding="UTF-8" isELIgnored="false" %>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<html>

<head>

<meta http-equiv="pragma" content="no-cache">

<meta http-equiv="cache-control" content="no-cache">

<meta http-equiv="expires" content="0">

<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

<%@ include file="/tags/jquery-lib.jsp"%>


<script type="text/javascript">

$(function () {


window.setInterval(function () {

$("#logs").append("[data: " + $($("#frame").get(0).contentDocument).find("body").text() + " ]<br/>");

$("#frame").attr("src", "${pageContext.request.contextPath}/communication/user/ajax.mvc?timed=" + new Date().getTime());

// 延迟1秒再重新请求

window.setTimeout(function () {

window.frames["polling"].location.reload();

}, 1000);

}, 5000);


});

</script>

</head>


<body>

<iframe id="frame" name="polling" style="display: none;"></iframe>

<div id="logs"></div>

</body>

</html>

[/code]



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





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



3、长连接iframe方式



[code]<%@ page language="java" import="java.util.*" pageEncoding="UTF-8" isELIgnored="false" %>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<html>

<head>

<meta http-equiv="pragma" content="no-cache">

<meta http-equiv="cache-control" content="no-cache">

<meta http-equiv="author" content="hoojo & http://hoojo.cnblogs.com">[/code] 
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

<%@ include file="/tags/jquery-lib.jsp"%>


<script type="text/javascript">

$(function () {


window.setInterval(function () {

var url = "${pageContext.request.contextPath}/communication/user/ajax.mvc?timed=" + new Date().getTime();

var $iframe = $('<iframe id="frame" name="polling" style="display: none;" src="' + url + '"></iframe>');

$("body").append($iframe);


$iframe.load(function () {

$("#logs").append("[data: " + $($iframe.get(0).contentDocument).find("body").text() + " ]<br/>");

$iframe.remove();

});

}, 5000);


});

</script>

</head>


<body>


<div id="logs"></div>

</body>

</html>

[/code]

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





如果要保证有序,可以不使用setInterval,将创建iframe的方法放在load事件中即可,即使用递归方式。调整后的代码片段如下:

[code]<script type="text/javascript">

$(function () {

(function iframePolling() {

var url = "${pageContext.request.contextPath}/communication/user/ajax.mvc?timed=" + new Date().getTime();

var $iframe = $('<iframe id="frame" name="polling" style="display: none;" src="' + url + '"></iframe>');

$("body").append($iframe);


$iframe.load(function () {

$("#logs").append("[data: " + $($iframe.get(0).contentDocument).find("body").text() + " ]<br/>");

$iframe.remove();


// 递归

iframePolling();

});

})();

});

</script>

[/code]

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



4、ajax实现长连接




[code][code]<%@ page language="java" import="java.util.*" pageEncoding="UTF-8" isELIgnored="false" %>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<html>

<head>

<meta http-equiv="pragma" content="no-cache">

<meta http-equiv="cache-control" content="no-cache">

<meta http-equiv="expires" content="0">

<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

<%@ include file="/tags/jquery-lib.jsp"%>


<script type="text/javascript">

$(function () {


(function longPolling() {


$.ajax({

url: "${pageContext.request.contextPath}/communication/user/ajax.mvc",

data: {"timed": new Date().getTime()},

dataType: "text",

timeout: 5000,

error: function (XMLHttpRequest, textStatus, errorThrown) {

$("#state").append("[state: " + textStatus + ", error: " + errorThrown + " ]<br/>");

if (textStatus == "timeout") { // 请求超时

longPolling(); // 递归调用


// 其他错误,如网络错误等

} else { 

longPolling();

}

},

success: function (data, textStatus) {

$("#state").append("[state: " + textStatus + ", data: { " + data + "} ]<br/>");


if (textStatus == "success") { // 请求成功

longPolling();

}

}

});

})();


});

</script>

</head>


<body>

[/code]
[/code]



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







六、总结


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

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

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

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: