【背景】
今天,一個前端的師弟問我怎樣做實時聊天窗口,我毫不猶豫地說:在前台定時訪問服務端呀!師弟默默地百度了一番,最后告訴我,有一種技術是后服務端動推送信息給客戶端的,這種技術的名字叫comet,我驚呆了,因為完全沒聽過,趕緊上網搜集資料,耗了一個晚上寫了個簡單的例子,實現主動向客戶端發送信息。說是說主動,其實還是要客戶端先獻出它的“第一次”,即只要它有先請求你一下,以后你們熟了,你想主動約它就約它!
關於comet技術介紹及其實現原理,可以參考網站 http://www.ibm.com/developerworks/cn/web/wa-lo-comet/ 的介紹。
簡單來說,就是客戶端發送請求到服務端,服務器端會阻塞請求直到有數據傳遞或超時才返回,之后客戶端 JavaScript 響應處理函數會在處理完服務器返回的信息后,再次發出請求,重新建立連接。當客戶端處理接收的數據、重新建立連接時,服務器端可能有新的數據到達;這些信息會被服務器端保存直到客戶端重新建立連接,客戶端會一次把當前服務器端所有的信息取回。
【工作環境】
1、myeclipse2013
2、tomcat 6.0
3、jdk 7
4、火狐瀏覽器
說明:
測試成功的瀏覽器有:(1)火狐瀏覽器 (2)IE10、IE9、IE8 (3)360極速瀏覽器極速模式
測試失敗的瀏覽器有:(1)IE10兼容模式、IE7
【准備工作】
1、下載comet4j.js :http://files.cnblogs.com/xiaoMzjm/comet4j.js.rar
2、下載comet4j-tomcat6.jar :http://files.cnblogs.com/xiaoMzjm/comet4j-tomcat6.jar.rar
3、到tomcat目錄下——conf——server.xml 下,把
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />
修改成:
<Connector URIEncoding="UTF-8" connectionTimeout="20000" port="8080" protocol="org.apache.coyote.http11.Http11NioProtocol" redirectPort="8443" />
說明:
其實那個js文件和jar官網是https://code.google.com/p/comet4j/ 的,但它是谷歌,這里是天朝吶,所以貼了兩個我文件夾里面的包的地址上來。
comet4j-tomcat6.jar 還有另一個版本是 comet4j-tomcat7.jar , 自己選擇合適的版本去下載。6以下的tomcat肯定不行就對了。
comet4j.js 的官方使用文檔: http://doc.comet4j.tk/jsdocs/
comet4j-tomcat6.jar 的官方使用文檔: http://doc.comet4j.tk/apidocs/
【新建項目過程】
(1)新建服務端的類TestComet , 實現 ServletContextListener 接口
(2)在web.xml 里面應該配置 攔截器:
<listener> <listener-class>org.comet4j.core.CometAppListener</listener-class> </listener> <listener> <description>HelloWorld</description> <listener-class>com.zjm.www.test.TestComet</listener-class> </listener> <servlet> <display-name>CometServlet</display-name> <servlet-name>CometServlet</servlet-name> <servlet-class>org.comet4j.core.CometServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>CometServlet</servlet-name> <url-pattern>/conn</url-pattern> </servlet-mapping>
注:其中的要配置的有兩個地方
一個是comet4j-tomcat6.jar下的一個servlet:org.comet4j.core.CometServlet , 客戶端訪問的入口
另一個是comet4j-tomcat6.jar下的監聽器:org.comet4j.core.CometAppListener , 監聽我們自己的類。
【具體代碼(說明都寫在注釋里面)】
1、web.xml

1 <?xml version="1.0" encoding="UTF-8"?> 2 <web-app version="2.5" 3 xmlns="http://java.sun.com/xml/ns/javaee" 4 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 5 xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 6 http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> 7 <listener> 8 <listener-class>org.comet4j.core.CometAppListener</listener-class> 9 </listener> 10 <listener> 11 <description>HelloWorld</description> 12 <listener-class>com.zjm.www.test.TestComet</listener-class> 13 </listener> 14 <servlet> 15 <display-name>CometServlet</display-name> 16 <servlet-name>CometServlet</servlet-name> 17 <servlet-class>org.comet4j.core.CometServlet</servlet-class> 18 </servlet> 19 <servlet-mapping> 20 <servlet-name>CometServlet</servlet-name> 21 <url-pattern>/conn</url-pattern> 22 </servlet-mapping> 23 24 25 <welcome-file-list> 26 <welcome-file>index.jsp</welcome-file> 27 </welcome-file-list> 28 </web-app>
2、java類TestComet
里面附上了不少的注釋,如果想仔細研究建議看上面的賦予的API文檔鏈接。

