情境1:當有新郵件的時候,網頁自動彈出提示資訊而無需使用者手動的重新整理收件匣。
情境2:當使用者的手機掃描完成頁面中的二維碼以後,頁面會自動跳轉。
情境3:在類似聊天室的環境中有任何人發言,所有登入使用者都可以即時看見資訊。
與傳統的MVC模型請求必須從用戶端發起由伺服器響應相比,使用反向Ajax能夠類比伺服器端主動向用戶端推送事件從而提高使用者體驗。本文將分兩個部分討論反向Ajax技術,包括:Comet和WebSocket。文章旨在示範如何?以上兩種技術手段,Struts2或SpringMVC中的應用並未涉及。此外,Servlet的配置也採用註解的方式,相關知識大家可以參考其它資料。
一、Comet(最佳的相容手段)
Comet本質上則是這樣的一種概念:能夠從伺服器端向用戶端發送資料。在一個標準的 HTTP Ajax 請求中,資料是發送給伺服器端的,反向 Ajax 以某些特定的方式來類比發出一個 Ajax 請求,這樣的話,伺服器就可以儘可能快地向用戶端發送事件。由於普通HTTP請求往往會伴隨頁面的跳轉,而推送事件則需要瀏覽器停留在同一個頁面或者架構下,因此Comet的實現只能夠通過Ajax來完成。
它的實現過程如下:頁面載入的時候隨即向伺服器發送一條Ajax請求,伺服器端擷取請求並將它儲存在一個安全執行緒的容器中(通常為隊列)。同時伺服器端仍然可以正常響應其他請求。當需要推送的事件到來的時候,伺服器遍曆容器中的請求在返回應答後刪除。於是所有停留在頁面中的瀏覽器都會獲得該應答,並再次發送Ajax請求,重複上述過程。
<%@ 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="發送" /></body></html>
我們注意到,由btn發送的請求其實並不需要擷取應答。整個過程的關鍵是需要用戶端始終讓伺服器保持connect()的請求。而伺服器端首先需要支援這種非同步回應程式式,幸運的是目前為止絕大部分的Servlet容器都已經提供了良好的支援。下面以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>();@Overrideprotected 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隊列的一個安全執行緒實現,這裡使用它來作為儲存請求的容器。AsyncContext是Tomcat支援的非同步環境,不同的伺服器使用的對象也略有不同。Jetty支援的對象是Continuation。完成了廣播的請求需要通過context.complete()將相關請求結束,並使用CONNECTIONS.remove(context)刪除隊列。
二、WebSocket(來自HTML5的支援)
使用 HTTP 長輪詢的 Comet 是可靠地實現反向 Ajax 的最佳方式,因為現在所有瀏覽器都提供了這方面的支援。
WebSockets 在 HTML5 中出現,是比 Comet 更新的反向 Ajax 技術。WebSockets 支援雙向、全雙工系統通訊通道,而且許多瀏覽器(Firefox、Google Chrome 和 Safari)也支援它。串連通過 HTTP 要求(也稱為 WebSockets 握手)和一些特殊的標題 (header)。串連一直處於啟用狀態,您可以用 JavaScript 編寫和接收資料,正如您使用原始 TCP 通訊端一樣。
通過輸入 ws:// 或 wss://(在 SSL 上)啟動 WebSocket URL。如圖:
首先:WebSockets並非在所有瀏覽器上都能獲得良好的支援,顯然IE又拖了後腿。因此當你打算使用這項技術之前必須考慮到使用者的使用環境,如果你的項目面向的是互連網或者包括手機端使用者,奉勸大家三思。
其次:WebSockets提供的請求區別於普通的HTTP請求,它是一種全雙工系統通訊且始終處於啟用狀態(如果你不去關閉它的話)。這就意味著你不用每次獲得應答後再次向伺服器發送請求,這樣可以節約大量的資源。
<%@ 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="發送"/></body></html>
JQuery對WebSocket還未提供更良好的支援,因此我們必須使用Javascript來編寫部分代碼(好在並不複雜)。並且打部分常見的伺服器都可以支援ws請求,以Tomcat為例。在6.0版本中WebSocketServlet對象已經被標註為@java.lang.Deprecated,7.0以後的版本支援jsr365提供的實現,因此你必須使用註解來完成相關配置。
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;@OnOpenpublic void onOpen(Session session) {this.session = session;CONNECTIONS.offer(this);}@OnMessagepublic void onMessage(String message) {broadcast(message);}@OnClosepublic 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) {}}}}}
三、總結(從請求到推送)
在傳統通訊方案中,如果系統 A 需要系統 B 中的資訊,它會向系統 B 發送一個請求。系統 B 將處理請求,而系統 A 會等待響應。處理完成後,會將響應發送回系統 A。在同步 通訊模式下,資源使用效率比較低,這是因為等待響應時會浪費處理時間。
在非同步 模式下,系統 A 將訂閱它想從系統 B 中擷取的資訊。然後,系統 A 可以向系統 B 發送一個通知,也可以立即返回資訊,與此同時,系統 A 可以處理其他事務。這個步驟是可選的。在事件驅動應用程式中,通常不必請求其他系統發送事件,因為您不知道這些事件是什麼。在系統 B 發布響應之後,系統 A 會立即收到該響應。
Web 架構過去通常依賴傳統 “要求-回應” 模式,該模式會導致頁面重新整理。隨著 Ajax、Reverse Ajax 以及 WebSocket 的出現,現在可以將事件驅動架構的概念輕鬆應用於 Web,獲得去耦合、延展性和反應性 (reactivity) 等好處。更良好的使用者體驗也會帶來新的商業契機。
以上所述是小編給大家介紹的反向Ajax 30分鐘快速掌握,希望對大家有所協助,如果大家有任何疑問請給我留言,小編會及時回複大家的。在此也非常感謝大家對雲棲社區網站的支援!