反向Ajax 30分鐘快速掌握
場(chǎng)景1:當(dāng)有新郵件的時(shí)候,網(wǎng)頁(yè)自動(dòng)彈出提示信息而無(wú)需用戶手動(dòng)的刷新收件箱。
場(chǎng)景2:當(dāng)用戶的手機(jī)掃描完成頁(yè)面中的二維碼以后,頁(yè)面會(huì)自動(dòng)跳轉(zhuǎn)。
場(chǎng)景3:在類似聊天室的環(huán)境中有任何人發(fā)言,所有登錄用戶都可以即時(shí)看見(jiàn)信息。
與傳統(tǒng)的MVC模型請(qǐng)求必須從客戶端發(fā)起由服務(wù)器響應(yīng)相比,使用反向Ajax能夠模擬服務(wù)器端主動(dòng)向客戶端推送事件從而提高用戶體驗(yàn)。本文將分兩個(gè)部分討論反向Ajax技術(shù),包括:Comet和WebSocket。文章旨在演示如何實(shí)現(xiàn)以上兩種技術(shù)手段,Struts2或SpringMVC中的應(yīng)用并未涉及。此外,Servlet的配置也采用注解的方式,相關(guān)知識(shí)大家可以參考其它資料。
一、Comet(最佳的兼容手段)
Comet本質(zhì)上則是這樣的一種概念:能夠從服務(wù)器端向客戶端發(fā)送數(shù)據(jù)。在一個(gè)標(biāo)準(zhǔn)的 HTTP Ajax 請(qǐng)求中,數(shù)據(jù)是發(fā)送給服務(wù)器端的,反向 Ajax 以某些特定的方式來(lái)模擬發(fā)出一個(gè) Ajax 請(qǐng)求,這樣的話,服務(wù)器就可以盡可能快地向客戶端發(fā)送事件。由于普通HTTP請(qǐng)求往往會(huì)伴隨頁(yè)面的跳轉(zhuǎn),而推送事件則需要瀏覽器停留在同一個(gè)頁(yè)面或者框架下,因此Comet的實(shí)現(xiàn)只能夠通過(guò)Ajax來(lái)完成。

它的實(shí)現(xiàn)過(guò)程如下:頁(yè)面加載的時(shí)候隨即向服務(wù)器發(fā)送一條Ajax請(qǐng)求,服務(wù)器端獲取請(qǐng)求并將它保存在一個(gè)線程安全的容器中(通常為隊(duì)列)。同時(shí)服務(wù)器端仍然可以正常響應(yīng)其他請(qǐng)求。當(dāng)需要推送的事件到來(lái)的時(shí)候,服務(wù)器遍歷容器中的請(qǐng)求在返回應(yīng)答后刪除。于是所有停留在頁(yè)面中的瀏覽器都會(huì)獲得該應(yīng)答,并再次發(fā)送Ajax請(qǐng)求,重復(fù)上述過(guò)程。
<%@ 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ā)送的請(qǐng)求其實(shí)并不需要獲取應(yīng)答。整個(gè)過(guò)程的關(guān)鍵是需要客戶端始終讓服務(wù)器保持connect()的請(qǐng)求。而服務(wù)器端首先需要支持這種異步的響應(yīng)方式,幸運(yùn)的是目前為止絕大部分的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隊(duì)列的一個(gè)線程安全實(shí)現(xiàn),這里使用它來(lái)作為保存請(qǐng)求的容器。AsyncContext是Tomcat支持的異步環(huán)境,不同的服務(wù)器使用的對(duì)象也略有不同。Jetty支持的對(duì)象是Continuation。完成了廣播的請(qǐng)求需要通過(guò)context.complete()將相關(guān)請(qǐng)求結(jié)束,并使用CONNECTIONS.remove(context)刪除隊(duì)列。
二、WebSocket(來(lái)自HTML5的支持)
使用 HTTP 長(zhǎng)輪詢的 Comet 是可靠地實(shí)現(xiàn)反向 Ajax 的最佳方式,因?yàn)楝F(xiàn)在所有瀏覽器都提供了這方面的支持。
WebSockets 在 HTML5 中出現(xiàn),是比 Comet 更新的反向 Ajax 技術(shù)。WebSockets 支持雙向、全雙工通信信道,而且許多瀏覽器(Firefox、Google Chrome 和 Safari)也支持它。連接通過(guò) HTTP 請(qǐng)求(也稱為 WebSockets 握手)和一些特殊的標(biāo)頭 (header)。連接一直處于激活狀態(tài),您可以用 JavaScript 編寫(xiě)和接收數(shù)據(jù),正如您使用原始 TCP 套接字一樣。
通過(guò)輸入 ws:// 或 wss://(在 SSL 上)啟動(dòng) WebSocket URL。如圖:

