反向Ajax 30分钟快速掌握
场景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>(); @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队列的一个线程安全实现,这里使用它来作为保存请求的容器。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; @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) { } } } } }
三、总结(从请求到推送)
在传统通信方案中,如果系统 a 需要系统 b 中的信息,它会向系统 b 发送一个请求。系统 b 将处理请求,而系统 a 会等待响应。处理完成后,会将响应发送回系统 a。在同步 通信模式下,资源使用效率比较低,这是因为等待响应时会浪费处理时间。
在异步 模式下,系统 a 将订阅它想从系统 b 中获取的信息。然后,系统 a 可以向系统 b 发送一个通知,也可以立即返回信息,与此同时,系统 a 可以处理其他事务。这个步骤是可选的。在事件驱动应用程序中,通常不必请求其他系统发送事件,因为您不知道这些事件是什么。在系统 b 发布响应之后,系统 a 会立即收到该响应。
web 框架过去通常依赖传统 “请求-响应” 模式,该模式会导致页面刷新。随着 ajax、reverse ajax 以及 websocket 的出现,现在可以将事件驱动架构的概念轻松应用于 web,获得去耦合、可伸缩性和反应性 (reactivity) 等好处。更良好的用户体验也会带来新的商业契机。
以上所述是小编给大家介绍的反向ajax 30分钟快速掌握,希望对大家有所帮助