1、什么是反向ajax
反向Ajax(Reverse Ajax)本质上则是这样的一种概念:能够从服务器端向客户端发送数据。在一个标准的HTTPAjax请求中,数据是发送给服务器端的,反向Ajax可以某些特定的方式来模拟发出一个Ajax请求,这些方式本文都会论及,这样的话,服务器就可以尽可能快地向客户端发送事件(低延迟通信)。
2、反向ajax的目的
反向Ajax的目的是允许服务器端向客户端推送信息。Ajax请求在缺省情况下是无状态的,且只能从客户端向服务器端发出请求。你可以通过使用技术模拟服务器端和客户端之间的响应式通信来绕过这一限制。
3、为什么使用反向ajax
web开发在过去的几年中有了很大的进展,我们已经远超了把静态网页链接在一起的做法,这种做法会引起浏览器的刷新,并且要等待页面的加载。现在需要的是能够通过web来访问的完全动态的应用,这些应用通常需要尽可能的快,提供近乎实时的组件。
4、 实现反向ajax
a、轮询(polling)
轮询(polling)涉及了从客户端向服务器端发出请求以获取一些数据,这显然就是一个纯粹的Ajax HTTP请求。为了尽快地获得服务器端事件,轮询的间隔(两次请求相隔的时间)必须尽可能地小。但有这样的一个缺点存在:如果间隔减小的话,客户端浏览器就会发出更多的请求,这些请求中的许多都不会返回任何有用的数据,而这将会白白地浪费掉带宽和处理资源。
JSONP轮询基本上与HTTP轮询一样,不同之处则是JSONP可以发出跨域请求(不是在你的域内的请求)。清单1使用JSONP来通过邮政编码获取地名,JSONP请求通常可通过它的回调参数和返回内容识别出来,这些内容是可执行的JavaScript代码。
用JavaScript实现的轮询的优点和缺点:
1. 优点:很容易实现,不需要任何服务器端的特定功能,且在所有的浏览器上都能工作。
2. 缺点:这种方法很少被用到,因为它是完全不具伸缩性的。试想一下,在100个客户端每个都发出2秒钟的轮询请求的情况下,所损失的带宽和资源数量,在这种情况下30%的请求没有返回数据。
5、客户端代码实现
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <title>HTTP Polling</title> <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js"></script> <script type="text/javascript" src="http://jquery-json.googlecode.com/files/jquery.json-2.2.min.js"></script> <script type="text/javascript"> jQuery(function($) { setInterval(function() { $('body').append('<span>[client] checking for events...</span><br/>'); $.getJSON('events',function(events) { if(events.length) { $('body').append('<span style="color: blue;">[client] ' + events.length + ' events</span><br/>'); } else { $('body').append('<span style="color: red;">[client] no event</span><br/>'); } for (var i in events) { $('body').append('<span>[event] ' + events[i] + '</span><br/>'); } }); },2000); }); </script> </head> <body style="font-family: monospace;"> </body> </html>
public final class PollingServlet extends HttpServlet { private final Random random = new Random(); private final BlockingQueue<String> messages = new LinkedBlockingQueue<String>(); private final Thread generator = new Thread("Event generator") { @Override public void run() { while (!Thread.currentThread().isInterrupted()) { try { Thread.sleep(random.nextInt(5000)); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } messages.offer("At " + new Date()); } } }; @Override public void init() throws ServletException { generator.start(); } @Override public void destroy() { generator.interrupt(); } @Override protected void doGet(HttpServletRequest req,HttpServletResponse resp) throws ServletException,IOException { List<String> messages = new LinkedList<String>(); this.messages.drainTo(messages); resp.setStatus(HttpServletResponse.SC_OK); resp.setContentType("application/json"); resp.getWriter().write(new JSONArray(messages).toString()); resp.getWriter().flush(); } }
7、Piggyback
捎带轮询(piggyback polling)是一种比轮询更加聪明的做法,因为它会删除掉所有非必需的请求(没有返回数据的那些)。不存在时间间隔,客户端在需要的时候向服务器端发送请求。不同之处在于响应的那部分上,响应被分成两个部分:对请求数据的响应和对服务器事件的响应,如果任何一部分有发生的话。
这种方法也有着一些优点和缺点:
1. 优点:没有不返回数据的请求,因为客户端对何时发送请求做了控制,对资源的消耗较少。该方法也是可用在所有的浏览器上,不需要服务器端的特殊功能。
2. 缺点:当累积在服务器端的事件需要传送给客户端时,你却一点都不知道,因为这需要一个客户端行为来请求它们。
<!--客户端代码--> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <title>HTTP Polling</title> <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js"></script> <script type="text/javascript" src="http://jquery-json.googlecode.com/files/jquery.json-2.2.min.js"></script> <script type="text/javascript"> jQuery(function($) { function processEvents(events) { if (events.length) { $('#logs').append('<span style="color: blue;">[client] ' + events.length + ' events</span><br/>'); } else { $('#logs').append('<span style="color: red;">[client] no event</span><br/>'); } for (var i in events) { $('#logs').append('<span>[event] ' + events[i] + '</span><br/>'); } } $('#submit').click(function() { $('#logs').append('<span>[client] checking for events...</span><br/>'); $.post('ajax',function(data) { $('#logs').append('<span>[server] form valid ? ' + data.formValid + ' </span><br/>'); processEvents(data.events); }); }); }); </script> </head> <body> <button id="submit">Submit form</button> <div id="logs" style="font-family: monospace;"> </div> </body> </html>
public final class PiggybackServlet extends HttpServlet { private final Random random = new Random(); private final BlockingQueue<String> messages = new LinkedBlockingQueue<String>(); private final Thread generator = new Thread("Event generator") { @Override public void run() { while (!Thread.currentThread().isInterrupted()) { try { Thread.sleep(random.nextInt(5000)); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } messages.offer("At " + new Date()); } } }; @Override public void init() throws ServletException { generator.start(); } @Override public void destroy() {generator.interrupt();} @Override protected void doPost(HttpServletRequest req,IOException { System.out.println("FORM POSTED !"); List<String> messages = new LinkedList<String>(); this.messages.drainTo(messages); resp.setStatus(HttpServletResponse.SC_OK); resp.setContentType("application/json"); try { resp.getWriter().write(new JSONObject().put("events",new JSONArray(messages)).put("formValid",true).toString()); } catch (JSONException e) { throw new ServletException(e.getMessage(),e); } resp.getWriter().flush(); } @Override protected void doGet(HttpServletRequest req,IOException { List<String> messages = new LinkedList<String>(); this.messages.drainTo(messages); resp.setStatus(HttpServletResponse.SC_OK); resp.setContentType("application/json"); resp.getWriter().write(new JSONArray(messages).toString()); resp.getWriter().flush(); } }
使用了轮询或是捎带的反向Ajax非常受限:其不具伸缩性,不提供低延迟通信(只要事件一到达服务器端,它们就以尽可能快的速度到达浏览器端)。Comet是一个web应用模型,在该模型中,请求被发送到服务器端并保持一个很长的存活期,直到超时或是有服务器端事件发生。在该请求完成后,另一个长生存期的Ajax请求就被送去等待另一个服务器端事件。使用Comet的话,web服务器就可以在无需显式请求的情况下向客户端发送数据。
Comet的一大优点是,每个客户端始终都有一个向服务器端打开的通信链路。服务器端可以通过在事件到来时立即提交(完成)响应来把事件推给客户端,或者它甚至可以累积再连续发送。因为请求长时间保持打开的状态,故服务器端需要特别的功能来处理所有的这些长生存期请求。9、流(streaming)
在流(streaming)模式中,有一个持久连接会被打开。只会存在一个长生存期请求(图3中的#1),因为每个到达服务器端的事件都会通过这同一连接来发送。因此,客户端需要有一种方法来把通过这同一连接发送过来的不同响应分隔开来。从技术上来讲,两种常见的流技术包括Forever Iframe(隐藏的IFrame),或是被用来在JavaScript中创建Ajax请求的XMLHttpRequest对象的多部分(multi-part)特性。
10、Forever Iframe
Forever Iframe(永存的Iframe)技术涉及了一个置于页面中的隐藏Iframe标签,该标签的src属性指向返回服务器端事件的servlet路径。每次在事件到达时,servlet写入并刷新一个新的script标签,该标签内部带有JavaScript代码,iframe的内容被附加上这一script标签,标签中的内容就会得到执行。
1. 优点:实现简单,在所有支持iframe的浏览器上都可用。
2. 缺点: 没有方法可用来实现可靠的错误处理或是跟踪连接的状态,因为所有的连接和数据都是由浏览器通过HTML标签来处理的,因此你没有办法知道连接何时在哪一端已被断开了。
11、Multi-part XMLHttpRequest
第二种技术,更可靠一些,是XMLHttpRequest对象上使用某些浏览器(比如说Firefox)支持的multi-part标志。Ajax请求被发送给服务器端并保持打开状态,每次有事件到来时,一个多部分的响应就会通过这同一连接来写入。
1. 优点:只打开了一个持久连接,这就是节省了大部分带宽使用率的Comet技术。
2. 缺点:并非所有的浏览器都支持multi-part标志。某些被广泛使用的库,比如说用Java实现的CometD,被报告在缓冲方面有问题。例如,一些数据块(多个部分)可能被缓冲,然后只有在连接完成或是缓冲区已满时才被发送,而这有可能会带来比预期要高的延迟。<!--客户端代码--> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <title>HTTP Polling</title> <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js"></script> <script type="text/javascript" src="http://jquery-json.googlecode.com/files/jquery.json-2.2.min.js"></script> <script type="text/javascript"> jQuery(function($) { if (!('XMLHttpRequest' in window && 'multipart' in window.XMLHttpRequest.prototype)) { alert('Comet Http Streaming is not supported in your browser !'); throw new Error('Comet Http Streaming is not supported in your browser !'); } function processEvents(events) { if (events.length) { $('#logs').append('<span style="color: blue;">[client] ' + events.length + ' events</span><br/>'); } else { $('#logs').append('<span style="color: red;">[client] no event</span><br/>'); } for (var i in events) { $('#logs').append('<span>[event] ' + events[i] + '</span><br/>'); } } var xhr = $.ajaxSettings.xhr(); xhr.multipart = true; xhr.open('GET','ajax',true); xhr.onreadystatechange = function() { if (xhr.readyState == 4) { processEvents($.parseJSON(xhr.responseText)); } }; xhr.send(null); }); </script> </head> <body> <div id="logs" style="font-family: monospace;"> </div> </body> </html>
public final class ReverseAjaxServlet extends HttpServlet { private final Queue<AsyncContext> asyncContexts = new ConcurrentLinkedQueue<AsyncContext>(); private final String boundary = "ABCDEFGHIJKLMNOPQRST"; // generated private final Random random = new Random(); private final Thread generator = new Thread("Event generator") { @Override public void run() { while (!Thread.currentThread().isInterrupted()) { try { Thread.sleep(random.nextInt(5000)); for (AsyncContext asyncContext : asyncContexts) { HttpServletResponse peer = (HttpServletResponse) asyncContext.getResponse(); peer.getOutputStream().println("Content-Type: application/json"); peer.getOutputStream().println(); peer.getOutputStream().println(new JSONArray().put("At " + new Date()).toString()); peer.getOutputStream().println("--" + boundary); peer.flushBuffer(); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } catch (IOException e) { throw new RuntimeException(e.getMessage(),e); } } } }; @Override public void init() throws ServletException { generator.start(); } @Override public void destroy() { generator.interrupt(); } @Override protected void doGet(HttpServletRequest req,IOException { AsyncContext asyncContext = req.startAsync(); asyncContext.setTimeout(0); resp.setContentType("multipart/x-mixed-replace;boundary=\"" + boundary + "\""); resp.setHeader("Connection","keep-alive"); resp.getOutputStream().print("--" + boundary); resp.flushBuffer(); asyncContexts.offer(asyncContext); } }
12、长轮询(long polling)
长轮询(long polling)模式涉及了打开连接的技术。连接由服务器端保持着打开的状态,只要一有事件发生,响应就会被提交,然后连接关闭。接下来。一个新的长轮询连接就会被正在等待新事件到达的客户端重新打开。
12.1、script标签
正如iframe一样,其目标是把script标签附加到页面上以让脚本执行。服务器端则会:挂起连接直到有事件发生,接着把脚本内容发送回浏览器,然后重新打开另一个script标签来获取下一个事件。
1. 优点:因为是基于HTML标签的,所有这一技术非常容易实现,且可跨域工作(缺省情况下,XMLHttpRequest不允许向其他域或是子域发送请求)。
2. 缺点:类似于iframe技术,错误处理缺失,你不能获得连接的状态或是有干涉连接的能力。
12.2、XMLHttpRequest长轮询
也是一种推荐的实现Comet的做法是打开一个到服务器端的Ajax请求然后等待响应。服务器端需要一些特定的功能来允许请求被挂起,只要一有事件发生,服务器端就会在挂起的请求中送回响应并关闭该请求,完全就像是你关闭了servlet响应的输出流。然后客户端就会使用这一响应并打开一个新的到服务器端的长生存期的Ajax请求。
1. 优点:客户端很容易实现良好的错误处理系统和超时管理。这一可靠的技术还允许在与服务器端的连接之间有一个往返,即使连接是非持久的(当你的应用有许多的客户端时,这是一件好事)。它可用在所有的浏览器上;你只需要确保所用的XMLHttpRequest对象发送到的简单的Ajax请求就可以了。
2. 缺点:相比于其他技术来说,不存在什么重要的缺点,像所有我们已经讨论过的技术一样,该方法依然依赖于无状态的HTTP连接,其要求服务器端有特殊的功能来临时挂起连接。
<!--客户端代码--> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <title>HTTP Polling</title> <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js"></script> <script type="text/javascript" src="http://jquery-json.googlecode.com/files/jquery.json-2.2.min.js"></script> <script type="text/javascript"> jQuery(function($) { function processEvents(events) { if (events.length) { $('#logs').append('<span style="color: blue;">[client] ' + events.length + ' events</span><br/>'); } else { $('#logs').append('<span style="color: red;">[client] no event</span><br/>'); } for (var i in events) { $('#logs').append('<span>[event] ' + events[i] + '</span><br/>'); } } function long_polling() { $.getJSON('ajax',function(events) { processEvents(events); long_polling(); }); } long_polling(); }); </script> </head> <body> <div id="logs" style="font-family: monospace;"> </div> </body> </html>
public final class ReverseAjaxServlet extends HttpServlet { private final Queue<AsyncContext> asyncContexts = new ConcurrentLinkedQueue<AsyncContext>(); private final Random random = new Random(); private final Thread generator = new Thread("Event generator") { @Override public void run() { while (!Thread.currentThread().isInterrupted()) { try { Thread.sleep(random.nextInt(5000)); while (!asyncContexts.isEmpty()) { AsyncContext asyncContext = asyncContexts.poll(); HttpServletResponse peer = (HttpServletResponse) asyncContext.getResponse(); peer.getWriter().write(new JSONArray().put("At " + new Date()).toString()); peer.setStatus(HttpServletResponse.SC_OK); peer.setContentType("application/json"); asyncContext.complete(); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } catch (IOException e) { throw new RuntimeException(e.getMessage(),e); } } } }; @Override public void init() throws ServletException { generator.start(); } @Override public void destroy() { generator.interrupt(); } @Override protected void doGet(HttpServletRequest req,IOException { AsyncContext asyncContext = req.startAsync(); asyncContext.setTimeout(0); asyncContexts.offer(asyncContext); } }
13、pushlets框架
Pushlets 是一个开源的 Comet 框架,Pushlets 使用了观察者模型:客户端发送请求,订阅感兴趣的事件;服务器端为每个客户端分配一个会话 ID 作为标记,事件源会把新产生的事件以多播的方式发送到订阅者的事件队列里。
下载pushlets,官方地址:http://www.pushlets.com/
原文链接:https://www.f2er.com/ajax/165286.html