場景1:當(dāng)有新郵件的時候,網(wǎng)頁自動彈出提示信息而無需用戶手動的刷新收件箱。
場景2:當(dāng)用戶的手機掃描完成頁面中的二維碼以后,頁面會自動跳轉(zhuǎn)。
場景3:在類似聊天室的環(huán)境中有任何人發(fā)言,所有登錄用戶都可以即時看見信息。
與傳統(tǒng)的MVC模型請求必須從客戶端發(fā)起由服務(wù)器響應(yīng)相比,使用反向Ajax能夠模擬服務(wù)器端主動向客戶端推送事件從而提高用戶體驗。本文將分兩個部分討論反向Ajax技術(shù),包括:Comet和WebSocket。文章旨在演示如何實現(xiàn)以上兩種技術(shù)手段,Struts2或SpringMVC中的應(yīng)用并未涉及。此外,Servlet的配置也采用注解的方式,相關(guān)知識大家可以參考其它資料。
一、Comet(最佳的兼容手段)
Comet本質(zhì)上則是這樣的一種概念:能夠從服務(wù)器端向客戶端發(fā)送數(shù)據(jù)。在一個標(biāo)準(zhǔn)的 HTTP Ajax 請求中,數(shù)據(jù)是發(fā)送給服務(wù)器端的,反向 Ajax 以某些特定的方式來模擬發(fā)出一個 Ajax 請求,這樣的話,服務(wù)器就可以盡可能快地向客戶端發(fā)送事件。由于普通HTTP請求往往會伴隨頁面的跳轉(zhuǎn),而推送事件則需要瀏覽器停留在同一個頁面或者框架下,因此Comet的實現(xiàn)只能夠通過Ajax來完成。
它的實現(xiàn)過程如下:頁面加載的時候隨即向服務(wù)器發(fā)送一條Ajax請求,服務(wù)器端獲取請求并將它保存在一個線程安全的容器中(通常為隊列)。同時服務(wù)器端仍然可以正常響應(yīng)其他請求。當(dāng)需要推送的事件到來的時候,服務(wù)器遍歷容器中的請求在返回應(yīng)答后刪除。于是所有停留在頁面中的瀏覽器都會獲得該應(yīng)答,并再次發(fā)送Ajax請求,重復(fù)上述過程。
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <% String path = request.getContextPath(); String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path + "/"; %> <!DOCTYPE html> <html lang="en"> <base href="<%=basePath%>"> <head> <title>WebSocket</title> <script type="text/javascript" src="static/jquery-1.9.1.min.js"></script> <script type="text/javascript"> $(function() { connect(); $("#btn").click(function() { var value = $("#message").val(); $.ajax({ url : "longpolling?method=onMessage&msg=" + value, cache : false, dataType : "text", success : function(data) { } }); }); }); function connect() { $.ajax({ url : "longpolling?method=onOpen", cache : false, dataType : "text", success : function(data) { connect(); alert(data); } }); } </script> </head> <body> <h1>LongPolling</h1> <input type="text" id="message" /> <input type="button" id="btn" value="發(fā)送" /> </body> </html>
我們注意到,由btn發(fā)送的請求其實并不需要獲取應(yīng)答。整個過程的關(guān)鍵是需要客戶端始終讓服務(wù)器保持connect()的請求。而服務(wù)器端首先需要支持這種異步的響應(yīng)方式,幸運的是目前為止絕大部分的Servlet容器都已經(jīng)提供了良好的支持。下面以Tomcat為例:
package servlet; import java.io.IOException; import java.io.PrintWriter; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import javax.servlet.AsyncContext; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @WebServlet(value="/longpolling", asyncSupported=true) public class Comet extends HttpServlet { private static final Queue<AsyncContext> CONNECTIONS = new ConcurrentLinkedQueue<AsyncContext>(); @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String method = req.getParameter("method"); if (method.equals("onOpen")) { onOpen(req, resp); } else if (method.equals("onMessage")) { onMessage(req, resp); } } private void onOpen(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { AsyncContext context = req.startAsync(); context.setTimeout(0); CONNECTIONS.offer(context); } private void onMessage(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String msg = req.getParameter("msg"); broadcast(msg); } private synchronized void broadcast(String msg) { for (AsyncContext context : CONNECTIONS) { HttpServletResponse response = (HttpServletResponse) context.getResponse(); try { PrintWriter out = response.getWriter(); out.print(msg); out.flush(); out.close(); context.complete(); CONNECTIONS.remove(context); } catch (IOException e) { e.printStackTrace(); } } } }
ConcurrentLinkedQueue是Queue隊列的一個線程安全實現(xiàn),這里使用它來作為保存請求的容器。AsyncContext是Tomcat支持的異步環(huán)境,不同的服務(wù)器使用的對象也略有不同。Jetty支持的對象是Continuation。完成了廣播的請求需要通過context.complete()將相關(guān)請求結(jié)束,并使用CONNECTIONS.remove(context)刪除隊列。
二、WebSocket(來自HTML5的支持)
使用 HTTP 長輪詢的 Comet 是可靠地實現(xiàn)反向 Ajax 的最佳方式,因為現(xiàn)在所有瀏覽器都提供了這方面的支持。
WebSockets 在 HTML5 中出現(xiàn),是比 Comet 更新的反向 Ajax 技術(shù)。WebSockets 支持雙向、全雙工通信信道,而且許多瀏覽器(Firefox、Google Chrome 和 Safari)也支持它。連接通過 HTTP 請求(也稱為 WebSockets 握手)和一些特殊的標(biāo)頭 (header)。連接一直處于激活狀態(tài),您可以用 JavaScript 編寫和接收數(shù)據(jù),正如您使用原始 TCP 套接字一樣。
通過輸入 ws:// 或 wss://(在 SSL 上)啟動 WebSocket URL。如圖:
首先:WebSockets并非在所有瀏覽器上都能獲得良好的支持,顯然IE又拖了后腿。因此當(dāng)你打算使用這項技術(shù)之前必須考慮到用戶的使用環(huán)境,如果你的項目面向的是互聯(lián)網(wǎng)或者包括手機端用戶,奉勸大家三思。
其次:WebSockets提供的請求區(qū)別于普通的HTTP請求,它是一種全雙工通信且始終處于激活狀態(tài)(如果你不去關(guān)閉它的話)。這就意味著你不用每次獲得應(yīng)答后再次向服務(wù)器發(fā)送請求,這樣可以節(jié)約大量的資源。
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <% String path = request.getContextPath(); String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path + "/"; String ws = "ws://" + request.getServerName() + ":" + request.getServerPort() + path + "/"; %> <!DOCTYPE html> <html lang="en"> <base href="<%=basePath%>"> <head> <title>WebSocket</title> <script type="text/javascript" src="static/jquery-1.9.1.min.js"></script> <script type="text/javascript"> $(function() { var websocket = null; if ("WebSocket" in window){ websocket = new WebSocket("<%=ws%>websocket"); } else { alert("not support"); } websocket.onopen = function(evt) { } websocket.onmessage = function(evt) { alert(evt.data); } websocket.onclose = function(evt) { } $("#btn").click(function() { var text = $("#message").val(); websocket.send(text); }); }); </script> </head> <body> <h1>WebSocket</h1> <input type="text" id="message" /> <input type="button" id="btn" value="發(fā)送"/> </body> </html>
JQuery對WebSocket還未提供更良好的支持,因此我們必須使用Javascript來編寫部分代碼(好在并不復(fù)雜)。并且打部分常見的服務(wù)器都可以支持ws請求,以Tomcat為例。在6.0版本中WebSocketServlet對象已經(jīng)被標(biāo)注為@java.lang.Deprecated,7.0以后的版本支持jsr365提供的實現(xiàn),因此你必須使用注解來完成相關(guān)配置。
package servlet; import java.io.IOException; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import javax.websocket.OnClose; import javax.websocket.OnMessage; import javax.websocket.OnOpen; import javax.websocket.Session; import javax.websocket.server.ServerEndpoint; @ServerEndpoint("/websocket") public class WebSocket { private static final Queue<WebSocket> CONNECTIONS = new ConcurrentLinkedQueue<WebSocket>(); private Session session; @OnOpen public void onOpen(Session session) { this.session = session; CONNECTIONS.offer(this); } @OnMessage public void onMessage(String message) { broadcast(message); } @OnClose public void onClose() { CONNECTIONS.remove(this); } private synchronized void broadcast(String msg) { for (WebSocket point : CONNECTIONS) { try { point.session.getBasicRemote().sendText(msg); } catch (IOException e) { CONNECTIONS.remove(point); try { point.session.close(); } catch (IOException e1) { } } } } }
三、總結(jié)(從請求到推送)
在傳統(tǒng)通信方案中,如果系統(tǒng) A 需要系統(tǒng) B 中的信息,它會向系統(tǒng) B 發(fā)送一個請求。系統(tǒng) B 將處理請求,而系統(tǒng) A 會等待響應(yīng)。處理完成后,會將響應(yīng)發(fā)送回系統(tǒng) A。在同步 通信模式下,資源使用效率比較低,這是因為等待響應(yīng)時會浪費處理時間。
在異步 模式下,系統(tǒng) A 將訂閱它想從系統(tǒng) B 中獲取的信息。然后,系統(tǒng) A 可以向系統(tǒng) B 發(fā)送一個通知,也可以立即返回信息,與此同時,系統(tǒng) A 可以處理其他事務(wù)。這個步驟是可選的。在事件驅(qū)動應(yīng)用程序中,通常不必請求其他系統(tǒng)發(fā)送事件,因為您不知道這些事件是什么。在系統(tǒng) B 發(fā)布響應(yīng)之后,系統(tǒng) A 會立即收到該響應(yīng)。
Web 框架過去通常依賴傳統(tǒng) “請求-響應(yīng)” 模式,該模式會導(dǎo)致頁面刷新。隨著 Ajax、Reverse Ajax 以及 WebSocket 的出現(xiàn),現(xiàn)在可以將事件驅(qū)動架構(gòu)的概念輕松應(yīng)用于 Web,獲得去耦合、可伸縮性和反應(yīng)性 (reactivity) 等好處。更良好的用戶體驗也會帶來新的商業(yè)契機。
以上所述是小編給大家介紹的反向Ajax 30分鐘快速掌握,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復(fù)大家的。在此也非常感謝大家對腳本之家網(wǎng)站的支持!
聲明:本網(wǎng)頁內(nèi)容旨在傳播知識,若有侵權(quán)等問題請及時與本網(wǎng)聯(lián)系,我們將在第一時間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com