`
张江兴
  • 浏览: 122500 次
  • 性别: Icon_minigender_1
  • 来自: 湖南
社区版块
存档分类
最新评论

反向 Ajax,Comet 简介

 
阅读更多

 

轮询:客户端定时向服务器发送Ajax请求,服务器接到请求后马上返回响应信息并关闭连接。
优点:后端程序编写比较容易。
缺点:请求中有大半是无用,浪费带宽和服务器资源。
实例:适于小型应用。

 


长轮询:客户端向服务器发送Ajax请求,服务器接到请求后hold住连接,直到有新消息才返回响应信息并关闭连接,客户端处理完响应信息后再向服务器发送新的请求。
优点:在无消息的情况下不会频繁的请求,耗费资源小。
缺点:服务器hold连接会消耗资源,返回数据顺序无保证,难于管理维护。
实例:WebQQHi网页版、Facebook IM

 

 

 

长连接:在页面里嵌入一个隐蔵iframe,将这个隐蔵iframesrc属性设为对一个长连接的请求或是采用xhr请求,服务器端就能源源不断地往客户端输入数据。
优点:消息即时到达,不发无用请求;管理起来也相对方便。
缺点:服务器维护一个长连接会增加开销。
实例:Gmail聊天

 

 

 

Flash Socket在页面中内嵌入一个使用了Socket类的 Flash 程序JavaScript通过调用此Flash程序提供的Socket接口与服务器端的Socket接口进行通信,JavaScript在收到服务器端传送的信息后控制页面的显示。
优点:实现真正的即时通信,而不是伪即时。
缺点:客户端必须安装Flash插件;非HTTP协议,无法自动穿越防火墙。
实例:网络互动游戏。

 

 

 

简介

 

Web 开发在过去的几年中有了很大的进展,我们已经远超了把静态网页链接在一起的做法,这种做法会引起浏览器的刷新,并且要等待页面的加载。现在需要的是能够通过 Web 来访问完全动态的应用。这些应用通常需要尽可能的快,提供近乎实时的组件。在这个分为 5 部分的新系列中,我们学习如何使用反向 Ajax (Reverse Ajax) 技术来开发事件驱动的 Web 应用。

 

在这第一篇文章中,我们要了解反向 Ajax、轮询 (polling)、流 (streaming)Comet 和长轮询 (long polling)。学习如何实现不同的反向 Ajax 通信技术,并探讨每种方法的优点和缺点。

 


 

Ajax、反向 Ajax WebSockets

 

异步的 JavaScript XML (Ajax),一种可通过 JavaScript 来访问的浏览器功能特性,其允许脚本向幕后的网站发送一个 HTTP 请求而又无需重新加载页面。 Ajax 的出现已经超过了十年,尽管其名字中包含了 XML,但您几乎可以在 Ajax 请求中传送任何的东西。最常使用的数据是 JSON,它与 JavaScript 语法非常接近且消耗更少的带宽。清单 1清单 1 给出了这样的一个例子,Ajax 请求通过某个地方的邮政编码来检索该地的名称。

 

清单 1. 清单 1 Ajax 请求示例

 

var url = 'http://www.geonames.org/postalCodeLookupJSON?postalcode='

    + $('#postalCode').val() + '&country='

    + $('#country').val() + '&callback=?';

$.getJSON(url, function(data) {

    $('#placeName').val(data.postalcodes[0].placeName);

});

 

反向 Ajax (Reverse Ajax) 本质上则是这样的一种概念:能够从服务器端向客户端发送数据。在一个标准的 HTTP Ajax 请求中,数据是发送给服务器端的,反向 Ajax 可以某些特定的方式来模拟发出一个 Ajax 请求,这些方式本文都会论及,这样的话,服务器就可以尽可能快地向客户端发送事件(低延迟通信)。

 

WebSocket 技术来自 HTML5,是一种最近才出现的技术,许多浏览器已经支持它(FirefoxGoogle ChromeSafari 等等)。WebSocket 启用双向的、全双工的通信信道,其通过某种被称为 WebSocket 握手的 HTTP 请求来打开连接,并用到了一些特殊的报头。连接保持在活动状态,您可以用 JavaScript 来写和接收数据,就像是正在用一个原始的 TCP 套接口一样。WebSocket 会在这一文章系列的第二部分中谈及。

 


 

反向 Ajax 技术

 

反向 Ajax 的目的是让服务器将信息推送到客户端。Ajax 请求默认情况下是无状态的,且只能从客户端向服务器端发出请求。您可以通过使用技术模拟服务器端和客户端之间的响应式通信来绕过这一限制。

 

HTTP 轮询和 JSONP 轮询

 

轮询 (Polling) 涉及了从客户端向服务器端发出请求以获取一些数据,这显然就是一个纯粹的 Ajax HTTP 请求。为了尽快地获得服务器端事件,轮询的间隔(两次请求相隔的时间)必须尽可能地小。但有这样的一个缺点存在:如果间隔减小的话,客户端浏览器就会发出更多的请求,这些请求中的许多都不会返回任何有用的数据,而这将会白白地浪费掉带宽和处理资源。

 

1 1 中的时间线说明了客户端发出了某些轮询请求,但没有信息返回这种情况,客户端必须要等到下一个轮询来获取两个服务器端接收到的事件。

 

1. 使用 HTTP 轮询的反向 Ajax

 

 

 

JSONP 轮询基本上与 HTTP 轮询一样。不同之处在于使用 JSONP 您可以发送跨域请求(请求不属于您所在的域)。清单1使用了 JSONP 来通过邮政编码获取地名。JSONP 请求通常可通过它的回调参数和返回内容识别出来,这些内容是可执行的 JavaScript 代码。

 

要在 JavaScript 中实现轮询,您可以使用setInterval来定期地发出 Ajax 请求,如清单 2清单 2 所示:

 

清单 2. 清单 2 JavaScript 轮询

 

setInterval(function() {

    $.getJSON('events', function(events) {

        console.log(events);

    });

}, 2000);

 

轮询演示给出了轮询方法所消耗的带宽,间隔很小,但可以看到有些请求并未返回事件,清单 3清单 3 给出了这一轮询示例的输出。

 

清单 3. 清单 3 轮询演示例子的输出

 

[client] checking for events...

[client] no event

[client] checking for events...

[client] 2 events

[event] At Sun Jun 05 15:17:14 EDT 2011

[event] At Sun Jun 05 15:17:14 EDT 2011

[client] checking for events...

[client] 1 events

[event] At Sun Jun 05 15:17:16 EDT 2011

 

JavaScript 实现的轮询的优点和缺点。

 

<!--[if !supportLists]-->·         <!--[endif]-->优点:很容易实现,不需要任何服务器端的特定功能,且在所有的浏览器上都能工作。

 

<!--[if !supportLists]-->·         <!--[endif]-->缺点:这种方法很少被用到,因为它是完全不具伸缩性的。试想一下,在 100 个客户端每个都发出 2 秒钟的轮询请求的情况下,所损失的带宽和资源数量,在这种情况下 30% 的请求没有返回数据。

 

Piggyback

 

捎带轮询 (piggyback polling) 是一种比轮询更加聪明的做法,因为它会删除掉所有非必需的请求(没有返回数据的那些)。不存在时间间隔,客户端在需要的时候向服务器端发送请求。不同之处在于响应的那部分上,响应被分成两个部分:对请求数据的响应和对服务器事件的响应,如果任何一部分有发生的话。图 2 2 给出了一个例子。

 

2. 使用了 piggyback 轮询的反向 Ajax

 

 

 

在实现 piggyback 技术时,通常针对服务器端的所有 Ajax 请求可能会返回一个混合的响应。

 

清单 4. 清单 4 piggyback 代码示例

 

$('#submit').click(function() {

    $.post('ajax', function(data) {

        var valid = data.formValid;

        // process validation results

        // then process the other part of the response (events)

        processEvents(data.events);

    });

});

 

清单 5清单 5 给出了一些 piggyback 输出。

 

清单 5. 清单 5 piggyback 输出示例

 

[client] checking for events...

[server] form valid ? true

[client] 4 events

[event] At Sun Jun 05 16:08:32 EDT 2011

[event] At Sun Jun 05 16:08:34 EDT 2011

[event] At Sun Jun 05 16:08:34 EDT 2011

[event] At Sun Jun 05 16:08:37 EDT 2011

 

您可以看到表单验证的结果和附加到响应上的事件。同样,这种方法也有着一些优点和缺点。

 

<!--[if !supportLists]-->·         <!--[endif]-->优点:没有不返回数据的请求,因为客户端对何时发送请求做了控制,对资源的消耗较少。该方法也是可用在所有的浏览器上,不需要服务器端的特殊功能。

 

<!--[if !supportLists]-->·         <!--[endif]-->缺点:当累积在服务器端的事件需要传送给客户端时,您却一点都不知道,因为这需要一个客户端行为来请求它们。

 


 

Comet

 

使用了轮询或是捎带的反向 Ajax 非常受限:其不具伸缩性,不提供低延迟通信(只要事件一到达服务器端,它们就以尽可能快的速度到达浏览器端)。 Comet是一个 Web 应用模型,在该模型中,请求被发送到服务器端并保持一个很长的存活期,直到超时或是有服务器端事件发生。在该请求完成后,另一个长生存期的 Ajax 请求就被送去等待另一个服务器端事件。使用 Comet 的话,Web 服务器就可以在无需显式请求的情况下向客户端发送数据。

 

Comet 的一大优点是,每个客户端始终都有一个向服务器端打开的通信链路。服务器端可以通过在事件到来时立即提交(完成)响应来把事件推给客户端,或者它甚至可以累积再连续发送。因为请求长时间保持打开的状态,故服务器端需要特别的功能来处理所有的这些长生存期请求。图 3 3 给出了一个例子。(本系列的第 2 部分会更详细地解释服务器端的约束条件。)

 

3. 3.使用 Comet 的反向 Ajax

 

 

 

Comet 的实现可以分成两类:使用流 (streaming) 的那些和使用长轮询 (long polling) 的那些。

 


 

使用 HTTP 流的 Comet

 

在流 (streaming) 模式中,有一个持久连接会被打开。只会存在一个长生存期请求( 3 3 中的 #1),因为每个到达服务器端的事件都会通过这同一连接来发送。因此,客户端需要有一种方法来把通过这同一连接发送过来的不同响应分隔开来。从技术上来讲,两种常见的流技术包括 Forever Iframe(或者 hidden IFrame),或是被用来在 JavaScript 中创建 Ajax 请求的XMLHttpRequest对象的多部分 (multi-part) 特性。

 

Forever Iframes

 

Forever Iframe(永存的 Iframe)技术涉及了一个置于页面中的隐藏 Iframe 标签,该标签的src属性指向返回服务器端事件的 servlet 路径。每次在事件到达时,servlet 写入并刷新一个新的 script 标签,该标签内部带有 JavaScript 代码,iframe 的内容被附加上这一 script 标签,标签中的内容就会得到执行。

 

<!--[if !supportLists]-->·         <!--[endif]-->优点:实现简单,在所有支持 iframe 的浏览器上都可用。

 

<!--[if !supportLists]-->·         <!--[endif]-->缺点:没有方法可用来实现可靠的错误处理或是跟踪连接的状态,因为所有的连接和数据都是由浏览器通过 HTML 标签来处理的,因此您没有办法知道连接何时在哪一端已被断开了。

 

多部分的XMLHttpRequest

 

第二种技术(更加可靠)是在XMLHttpRequest对象上使用某些浏览器(比如 Firefox)支持的 multi-part 标志。Ajax 请求被发送给服务器端并保持打开状态,每次有事件到来时,一个多部分的响应就会通过这同一连接来写入。清单 6清单 6 给出了一个例子。

 

清单 6. 清单 6 设置多部分流请求的 JavaScript 代码示例

 

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);

 

在服务器端,事情要稍加复杂一些。首先您必须要设置多部分请求,然后挂起连接。清单 7清单 7 展示了如何挂起一个 HTTP 流请求。(本系列的第 3 部分会更加详细地谈及这些 API。)

 

清单 7. 清单 7 使用 Servlet 3 API 来在 servlet 中挂起一个 HTTP 流请求

 

protected void doGet(HttpServletRequest req, HttpServletResponse resp)

        throws ServletException, 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);

}

 

现在,每次有事件发生时您都可以遍历所有的挂起连接并向它们写入数据,如清单 8清单 8 所示:

 

清单 8. 使用 Servlet 3 API 来向挂起的多部分请求发送事件

 

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();

}

 

本文可下载文件的 Comet-straming 文件夹中的部分说明了 HTTP 流,在运行例子并打开主页时,您会看到只要事件一到达服务器端,虽然不同步但它们几乎立刻会出现在页面上。而且,如果打开 Firebug 控制台的话,您就能看到只有一个 Ajax 请求是打开的。如果再往下看一些,您会看到 JSON 响应被附在 Response 选项卡中,如图 4 4 所示:

 

4. HTTP 流请求的 Firebug 视图

 

 

 

照例,做法存在着一些优点和缺点。

 

<!--[if !supportLists]-->·         <!--[endif]-->优点:只打开了一个持久连接,这就是节省了大部分带宽使用率的 Comet 技术。

 

<!--[if !supportLists]-->·         <!--[endif]-->缺点:并非所有的浏览器都支持 multi-part 标志。某些被广泛使用的库,比如说用 Java 实现的 CometD,被报告在缓冲方面有问题。例如,一些数据块(多个部分)可能被缓冲,然后只有在连接完成或是缓冲区已满时才被发送,而这有可能会带来比预期要高的延迟。

 


 

使用 HTTP 长轮询的 Comet

 

长轮询 (long polling) 模式涉及了打开连接的技术。连接由服务器端保持着打开的状态,只要一有事件发生,响应就会被提交,然后连接关闭。接下来。一个新的长轮询连接就会被正在等待新事件到达的客户端重新打开。

 

您可以使用 script 标签或是单纯的XMLHttpRequest对象来实现 HTTP 长轮询。

 

script 标签

 

正如 iframe 一样,其目标是把 script 标签附加到页面上以让脚本执行。服务器端则会:挂起连接直到有事件发生,接着把脚本内容发送回浏览器,然后重新打开另一个 script 标签来获取下一个事件。

 

<!--[if !supportLists]-->·         <!--[endif]-->优点:因为是基于 HTML 标签的,所有这一技术非常容易实现,且可跨域工作(默认情况下,XMLHttpRequest不允许向其他域或是子域发送请求)。

 

<!--[if !supportLists]-->·         <!--[endif]-->缺点:类似于 iframe 技术,错误处理缺失,您不能获得连接的状态或是有干涉连接的能力。

 


 

XMLHttpRequest长轮询

 

第二种,也是一种推荐的实现 Comet 的做法是打开一个到服务器端的 Ajax 请求然后等待响应。服务器端需要一些特定的功能来允许请求被挂起,只要一有事件发生,服务器端就会在挂起的请求中送回响应并关闭该请求,完全就像是您关闭了 servlet 响应的输出流。然后客户端就会使用这一响应并打开一个新的到服务器端的长生存期的 Ajax 请求,如清单 9清单 9 所示:

 

清单 9. 清单 9 设置长轮询请求的 JavaScript 代码示例

 

function long_polling() {

    $.getJSON('ajax', function(events) {

        processEvents(events);

        long_polling();

    });

}

 

long_polling();

 

在后端,代码也是使用 Servlet 3 API 来挂起请求,正如 HTTP 流的做法一样,但是您不需要所有的多部分处理代码。清单 10清单 10 给出了一个例子。

 

清单 10. 清单 10 挂起一个长轮询 Ajax 请求

 

protected void doGet(HttpServletRequest req, HttpServletResponse resp)

        throws ServletException, IOException {

    AsyncContext asyncContext = req.startAsync();

    asyncContext.setTimeout(0);

    asyncContexts.offer(asyncContext);

}

 

在接收到事件时,只是取出所有的挂起请求并完成它们,如清单 11清单 11 所示:

 

清单 11. 清单 11 在有事件发生时完成长轮询 Ajax 请求

 

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();

}

 

<!--[if !supportLists]-->·         <!--[endif]-->优点:客户端很容易实现良好的错误处理系统和超时管理。这一可靠的技术还允许在与服务器端的连接之间有一个往返,即使连接是非持久的(当您的应用有许多的客户端时,这是一件好事)。它可用在所有的浏览器上;您只需要确保所用的XMLHttpRequest对象发送到了简单的 Ajax 请求就可以了。

 

<!--[if !supportLists]-->·         <!--[endif]-->缺点:相比于其他技术来说,不存在什么重要的缺点,像所有我们已经讨论过的技术一样,该方法依然依赖于无状态的 HTTP 连接,其要求服务器端有特殊的功能来临时挂起连接。

 


 

建议

 

因为所有现代的浏览器都支持跨域资源共享(Cross-Origin Resource ShareCORS)规范,该规范允许XHR执行跨域请求,因此基于脚本的和基于 iframe 的技术已成为了一种过时的需要。

 

为反向 Ajax 实现和使用 Comet 的最好方法是通过XMLHttpRequest对象,它提供了一个真正的连接句柄和错误处理。考虑到不是所有的浏览器都支持 multi-part 标志,且多部分流可能会遇到缓冲问题,因此建议您选择经由 HTTP 长轮询使用XMLHttpRequest对象(在服务器端挂起的一个简单的 Ajax 请求)的 Comet 模式,所有支持 Ajax 的浏览器也都支持该种做法。

 


 

结束语

 

本文提供的是反向 Ajax 技术的一个入门级介绍,文章探索了实现反向 Ajax 通信的不同方法,并说明了每种实现的优势和弊端。您的具体情况和应用需求将会影响到您对最合适方法的选择。不过一般来说,如果您想要在低延迟通信、超时和错误检测、简易性,以及所有浏览器和平台的良好支持这几方面有一个最好的折中的话,那就选择使用了 Ajax 长轮询请求的 Comet

 

 

 

分享到:
评论

相关推荐

    Comet,反向Ajax,直接就能跑

    dwr comet 反向ajax实力 直接抛 我打了一个包, 放到Tomcat,jetty下面就能直接跑了 很方便 还有注视 对新手 。。。。很好的

    反向ajax教程 (自己整理的两篇)

    反向Ajax,也称为Comet技术,是一种创新的Web应用通信模式,旨在克服传统Ajax模型中的实时性问题。在传统的Ajax应用中,客户端通过异步请求来获取服务器端的更新,这种方式并不真正实时,因为更新依赖于客户端的定期...

    反向ajax

    **反向Ajax(Reverse Ajax)**,又称为**Comet技术**,是Web开发中的一种创新方式,主要用于实现服务器向客户端的实时数据推送。在传统的Ajax应用中,主要是客户端通过JavaScript向服务器发送异步请求,获取数据并...

    dwr反向Ajax的三种情况

    DWR提供了三种反向Ajax技术,分别是轮询、Comet和PiggyBack。 1. **轮询(Polling)**: 轮询是最基础的反向Ajax技术。客户端每隔一定时间(例如几秒或几十秒)就向服务器发送一个请求,询问是否有新数据。如果有...

    反向ajax聊天简单例子

    而“反向Ajax”(Reverse Ajax)则是Ajax的一种扩展应用,也被称为Comet技术。这种技术的主要特点是服务器能够主动向客户端推送数据,而不是传统的客户端请求、服务器响应模式。本文将围绕一个基于Java的反向Ajax...

    反向Ajax的聊天室

    而在反向Ajax(也称为Comet技术)中,服务器可以主动推送数据到客户端,而非等待客户端发起请求。这对于创建实时通信的应用,如聊天室,非常有用。 DWR(Direct Web Remoting)是一个开源Java框架,它简化了在Web...

    dwr的例子 反向AJAX 实现时时提醒

    Direct Web Remoting (DWR) 是一个开源Java库,它允许在浏览器和服务器之间进行安全、高效的异步通信,即所谓的“反向AJAX”或“Comet”技术。DWR使得JavaScript能够调用服务器端的Java方法,就像它们是本地函数一样...

    Comet, 下一代反向AJAX(即服务器推送技术- Server-side push)

    Comet 有时也称反向 Ajax 或服务器端推技术(server-side push)。其思想很简单:将数据直接从服务器推到浏览器,而不必等到浏览器请求数据。听起来简单,但是如果熟悉 Web 应用程序,尤其是 HTTP 协议,那么您就会...

    反向Ajax 30分钟快速掌握

    本篇文章将重点探讨两种反向Ajax技术:Comet和WebSocket。 **Comet技术** Comet是最早的服务器推送技术之一,适用于需要向浏览器实时推送数据的应用场景。它的核心思想是通过长时间运行的Ajax请求保持客户端和...

    php开发客服系统(持久连接+轮询+反向ajax) - php严程序

    一:iframe + 服务器推技术comet(反向ajax,即服务器向浏览器推送数据) 二:ajax持久连接 + 长轮询 客服端采用第一种方式:iframe + 服务器推技术 思路: 1:新建comentbyiframe.php 该用文件使用while(true)一直连接...

    Comet4j demo

    通过深入理解并实践这个Comet4j demo,开发者可以掌握如何利用反向Ajax技术实现实时聊天应用,这对于构建交互性强、用户体验良好的Web应用非常有帮助。在实际项目中,可以根据需求扩展这个基础架构,例如添加文件...

    comet demo 向客户端推送例子

    Comet技术是一种基于HTTP长连接的反向Ajax技术,它允许服务器向客户端浏览器主动推送数据,从而实现双向通信。在Web应用中,通常的HTTP请求是客户端发起的,而Comet打破了这种模式,使得服务器可以在适当的时候主动...

    comet的demo

    - **反向Ajax**:Comet技术有时被称为反向Ajax,因为它是服务器向浏览器推送数据,而不是传统的Ajax(Asynchronous JavaScript and XML)由浏览器发起请求获取数据。 2. **Comet技术类型**: - **HTTP流**:...

    comet4j开发指南

    Comet4J的核心理念是 Comet(反向Ajax)技术,它通过长时间运行的HTTP连接,使得服务器可以主动向客户端推送数据,而无需等待客户端的请求。这极大地提高了实时应用的性能和用户体验。在Comet4J中,服务器端通过一个...

    DWR学习资料

    DWR学习资料 :DWR 3.0 上传文件.txt DWR3.0反向Ajax示例.txt DWR3.0学习笔记.txt DWR3.0学习网址.txt dwr分页.doc DWR分页代码.doc DWR中文文档.doc DWR中文文档.pdf dwr做comet的完整实现.doc Spring整合DWR comet ...

    PHP使用反向Ajax技术实现在线客服系统详解

    与传统Ajax技术的客户端拉数据(客户端通过定时向服务器发送请求来获取最新数据)不同,反向Ajax技术中,服务器能够实时地将更新的数据推送到客户端。这种方式可以有效减少服务器的请求次数,提高数据传输效率,尤其...

    comet4j_7.jar和comet4j-0.0.2.js

    4. **推送技术**:Comet4j利用了HTTP长连接(Comet)策略,这是一种反向Ajax技术,让服务器能在客户端保持一个开放的HTTP连接,直到有新的数据需要推送或者连接超时。这极大地提高了实时性,降低了延迟,适用于股票...

    dwr comet的MyEclipse工程,可直接运行

    而DWR通过AJAX(Asynchronous JavaScript and XML)实现了一种反向通道,使得服务器能够主动推送数据到客户端,提高了交互性和实时性。 **Comet技术** Comet是一种Web编程模式,用于创建持久的、双向的浏览器与...

Global site tag (gtag.js) - Google Analytics