zoukankan      html  css  js  c++  java
  • [转]30分钟学会反向Ajax

    原文链接:http://www.cnblogs.com/learnhow/p/5708364.html

    场景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) 等好处。更良好的用户体验也会带来新的商业契机。

  • 相关阅读:
    Gstreamer 随笔
    Git 提交(commit)没有自动生成Change-Id导致无法push
    xmake构建C/C++编译环境
    编译configure常用参数详解
    ubuntu上安装meson & 如何使用meson编译C代码
    如何使用autotools/automake自动生成Makefile文件
    如何扩大镜像文件容量
    Mac OS X 键盘快捷键
    django 文件目录结构
    oh-my-zsh 更换主题后特殊字符显示错误
  • 原文地址:https://www.cnblogs.com/jasonHome/p/5715855.html
Copyright © 2011-2022 走看看