服务热线:13616026886

技术文档 欢迎使用技术文档,我们为你提供从新手到专业开发者的所有资源,你也可以通过它日益精进

位置:首页 > 技术文档 > 专题栏目 > WEB2.0新技术 > 查看文档

使用jetty和dwr实现comet web应用程序

  受异步服务器端事件驱动的 ajax 应用程序实现较为困难,并且难于扩展。philip mccarthy介绍了一种行之有效的方法:结合使用 comet 模式(将数据推到客户机)和 jetty 6 的 continuations api(将 comet 应用程序扩展到大量客户机中)。您可以方便地在 direct web remoting (dwr) 2 中将 comet 和 continuations 与 reverse ajax 技术结合使用……

  作为一种广泛使用的 web 应用程序开发技术,ajax 牢固确立了自己的地位,随之而来的是一些通用 ajax 使用模式。例如,ajax 经常用于对用户输入作出响应,然后使用从服务器获得的新数据修改页面的部分内容。但是,有时 web 应用程序的用户界面需要进行更新以响应服务器端发生的异步事件,而不需要用户操作 ―― 例如,显示到达 ajax 聊天应用程序的新消息,或者在文本编辑器中显示来自另一个用户的改变。由于只能由浏览器建立 web 浏览器和服务器之间的 http 连接,服务器无法在改动发生时将变化 “推送” 给浏览器。

  ajax 应用程序可以使用两种基本的方法解决这一问题:一种方法是浏览器每隔若干秒时间向服务器发出轮询以进行更新,另一种方法是服务器始终打开与浏览器的连接并在数据可用时发送给浏览器。长期连接技术被称为 comet。本文将展示如何结合使用 jetty servlet 引擎和 dwr 简捷有效地实现一个 comet web 应用程序。

  为什么使用 comet?

  轮询方法的主要缺点是:当扩展到更多客户机时,将生成大量的通信量。每个客户机必须定期访问服务器以检查更新,这为服务器资源添加了更多负荷。最坏的一种情况是对不频繁发生更新的应用程序使用轮询,例如一种 ajax 邮件 inbox。在这种情况下,相当数量的客户机轮询是没有必要的,服务器对这些轮询的回答只会是 “没有产生新数据”。虽然可以通过增加轮询的时间间隔来减轻服务器负荷,但是这种方法会产生不良后果,即延迟客户机对服务器事件的感知。当然,很多应用程序可以实现某种权衡,从而获得可接受的轮询方法。

  尽管如此,吸引人们使用 comet 策略的其中一个优点是其显而易见的高效性。客户机不会像使用轮询方法那样生成烦人的通信量,并且事件发生后可立即发布给客户机。但是保持长期连接处于打开状态也会消耗服务器资源。当等待状态的 servlet 持有一个持久性请求时,该 servlet 会独占一个线程。这将限制 comet 对传统 servlet 引擎的可伸缩性,因为客户机的数量会很快超过服务器栈能有效处理的线程数量。

  jetty 6 有何不同

  jetty 6 的目的是扩展大量同步连接,使用 java 语言的非阻塞 i/o(java.nio)库并使用一个经过优化的输出缓冲架构。jetty 还为处理长期连接提供了一些技巧:该特性称为 continuations。我将使用一个简单的 servlet 对 continuations 进行演示,这个 servlet 将接受请求,等待处理,然后发送响应。接下来,我将展示当客户机数量超过服务器提供的处理线程后发生的状况。最后,我将使用 continuations 重新实现 servlet,您将了解 continuations 在其中扮演的角色。

  为了便于理解下面的示例,我将把 jetty servlet 引擎限制在一个单请求处理线程。清单 1 展示了 jetty.xml 中的相关配置。我实际上需要在 threadpool 使用三个线程:jetty 服务器本身使用一个线程,另一线程运行 http 连接器,侦听到来的请求。第三个线程执行 servlet 代码。

  清单 1. 单个 servlet 线程的 jetty 配置

<?xml version="1.0"?>
<!doctype configure public "-//mort bay consulting//dtd configure//en"
  "http://jetty.mortbay.org/configure.dtd">
<configure id="server" class="org.mortbay.jetty.server">
    <set name="threadpool">
      <new class="org.mortbay.thread.boundedthreadpool">
        <set name="minthreads">3</set>
        <set name="lowthreads">0</set>
        <set name="maxthreads">3</set>
      </new>
    </set>
</configure>

  接下来,为了模拟对异步事件的等待,清单 2 展示了 blockingservlet 的 service() 方法,该方法将使用 thread.sleep() 调用在线程结束之前暂停 2000 毫秒的时间。它还在执行开始和结束时输出系统时间。为了区别输出和不同的请求,还将作为标识符的请求参数记录在日志中。

  清单 2. blockingservlet

public class blockingservlet extends httpservlet {

  public void service(httpservletrequest req, httpservletresponse res)
                                              throws java.io.ioexception {

    string reqid = req.getparameter("id");
   
    res.setcontenttype("text/plain");
    res.getwriter().println("request: "+reqid+"\tstart:\t" + new date());
    res.getwriter().flush();

    try {
      thread.sleep(2000);
    } catch (exception e) {}
   
    res.getwriter().println("request: "+reqid+"\tend:\t" + new date());
  }
}

  现在可以观察到 servlet 响应一些同步请求的行为。清单 3 展示了控制台输出,五个使用 lynx 的并行请求。命令行启动五个 lynx 进程,将标识序号附加在请求 url 的后面。

  清单 3. 对 blockingservlet 并发请求的输出

$ for i in 'seq 1 5'  ; do lynx -dump localhost:8080/blocking?id=$i &  done
request: 1      start:  sun jul 01 12:32:29 bst 2007
request: 1      end:    sun jul 01 12:32:31 bst 2007

request: 2      start:  sun jul 01 12:32:31 bst 2007
request: 2      end:    sun jul 01 12:32:33 bst 2007

request: 3      start:  sun jul 01 12:32:33 bst 2007
request: 3      end:    sun jul 01 12:32:35 bst 2007

request: 4      start:  sun jul 01 12:32:35 bst 2007
request: 4      end:    sun jul 01 12:32:37 bst 2007

request: 5      start:  sun jul 01 12:32:37 bst 2007
request: 5      end:    sun jul 01 12:32:39 bst 2007

  ……

  

扫描关注微信公众号