Servlet 3.0笔记之异步请求Comet推送长轮询(long polling)篇

Comet另一种形式为长轮询(long polling),客户端会与服务器建立一个持久的连接,直到服务器端有数据发送过来,服务器端断开,客户端处理完推送的数据,会再次发起一个持久的连接,循环往复。
和流(Streaming)区别主要在于,在一次长连接中,服务器端只推送一次,然后断开连接。
其实现形式大概可分文AJAX长轮询和JAVASCRIPT轮询两种。
  1. AJAX方式请求长轮询

    服务器端可以返回原始的数据,或格式化的JSON、XML、JAVASCRIPT信息,客户端可拥有更多的处理自由。在形式上和传统意义上的AJAX GET方式大致一样,只不过下一次的轮询需要等到上一次的轮询数据返回处理完方可进行。
    这里给出客户端的小示范:

    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>jQuery 1.5 with long poll</title>
    </head>
    <body>
    <div id="tip"></div>
    <script type="text/javascript" src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.5.min.js"></script>
    <script type="text/javascript">
    $(function (){
    function log(resp){
    $("#tip").html("<b>" + resp + "</b>");
    } log("loading"); // 去除缓存
    $.ajaxSetup({ cache: false }); function initGet(){
    $.get("getNextTime")
    .success(function(resp){
    log(resp);
    }).error(function(){
    log("ERROR!");
    }).done(initGet);
    } initGet();
    });
    </script>
    </body>
    </html>

    基于jQuery 1.5,事件链,比以往更简洁明了,尤其是在done方法中又一次调用自身,棒极了。
    服务器端很简单,每1秒输出一次当前系统时间:

    /**
    * 简单模拟每秒输出一次当前系统时间,精细到毫秒
    *
    * @author yongboy
    * @date 2011-2-11
    * @version 1.0
    */
    @WebServlet("/getNextTime")
    public class GetNextTimeServlet extends HttpServlet {
    private static final long serialVersionUID = 1L; protected void doGet(HttpServletRequest request,
    HttpServletResponse response) throws ServletException, IOException {
    try {
    Thread.sleep(1000L);
    } catch (InterruptedException e) {
    e.printStackTrace();
    } response.setContentType("text/plain");
    PrintWriter out = response.getWriter(); out.print(DateFormatUtils.format(System.currentTimeMillis(),
    "yyyy-MM-dd HH:mm:ss SSS"));
    out.flush(); out.close();
    }
    }
  2. JAVASCRIPT标签轮询(Script tag long polling)

    引用的JAVASCRIPT脚本文件是可以跨域的,再加上JSONP,更为强大了。
    这里给出一个演示,实现功能类似上面。
    客户端清单:

    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>jQuery 1.5 with JSONP FOR Comet</title>
    </head>
    <body>
    <div id="tip"></div>
    <script type="text/javascript" src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.5.min.js"></script>
    <script type="text/javascript">
    $(function (){
    function log(resp){
    $("#tip").html("<b>" resp "</b>");
    } log("loading"); function jsonp(){
    $.getJSON('http://192.168.0.99:8080/servlet3/getNextTime2?callback=?').success(function(resp){
    log("now time : " resp);
    }).done(jsonp); // 以下方式也是合法的
    /*
    $.getJSON('http://192.168.0.99:8080/servlet3/getNextTime2?callback=?',function(date){
    log(date);
    }).done(jsonp);
    */
    } jsonp();
    });
    </script>
    </body>
    </html>

    服务器端清单:

    /**
    * JSONP形式简单模拟每秒输出一次当前系统时间,精细到毫秒
    * @author yongboy
    * @date 2011-2-11
    * @version 1.0
    */
    @WebServlet("/getNextTime2")
    public class GetNextTimeServlet2 extends HttpServlet {
    private static final long serialVersionUID = 1L; protected void doGet(HttpServletRequest request,
    HttpServletResponse response) throws ServletException, IOException {
    try {
    Thread.sleep(1000L);
    } catch (InterruptedException e) {
    e.printStackTrace();
    } String callback = request.getParameter("callback"); if(StringUtils.isBlank(callback)){
    callback = "showResult";
    } PrintWriter out = response.getWriter(); StringBuilder resultBuilder = new StringBuilder();
    resultBuilder
    .append(callback)
    .append("('")
    .append(DateFormatUtils.format(System.currentTimeMillis(), "yyyy-MM-dd HH:mm:ss SSS"))
    .append("');"); out.print(resultBuilder);
    out.flush(); out.close();
    }
    }

    每次请求时,都会在HTML头部生成一个JS网络地址(实现跨域):

    http://192.168.0.99:8080/servlet3/getNextTime2?callback=jQuery150832738454006945_1297761629067&_=1297761631777

    我们不用指定,jquery会自动生成一个随机的函数名。
    请求上面地址服务器端在有新的数据输出时,生成的回调JS函数:

    jQuery150832738454006945_1297761629067('2011-02-15 17:20:33 921');

    从下面截图可以看到这一点。

    生成相应内容:

    不过,长连接情况下,浏览器认为当前JS文件还没有加载完,会一直显示正在加载中。

