I remember that I wrote an article about redis actively pushing data to the page before, but it is still difficult to apply the method described in the article to a J2EE project (nodejs is also needed). Therefore, this article explores the active push technology that is more suitable for web projects.
Comet is a push technology for the web, which enables the server to transmit updated information to the client in real time without the client sending a request. There are currently two implementations: long-polling and iframe. Streaming. The following is the implementation of iframe stream to achieve the effect that the server actively pushes to the client (here, the client refers to the jsp page), and combines the publish and subscribe of redis, which is a typical example.
Client (page):
<script type="text/javascript"> $(function() { setCometUrl(); bindLenstener(); }); 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"; } // server push code 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>
This client page uses Comet supported by the browser. It only initiates an ajax request. After the background is opened, the background will actively send data to this page continuously.
The background is more complicated, and it also combines the publish and subscribe of redis. The data source is obtained by subscribing to a channel of redis.
Action:
@Controller public class PubSubAction { LinkedList<String> queue = new LinkedList<String>(); PrintWriter out; //thread MsgSubHandler subT = null; CheckQueueHandler checkT = null; @RequestMapping("/pubsub/push.json") @ResponseBody public void pushMsg(HttpServletResponse response) { System.out.println("Enter several times here..."); //subscription subT = new MsgSubHandler("pubsub_channel", queue); subT.start(); //an examination checkT = new CheckQueueHandler(queue); checkT.start(); //Create Comet Iframe sendHtmlScript(response, "<script type=\"text/javascript\"> var comet = window.parent.comet;</script>"); while (true) { try { Thread.sleep(1000);//Get the number from the queue every 1s if(queue.size() > 0) { String msg = queue.pop(); System.out.println("Information from the queue:" + 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("Start closing operation.."); //close the stream out.flush(); out.close(); // queue is empty queue.clear(); //Close the message subT.shut(); checkT.shut(); // thread stop 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); } } }
Among them, the thread class that subscribes to the message and the thread class that checks the size of the message queue are as follows:
1: Thread class that regularly checks the size of the queue, the purpose is to avoid the message queue size being too large
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);//Check the size of the specified queue every 1 minute if (queue.size() >= 500) { queue.clear(); } } } catch (InterruptedException e) { e.printStackTrace (); } } public void shut() { runFlag = false; } }
2: Thread class that subscribes to the corresponding 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 resource initialization pool = SysBeans.getBean("jedisPool"); jedis = pool.getResource(); //publish/subscribe listener initialization listener = new PubSubListener(queue); } @Override public void run() { //Subscribe to the specified channel information jedis.subscribe(listener, channel); } public void shut() { //Return the redis resource if(pool !=null && jedis != null) { pool.returnResource(jedis); } //Cancel channel subscription listener.unsubscribe(); } }
3: Redis publish/subscribe listener class
public class PubSubListener extends JedisPubSub { private LinkedList<String> queue =null; public PubSubListener(LinkedList<String> queue) { this.queue = queue; } / / Get the message processing after subscription @Override public void onMessage(String channel, String message) { //System.out.print("onMessage: Get the message processing after subscription"); queue.add(message); } //Processing after getting the message subscribed by expression @Override public void onPMessage(String pattern, String channel, String message) { System.out.print("onPMessage: Processing after getting the message subscribed by expression"); System.out.println(pattern + "=" + channel + "=" + message); } //Initialize the processing when subscribing by expression @Override public void onPSubscribe(String pattern, int subscribedChannels) { System.out.print("onPSubscribe: Initialize the processing of subscription by expression"); System.out.println(pattern + "=" + subscribedChannels); } //Cancel the processing of subscription by expression @Override public void onPUnsubscribe(String pattern, int subscribedChannels) { System.out.print("onPUnsubscribe: Cancel the processing of subscription by expression"); System.out.println(pattern + "=" + subscribedChannels); } //Processing when initializing subscription @Override public void onSubscribe(String channel, int subscribedChannels) { System.out.print("onSubscribe: Processing when initializing subscription"); System.out.println(channel + "=" + subscribedChannels); } //Processing when unsubscribing @Override public void onUnsubscribe(String channel, int subscribedChannels) { System.out.print("onUnsubscribe: Processing when unsubscribe"); System.out.println(channel + "=" + subscribedChannels); } }
Start the project, open the client page, the initial div:
At the same time the console prints:
Been here a few times.....
onSubscribe: Processing when initializing subscription pubsub_channel=1
This shows that as soon as the client is opened, the purpose of subscribing to the corresponding channel is achieved.
Next, in order to have data in this div, we start to publish some data on this channel, simulating:
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 (); } } }
Then you observe this div again, and you will find the following phenomenon (at a certain moment):
This shows that: we have achieved what we want in the title! ——Combined with redis publish/subscribe and the client only requests the server once, the server actively pushes data to the client.
Finally, when we try to close the client page again, we will find that the console prints:
onUnsubscribe: Processing when unsubscribing pubsub_channel=0
Note that as soon as the client closes, the subscription to the channel is cancelled. And the queue queue will also be emptied.
In fact, Comet is not an emerging technology. Regarding the [anti-ajax] technology, the latest one is WebSocket, and I will have the opportunity to study it later.