首先:WebSockets并非在所有瀏覽器上都能獲得良好的支持,顯然IE又拖了后腿。因此當(dāng)你打算使用這項(xiàng)技術(shù)之前必須考慮到用戶的使用環(huán)境,如果你的項(xiàng)目面向的是互聯(lián)網(wǎng)或者包括手機(jī)端用戶,奉勸大家三思。
其次:WebSockets提供的請(qǐng)求區(qū)別于普通的HTTP請(qǐng)求,它是一種全雙工通信且始終處于激活狀態(tài)(如果你不去關(guān)閉它的話)。這就意味著你不用每次獲得應(yīng)答后再次向服務(wù)器發(fā)送請(qǐng)求,這樣可以節(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對(duì)WebSocket還未提供更良好的支持,因此我們必須使用Javascript來(lái)編寫(xiě)部分代碼(好在并不復(fù)雜)。并且打部分常見(jiàn)的服務(wù)器都可以支持ws請(qǐng)求,以Tomcat為例。在6.0版本中WebSocketServlet對(duì)象已經(jīng)被標(biāo)注為@java.lang.Deprecated,7.0以后的版本支持jsr365提供的實(shí)現(xiàn),因此你必須使用注解來(lái)完成相關(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é)(從請(qǐng)求到推送)
在傳統(tǒng)通信方案中,如果系統(tǒng) A 需要系統(tǒng) B 中的信息,它會(huì)向系統(tǒng) B 發(fā)送一個(gè)請(qǐng)求。系統(tǒng) B 將處理請(qǐng)求,而系統(tǒng) A 會(huì)等待響應(yīng)。處理完成后,會(huì)將響應(yīng)發(fā)送回系統(tǒng) A。在同步 通信模式下,資源使用效率比較低,這是因?yàn)榈却憫?yīng)時(shí)會(huì)浪費(fèi)處理時(shí)間。
在異步 模式下,系統(tǒng) A 將訂閱它想從系統(tǒng) B 中獲取的信息。然后,系統(tǒng) A 可以向系統(tǒng) B 發(fā)送一個(gè)通知,也可以立即返回信息,與此同時(shí),系統(tǒng) A 可以處理其他事務(wù)。這個(gè)步驟是可選的。在事件驅(qū)動(dòng)應(yīng)用程序中,通常不必請(qǐng)求其他系統(tǒng)發(fā)送事件,因?yàn)槟恢肋@些事件是什么。在系統(tǒng) B 發(fā)布響應(yīng)之后,系統(tǒng) A 會(huì)立即收到該響應(yīng)。
Web 框架過(guò)去通常依賴傳統(tǒng) “請(qǐng)求-響應(yīng)” 模式,該模式會(huì)導(dǎo)致頁(yè)面刷新。隨著 Ajax、Reverse Ajax 以及 WebSocket 的出現(xiàn),現(xiàn)在可以將事件驅(qū)動(dòng)架構(gòu)的概念輕松應(yīng)用于 Web,獲得去耦合、可伸縮性和反應(yīng)性 (reactivity) 等好處。更良好的用戶體驗(yàn)也會(huì)帶來(lái)新的商業(yè)契機(jī)。
以上所述是小編給大家介紹的反向Ajax 30分鐘快速掌握,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
- 基于PHP技術(shù)開(kāi)發(fā)客服工單系統(tǒng)
- php 微信公眾平臺(tái)開(kāi)發(fā)模式實(shí)現(xiàn)多客服的實(shí)例代碼
- PHP+Mysql+Ajax實(shí)現(xiàn)淘寶客服或阿里旺旺聊天功能(前臺(tái)頁(yè)面)
- PHP+jquery+ajax實(shí)現(xiàn)即時(shí)聊天功能實(shí)例
- 發(fā)布一個(gè)迷你php+AJAX聊天程序[聊天室]提供下載
- Ajax PHP JavaScript MySQL實(shí)現(xiàn)簡(jiǎn)易無(wú)刷新在線聊天室
- 值得分享的php+ajax實(shí)時(shí)聊天室
- PHP+mysql+ajax輕量級(jí)聊天室實(shí)現(xiàn)方法詳解
- 基于javascript、ajax、memcache和PHP實(shí)現(xiàn)的簡(jiǎn)易在線聊天室
- PHP使用反向Ajax技術(shù)實(shí)現(xiàn)在線客服系統(tǒng)詳解
相關(guān)文章
ajax實(shí)現(xiàn)點(diǎn)擊不同的鏈接讓返回的內(nèi)容顯示在特定div里
過(guò)ajax實(shí)現(xiàn)在一個(gè)web頁(yè)面點(diǎn)擊不同的鏈接,然后將返回的結(jié)果顯示在該頁(yè)面固定的div里2014-06-06
仿google搜索提示 SuggestFramework的使用
使用幫助(英文版翻譯而來(lái),可能有錯(cuò)誤,請(qǐng)大家仔細(xì)核對(duì),也希望對(duì)新手理解能有所幫助)2008-09-09
ajax無(wú)刷新分頁(yè)的簡(jiǎn)單實(shí)現(xiàn)
這篇文章主要為大家詳細(xì)介紹了ajax無(wú)刷新分頁(yè)的簡(jiǎn)單實(shí)現(xiàn)代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-05-05
django使用ajax post數(shù)據(jù)出現(xiàn)403錯(cuò)誤如何解決
在django中,使用jquery ajax post數(shù)據(jù),會(huì)出現(xiàn)403的錯(cuò)誤,該如何解決呢?下面由腳本之家小編幫大家解決django使用ajax post數(shù)據(jù)出現(xiàn)403錯(cuò)誤,需要的朋友可以參考下2015-09-09
快速解決ajax傳遞為空但顯示在頁(yè)面上為undefined的問(wèn)題
今天小編就為大家分享一篇快速解決ajax傳遞為空但顯示在頁(yè)面上為undefined的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-08-08
jQuery Validator驗(yàn)證Ajax提交表單的方法和Ajax傳參的方法
這篇文章主要介紹了jQuery Validator驗(yàn)證Ajax提交表單的方法和Ajax傳參的方法,在文中還給大家提到了jquery .ajax提交表單的寫(xiě)法,具體實(shí)例代碼大家參考下本文2017-08-08
詳解ajax +jtemplate實(shí)現(xiàn)動(dòng)態(tài)分頁(yè)
jtemplate是一個(gè)基于JQuery的模板引擎插件,功能非常強(qiáng)大,有了她你就再不用為使用JS綁定數(shù)據(jù)集而發(fā)愁了。本文給大家分享ajax +jtemplate實(shí)現(xiàn)動(dòng)態(tài)分頁(yè),需要的朋友可以參考下本文2015-09-09