上面的JSONP例子太简单,服务器和客户端的连接等待时间不过1秒钟时间左右,若在真实环境下,需要设置一个超时时间。
以往可能会有人告诉你,在客户端需要设置一个超时时间,若指定期限内服务器没有数据返回,则需要通知服务器端程序,断开连接,然后自身再次发起一个新的连接请求。
但在Servlet 3.0 异步连接的规范中,我们就不用那么劳心费神,那般麻烦了。掉换个儿,让服务器端通知客户端超时啦,赶紧重新连接啊。
在前面几篇博客中,演示了一个伪真实场景,博客主添加博客,然后主动以流形式推送给终端用户。这里采用JSONP跨域长轮询连接形式。
客户端:
<html>
<head>
<title>Comet JSONP %u63A8%u9001%u6D4B%u8BD5</title>
<meta http-equiv="X-UA-Compatible" content="IE=8" />
<meta http-equiv="content-type" content="text/html;charset=UTF-8"/>
<meta name="author" content="yongboy@gmail.com"/>
<meta name="keywords" content="servlet3, comet, ajax"/>
<meta name="description" content=""/>
<link type="text/css" rel="stylesheet" href="css/main.css"/>
<script type="text/javascript" src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.5.min.js"></script>
<script type="text/javascript">
String.prototype.template=function(){
var args=arguments;
return this.replace(/\{(\d )\}/g, function(m, i){
return args[i];
});
}
var html = '<div class="logDiv">'
'<div class="contentDiv">{0}</div>'
'<div class="tipDiv">last date : {1}</div>'
'<div class="clear">&nbsp;</div>'
'</div>'; function showContent(json) {
$("#showDiv").prepend(html.template(json.content, json.date));
}
function initJsonp(){
$.getJSON('http://192.168.0.99/servlet3/getjsonpnew?callback=?').success(function(json){
if(json.state == 1){
showContent(json);
}else{
initJsonp();
return;
}
}).done(initJsonp)
.error(function(){
alert("error!");
});
}
$(function (){
initJsonp();
});
</script>
</head>
<body style="margin: 0; overflow: hidden">
<div id="showDiv" class="inputStyle">loading ...</div>
</body>
</html>
服务器端接收长轮询连接请求:
/**
* JSONP获取最新信息
*
* @author yongboy
* @date 2011-2-17
* @version 1.0
*/
@WebServlet(urlPatterns = "/getjsonpnew", asyncSupported = true)
public class GetNewJsonpBlogPosts extends HttpServlet {
private static final long serialVersionUID = 565698895565656L;
private static final Log log = LogFactory.getLog(GetNewJsonpBlogPosts.class); protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
response.setHeader("Cache-Control", "private");
response.setHeader("Pragma", "no-cache");
response.setHeader("Connection", "Keep-Alive");
response.setHeader("Proxy-Connection", "Keep-Alive");
response.setContentType("text/javascript;charset=UTF-8");
response.setCharacterEncoding("UTF-8"); String timeoutStr = request.getParameter("timeout"); long timeout; if (StringUtils.isNumeric(timeoutStr)) {
timeout = Long.parseLong(timeoutStr);
} else {
// 设置1分钟
timeout = 1L * 60L * 1000L;
} log.info("new request ...");
final HttpServletResponse finalResponse = response;
final HttpServletRequest finalRequest = request;
final AsyncContext ac = request.startAsync(request, finalResponse);
// 设置成长久链接
ac.setTimeout(timeout);
ac.addListener(new AsyncListener() {
public void onComplete(AsyncEvent event) throws IOException {
log.info("onComplete Event!");
NewBlogJsonpListener.ASYNC_AJAX_QUEUE.remove(ac);
} public void onTimeout(AsyncEvent event) throws IOException {
// 尝试向客户端发送超时方法调用,客户端会再次请求/blogpush,周而复始
log.info("onTimeout Event!"); // 通知客户端再次进行重连
final PrintWriter writer = finalResponse.getWriter(); String outString = finalRequest.getParameter("callback")
+ "({state:0,error:'timeout is now'});";
writer.println(outString);
writer.flush();
writer.close(); NewBlogJsonpListener.ASYNC_AJAX_QUEUE.remove(ac);
} public void onError(AsyncEvent event) throws IOException {
log.info("onError Event!");
NewBlogJsonpListener.ASYNC_AJAX_QUEUE.remove(ac);
} public void onStartAsync(AsyncEvent event) throws IOException {
log.info("onStartAsync Event!");
}
}); NewBlogJsonpListener.ASYNC_AJAX_QUEUE.add(ac);
}
}
很显然,在有博客数据到达时,会通知到已注册的客户端。
注意,推送数据之后,需要调用当前的异步上下文complete()函数:
/**
* 监听器单独线程推送到客户端
*
* @author yongboy
* @date 2011-2-17
* @version 1.0
*/
@WebListener
public class NewBlogJsonpListener implements ServletContextListener {
private static final Log log = LogFactory.getLog(NewBlogListener.class);
public static final BlockingQueue BLOG_QUEUE = new LinkedBlockingQueue();
public static final Queue ASYNC_AJAX_QUEUE = new ConcurrentLinkedQueue(); public void contextDestroyed(ServletContextEvent arg0) {
log.info("context is destroyed!");
} public void contextInitialized(ServletContextEvent servletContextEvent) {
log.info("context is initialized!");
// 启动一个线程处理线程队列
new Thread(runnable).start();
} private Runnable runnable = new Runnable() {
public void run() {
boolean isDone = true; while (isDone) {
if (!BLOG_QUEUE.isEmpty()) {
try {
log.info("ASYNC_AJAX_QUEUE size : "
+ ASYNC_AJAX_QUEUE.size());
MicBlog blog = BLOG_QUEUE.take(); if (ASYNC_AJAX_QUEUE.isEmpty()) {
continue;
} String targetJSON = buildJsonString(blog); for (AsyncContext context : ASYNC_AJAX_QUEUE) {
if (context == null) {
log.info("the current ASYNC_AJAX_QUEUE is null now !");
continue;
}
log.info(context.toString());
PrintWriter out = context.getResponse().getWriter(); if (out == null) {
log.info("the current ASYNC_AJAX_QUEUE's PrintWriter is null !");
continue;
} out.println(context.getRequest().getParameter(
"callback")
+ "(" + targetJSON + ");");
out.flush(); // 通知,执行完成函数
context.complete();
}
} catch (Exception e) {
e.printStackTrace();
isDone = false;
}
}
}
}
}; private static String buildJsonString(MicBlog blog) {
Map<string, object=""> info = new HashMap<string, object="">();
info.put("state", 1);
info.put("content", blog.getContent());
info.put("date",
DateFormatUtils.format(blog.getPubDate(), "HH:mm:ss SSS")); JSONObject jsonObject = JSONObject.fromObject(info); return jsonObject.toString();
}
}
长连接的超时时间,有人贴切的形容为“心跳频率”,联动着两端,需要根据环境设定具体值。
JSONP方式无论是轮询还是长轮询,都是可以很容易把目标程序融合到第三方网站,当前的个人主页、博客等很多小挂件大多也是采用这种形式进行跨域获取数据的。

