Use Comet technology to actively push data from the server to the client (combined with redis publish/subscribe)

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.

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=326782988&siteId=291194637