记得我之前写过 redis主动向页面push数据 的文章,但文中所描述的方法要应用到J2EE的项目中还是比较困难的(还需用到nodejs什么的)。于是本文来探究下比较适合web项目的主动推技术。
Comet是一种用于web的推送技术,能使服务器能实时地将更新的信息传送到客户端,而无须客户端发出请求,目前有两种实现方式:长轮询(long-polling)和iframe流(streaming)。下面就用iframe流的实现方式来实现服务端主动向客户端(这里客户端指的是jsp页面)推送的效果,并且结合了redis的发布订阅,算是比较典型的例子了。
客户端(页面):
<script type="text/javascript"> $(function() { setCometUrl(); bindLinstener(); }); function bindLinstener() { if (window.addEventListener) { window.addEventListener("load", comet.initialize, false); window.addEventListener("unload", comet.onUnload, false); } else if (window.attachEvent) { window.attachEvent("onload", comet.initialize); window.attachEvent("onunload", comet.onUnload); } } function setCometUrl(){ comet.cometUrl = "pubsub/push.json"; } //服务器推送代码 var comet = { connection : false, iframediv : false, initialize : function() { if (navigator.appVersion.indexOf("MSIE") != -1) { // For IE browsers comet.connection = new ActiveXObject("htmlfile"); comet.connection.open(); comet.connection.write("<html>"); comet.connection.write("<script>document.domain = '" + document.domain + "'"); comet.connection.write("</html>"); comet.connection.close(); comet.iframediv = comet.connection.createElement("div"); comet.connection.appendChild(comet.iframediv); comet.connection.parentWindow.comet = comet; comet.iframediv.innerHTML = "<iframe id='comet_iframe' src='"+comet.cometUrl+"'></iframe>"; } else if (navigator.appVersion.indexOf("KHTML") != -1) { // for KHTML browsers comet.connection = document.createElement('iframe'); comet.connection.setAttribute('id', 'comet_iframe'); comet.connection.setAttribute('src', comet.cometUrl); with (comet.connection.style) { position = "absolute"; left = top = "-100px"; height = width = "1px"; visibility = "hidden"; } document.body.appendChild(comet.connection); } else { // For other browser (Firefox...) comet.connection = document.createElement('iframe'); comet.connection.setAttribute('id', 'comet_iframe'); comet.iframediv = document.createElement('iframe'); comet.iframediv.setAttribute('src', comet.cometUrl); comet.connection.appendChild(comet.iframediv); document.body.appendChild(comet.connection); } }, onUnload : function() { if (comet.connection) { comet.connection = false; // release the iframe to prevent problems with IE when reloading the page closePage(); } }, receiveMsg : function(msg) { $("#content").append(msg + "<br/>"); } } function closePage() { $.ajax({ async : true, cache : false, type : "POST", //data:{objId:objId}, dataType:"json", url :"pubsub/close.json", success : function(data) { }, error: function(){ } }); } </script> </head> <body > <div id="content" class="show"></div> </body>
这个客户端页面是利用浏览器支持的Comet,仅发起一次ajax请求,打通后台后,后台就会源源不断主动往这个页面发送数据。
后台较为复杂,并且还结合了redis的发布订阅。数据来源则是订阅redis的一个channel而得到。
Action:
@Controller public class PubSubAction { LinkedList<String> queue = new LinkedList<String>(); PrintWriter out; //线程 MsgSubHandler subT = null; CheckQueueHandler checkT = null; @RequestMapping("/pubsub/push.json") @ResponseBody public void pushMsg(HttpServletResponse response) { System.out.println("这儿进几次........."); //订阅 subT = new MsgSubHandler("pubsub_channel", queue); subT.start(); //检查 checkT = new CheckQueueHandler(queue); checkT.start(); //创建Comet Iframe sendHtmlScript(response, "<script type=\"text/javascript\"> var comet = window.parent.comet;</script>"); while (true) { try { Thread.sleep(1000);//每隔1s从队列取数 if(queue.size() > 0) { String msg = queue.pop(); System.out.println("从队列里取到的信息:" + msg); sendHtmlScript(response, "<script type=\"text/javascript\"> comet.receiveMsg('"+msg+"');</script>"); } }catch(InterruptedException e) { e.printStackTrace(); } } } @RequestMapping("/pubsub/close.json") @ResponseBody public void shutdownServer() throws InterruptedException { System.out.println("开始关闭操作.."); //关闭流 out.flush(); out.close(); //队列情空 queue.clear(); //消息的关闭处理 subT.shut(); checkT.shut(); //线程停止 if(checkT.isAlive()) { checkT.interrupt(); checkT.join(); } if(subT.isAlive()) { subT.interrupt(); subT.join(); } } private void sendHtmlScript(HttpServletResponse response,String script){ response.setCharacterEncoding("UTF-8"); response.setContentType("text/html"); response.setDateHeader("Expires", 0); response.setHeader("Pragma", "No-cache"); response.setHeader("Cache-Control", "no-cache,no-store,max-age=0"); try { out = response.getWriter(); out.write(script); out.flush(); } catch (IOException e) { e.printStackTrace(); log.error(e.getMessage(), e); } } }
其中,订阅消息的线程类和检查消息队列大小的线程类分别如下:
1:定时检查队列大小的线程类,目的是避免消息队列大小过大
public class CheckQueueHandler extends Thread { private LinkedList<String> queue; private boolean runFlag = true; public CheckQueueHandler(LinkedList<String> queue) { this.queue = queue; } @Override public void run() { try { while (runFlag && queue.size()>0) { Thread.sleep(60 * 1000);//每隔1分钟检查指定队列的大小 if (queue.size() >= 500) { queue.clear(); } } } catch (InterruptedException e) { e.printStackTrace(); } } public void shut() { runFlag = false; } }
2:订阅相应的channel的线程类:
public class MsgSubHandler extends Thread{ private LinkedList<String> queue; private String channel; JedisPool pool; Jedis jedis; PubSubListener listener; public MsgSubHandler(String channel, LinkedList<String> queue) { this.channel = channel; this.queue = queue; //redis资源初始化 pool = SysBeans.getBean("jedisPool"); jedis = pool.getResource(); //发布/订阅监听初始化 listener = new PubSubListener(queue); } @Override public void run() { //订阅指定的渠道信息 jedis.subscribe(listener, channel); } public void shut() { //归还redis资源 if(pool !=null && jedis != null) { pool.returnResource(jedis); } //取消渠道订阅 listener.unsubscribe(); } }
3:redis的发布/订阅监听类
public class PubSubListener extends JedisPubSub { private LinkedList<String> queue =null; public PubSubListener(LinkedList<String> queue) { this.queue = queue; } //取得订阅后消息的处理 @Override public void onMessage(String channel, String message) { //System.out.print("onMessage:取得订阅后消息的处理 "); queue.add(message); } //取得按表达式的方式订阅的消息后的处理 @Override public void onPMessage(String pattern, String channel, String message) { System.out.print("onPMessage:取得按表达式的方式订阅的消息后的处理 "); System.out.println(pattern + "=" + channel + "=" + message); } //初始化按表达式的方式订阅时候的处理 @Override public void onPSubscribe(String pattern, int subscribedChannels) { System.out.print("onPSubscribe:初始化按表达式的方式订阅时候的处理 "); System.out.println(pattern + "=" + subscribedChannels); } //取消化按表达式的方式订阅时候的处理 @Override public void onPUnsubscribe(String pattern, int subscribedChannels) { System.out.print("onPUnsubscribe:取消化按表达式的方式订阅时候的处理 "); System.out.println(pattern + "=" + subscribedChannels); } //初始化订阅时候的处理 @Override public void onSubscribe(String channel, int subscribedChannels) { System.out.print("onSubscribe:初始化订阅时候的处理 "); System.out.println(channel + "=" + subscribedChannels); } //取消订阅时候的处理 @Override public void onUnsubscribe(String channel, int subscribedChannels) { System.out.print("onUnsubscribe:取消订阅时候的处理 "); System.out.println(channel + "=" + subscribedChannels); } }
启动工程,打开客户端页面,最初始的div:
同时控制台打印:
这儿进几次.........
onSubscribe:初始化订阅时候的处理 pubsub_channel=1
这说明:一打开客户端,就实现了订阅对应channel的目的。
接下来,为了让这个div中有数据,我们开始来对这个channel进行publish一些数据,模拟:
public static void main(String[] args) { Jedis jedis = new Jedis("localhost"); while(true) { try { Thread.sleep(2000); jedis.publish("pubsub_channel", "I like " + Math.random()*100 ); } catch (InterruptedException e) { e.printStackTrace(); } } }
然后你再观察这个div,会发现如下现象(某一时刻):
由此说明:我们达到了如题所想要的目的!——结合了redis的发布/订阅 并且客户端只请求服务端一次,服务端主动向客户端推送了数据。
最后,我们再试着关闭客户端页面,会发现控制台打印:
onUnsubscribe:取消订阅时候的处理 pubsub_channel=0
说明,客户端一关闭,就取消了对channel的订阅了。并且queue队列也会被清空。
其实Comet并不是新兴的技术,关于【反ajax】技术,最新的有WebSocket,以后有机会再研究。
相关推荐
通过tomcat服务器,向网页发送即时消息。comet的小例子,可以直接运行,用tomcat6.0。
服务端向浏览器实时推送消息,支持在线指定用户推送。内部带详细讲解,和举例,和代码嵌入步骤。
基于服务器推送框架 Comet4J ,后台模拟实时生成 gps 坐标信息然后再推送到前端页面显示。...这是客户端主动向服务器发起请求的方式,而采用 comet4j框架来实现正好相反,是服务器主动向客户端来推送消息。
它分为服务端与客户端两部分,你只要将服务器端(JAR文件,目前仅支持Tomcat6、7)放入WEB-INF\lib,客户端(JavaScript文件)引入到页面,那么你的应用就具备了向客户端推送信息的能力,而你仅需要在服务器端调用Comet4...
NULL 博文链接:https://zzc1684.iteye.com/blog/2187874
javaweb消息推送 基于comet实现局域网内部通讯(聊天室)demo 功能特性 推送消息广播。 推送定向消息。 提供连接上线前、上线、下线前、下线、发送消息等多种可处理事件。 消息缓存机制,确保长轮询工作模式下不丢失...
comet4j实现服务器端主动向客户端推送消息。基于java实现,封装了注册、推送,只需关注业务的实现,导入eclipse直接运行。好资源,值得拥有
comet4j 自己写的消息推送 觉得实用
Java 实现 Comet 长连接,服务器主动发送消息给客户端
这个项目是基于tomcat,comet4j的技术来实现推送消息到web页面,可以直接导入,直接运行的,里面包含了项目所需要的comet4j-tomcat7.jar(tomcat7的就导入这个),comet4j.js(页面引入这个js),等jar包,对于想要学习...
这是一个利用comet4j第三方插件,实现javaweb项目中后台消息向前台推送的一个功能,前台只要利用jquery,后台是用java实现,tomcat用7的版本
WebForm实现消息推送源码 功能介绍: 假设A,B,C用户登陆,内存记录下已登录的用户的信息,这时A在所在的客户端(SendInfo.aspx)页面向B发消息,则在B所在客户端页面(SendInfo.aspx)将弹出消息框。 关键点有两个: 1...
demo是采用comet的web推送技术,使用tomcat7做服务器,内含tomcat7上面配置说明,在MyEclipse、tomcat7上面完美运行,本人亲测!
Servlet3.0 异步处理 页面推送 Comet 实例
JavaScript数据推送Comet技术详解_.docx
先进入login.jsp通过名字登陆,然后可以发邮件一个框输内容一个框输想收件人id
Comet服务器推送技术
场景:后端更新数据推送到客户端(Java部分使用Tomcat服务器)。 后端推送数据的解决方案有很多,比如轮询、Comet、WebSocket。 1. 轮询对于后端来说开发成本最低,就是按照传统的方式处理Ajax请求并返回数据,在...
实现Comet消息推送功能,根据登陆人定向推送,解决刷新页面原有ScriptSession不能及时销毁的问题,DEMO比较简陋,请先进入login.jsp页面登陆。根据登陆名称判断推送目标,可登陆多个用户进行测试。
Java comet服务器推送(聊天)实现代码。