java ajax长连接请求服务器数据的更多相关文章

  1. Ajax长连接和SignalR(刷新客户端数据)的区别

    ajax实现长连接   <%@ page language="java" import="java.util.*" pageEncoding=" ...

  2. 本地主机作服务器解决AJAX跨域请求访问数据的方法

    近几天学到ajax,想测试一下ajax样例,由于之前在阿里租用的服务器过期了,于是想着让本地主机既做服务器又做客户端,只是简单地测试,应该还行. 于是,下载了xampp,下载网址http://www. ...

  3. Ajax在jQuery中的应用(加载异步数据、请求服务器数据)

    加载异步数据 jQuery中的load()方法 load(url,[data],[callback]) url:被加载的页面地址 [data]:可选项表示发送到服务器的数据,其格式为 key/valu ...

  4. Comet:基于 HTTP 长连接的“服务器推”技术解析

    原文链接:http://www.cnblogs.com/deepleo/p/Comet.html 一.背景介绍 传统web请求,是显式的向服务器发送http Request,拿到Response后显示 ...

  5. Comet:基于 HTTP 长连接的“服务器推”技术

    “服务器推”技术的应用 请访问 Ajax 技术资源中心,这是有关 Ajax 编程模型信息的一站式中心,包括很多文档.教程.论坛.blog.wiki 和新闻.任何 Ajax 的新信息都能在这里找到. c ...

  6. 转载:Comet:基于 HTTP 长连接的“服务器推”技术

    转自:http://www.ibm.com/developerworks/cn/web/wa-lo-comet/ 很多应用譬如监控.即时通信.即时报价系统都需要将后台发生的变化实时传送到客户端而无须客 ...

  7. [转载] Comet:基于 HTTP 长连接的“服务器推”技术

    转载自http://www.ibm.com/developerworks/cn/web/wa-lo-comet/ “服务器推”技术的应用 传统模式的 Web 系统以客户端发出请求.服务器端响应的方式工 ...

  8. Comet:基于 HTTP 长连接的“服务器推”技术(转载)

    “服务器推”技术的应用 传统模式的 Web 系统以客户端发出请求.服务器端响应的方式工作.这种方式并不能满足很多现实应用的需求,譬如: 监控系统:后台硬件热插拔.LED.温度.电压发生变化: 即时通信 ...

  9. 【转】Comet:基于 HTTP 长连接的“服务器推”技术

    原文链接:http://www.ibm.com/developerworks/cn/web/wa-lo-comet/ 很多应用譬如监控.即时通信.即时报价系统都需要将后台发生的变化实时传送到客户端而无 ...