1 package com.zjm.www.test; 2 3 import javax.servlet.ServletContextEvent; 4 import javax.servlet.ServletContextListener; 5 6 import org.comet4j.core.CometContext; 7 import org.comet4j.core.CometEngine; 8 9 /** 10 * 描述:服務端主動推送消息到客戶端 簡單例子 11 * @author zjm 12 * @time 2014/8/7 13 */ 14 public class TestComet implements ServletContextListener { 15 16 // 頻道1 17 private static final String CHANNEL1 = "result1"; 18 // 頻道2 19 private static final String CHANNEL2 = "result2"; 20 21 // 通過頻道1推送給前台的變量1 22 private static int number1 = 0 ; 23 // 通過頻道2推送給前台的變量2 24 private static int number2 = 100 ; 25 26 /** 27 * 初始化上下文 28 */ 29 public void contextInitialized(ServletContextEvent arg0) { 30 31 // CometContext : Comet4J上下文,負責初始化配置、引擎對象、連接器對象、消息緩存等。 32 CometContext cc = CometContext.getInstance(); 33 // 注冊頻道,即標識哪些字段可用當成頻道,用來作為向前台傳送數據的“通道” 34 cc.registChannel(CHANNEL1); 35 cc.registChannel(CHANNEL2); 36 37 Thread myThread = new Thread(new SendToClientThread(), "SendToClientThread"); 38 // 下面的內部類的方法是個死循環,設置helloAppModule線程為“守護線程”,則當jvm只剩“守護線程”時(主線程結束),該線程也會結束。 39 myThread.setDaemon(true); 40 // 開始線程 41 myThread.start(); 42 } 43 44 /** 45 * 內部類線程類 46 */ 47 class SendToClientThread implements Runnable { 48 public void run() { 49 while (true) { 50 try { 51 Thread.sleep(1000); 52 } catch (Exception ex) { 53 ex.printStackTrace(); 54 } 55 // CometEngine : 引擎,負責管理和維持連接,並能夠必要的發送服務 56 CometEngine engine = CometContext.getInstance().getEngine(); 57 // 參數的意思:通過什么頻道(CHANNEL1)發送什么數據(number1++),前台可用可用頻道的值(result1)來獲取某頻道發送的數據 58 engine.sendToAll(CHANNEL1, number1++); 59 engine.sendToAll(CHANNEL2, number2++); 60 } 61 } 62 } 63 64 public void contextDestroyed(ServletContextEvent arg0) { 65 } 66 }
3、客戶端代碼

1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 2 <html xmlns="http://www.w3.org/1999/xhtml"> 3 <head> 4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 5 <title>Comet4J Hello World</title> 6 <script type="text/javascript" src="js/comet4j.js"></script> 7 <script type="text/javascript"> 8 function init(){ 9 10 var number1 = document.getElementById('number1'); 11 var number2 = document.getElementById('number2'); 12 // 建立連接,conn 即web.xml中 CometServlet的<url-pattern> 13 JS.Engine.start('conn'); 14 // 監聽后台某個頻道 15 JS.Engine.on( 16 { 17 // 對應服務端 “頻道1” 的值 result1 18 result1 : function(num1){ 19 number1.innerHTML = num1; 20 }, 21 // 對應服務端 “頻道2” 的值 result2 22 result2 : function(num2){ 23 number2.innerHTML = num2; 24 }, 25 } 26 ); 27 } 28 </script> 29 </head> 30 <body onload="init()"> 31 數字1:<span id="number1">...</span><br></br> 32 數字2:<span id="number2">...</span> 33 </body> 34 </html>
4、網頁顯示
數字1:2221
數字2:2321
可以看出,兩個數字不停地每秒遞增。數字2比數字1 多了100,因為在服務端,number2的初始值為100,number1的初始值為0。
在瀏覽器上按F12,選擇netWork,如下圖,可以看出,此連接從未斷開過。
有什么寫錯或寫得不好的地方,歡迎大家提出來~