随机推荐

  1. mui与springMVC前后端分离

    这个小dome简单来说的前后端分离,通过跨域调用接口来显示数据. 前端用到mui框架,主要来显示数据. 后端用到Java的springMVC,restful服务来做增删改查管理, 这里主要实现动态显示 ...

  2. (转)OC学习笔记 @property的属性 strong 和 weak 理解

    在ObjectiveC里,用@property访问所有的实例变量.@property有一对属性:strong 和 weak.官方文档里的解释晦涩难懂:Stack Overflow里的用户RDC (ht ...

  3. 【开篇】基于C#+EmguCV的机器视觉平台开发

    市面上关于通用的机器视觉平台已有不少,一些大的视觉产品.设备制造商都有开发自己的一套系统.其通用性也都有一些行业局限,难以囊括所有可能性,一些需要经过二次开发,这也是难以攻克的问题.本人水平有限,再加 ...

  4. 在桌面上显示IE图标(非快捷键)

    1.在桌面点击右键选择"属性"打开"显示属性",选择"桌面">"自定义桌面">"常规"& ...

  5. Spring随笔-bean装配-自动装配

    Spring提供了三种装配方式 1.XML文件进行显式装配 2.java中进行显示装配 3.自动化装配 1.自动化装配的两种实现方式 1.组件扫描:Spring会自动发现应用上下文中创建的bean 2 ...

  6. SQLServer 安装失败可能的原因

    问题:安装的时候显示参数指定的目录无效 解决:你的安装盘使用了文件/文件夹压缩功能,去掉压缩属性即可! 建议不要轻易使用储存盘的压缩功能

  7. linux下链接时缺少动态链接库

    1, 用ln将需要的so文件链接到/usr/lib或者/lib这两个默认的目录下边 ln -s /where/you/install/lib/*.so /usr/libsudo ldconfig 2, ...

  8. Python高级核心技术97讲✍✍✍

    Python高级核心技术97讲  整个课程都看完了,这个课程的分享可以往下看,下面有链接,之前做java开发也做了一些年头,也分享下自己看这个视频的感受,单论单个知识点课程本身没问题,大家看的时候可以 ...

  9. JS事件 鼠标移开事件(onmouseout)鼠标移开事件,当鼠标移开当前对象时,执行onmouseout调用的程序。

    鼠标移开事件(onmouseout) 鼠标移开事件,当鼠标移开当前对象时,执行onmouseout调用的程序. 当把鼠标移动到"登录"按钮上,然后再移开时,触发onmouseout ...

  10. 双十一HostGator独立服务器方案

    一年一度的“双十一”购物狂欢节到来,各大电商平台线上消费的各种“吸金”开启了“双十一”模式,一年一度的“双十一”网购狂欢又开始以“巨大的价格优势”来勾起消费者的购买欲望. 此次双十一期间,HostGa ...