`
yangxianjiangg
  • 浏览: 61289 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

使用Java实现Comet风格的Web应用

阅读更多

探索 Comet 开发的不同实现。看看 Jetty 和 Tomcat 之类的流行 Java™ Web 服务器如何支持 Comet 应用程序,并了解如何为不同的服务器编程。最后,了解 Java 中有关 Comet 的标准化建议,这些建议是即将到来的 Servlet 3.0 和 JavaEE 6 规范的一部分。
<!-- START RESERVED FOR FUTURE USE INCLUDE FILES--><!-- include java script once we verify teams wants to use this and it will work on dbcs and cyrillic characters --> <!-- END RESERVED FOR FUTURE USE INCLUDE FILES-->

开始

在本文中,我将展示如何使用各种不同的 Java 技术构建一些简单的 Comet 风格的 Web 应用程序。读者对 Java servlet、Ajax 和 JavaScript 应该有一定的了解。我们将考察 Tomcat 和 Jetty 中一些支持 Comet 的特性,因此需要使用这两个产品的最新版本。本文使用 Tomcat 6.0.14 和 Jetty 6.1.14。另外还需要一个支持 Java 5 或更高版本的 JDK。本文使用 JDK 1.5.0-16。此外还需要看看 Jetty 7 的预发布版,因为它实现了 Servlet 3.0 规范,我们将在本文中研究该规范。请参阅 参考资料 ,找到下载链接。



理解 Comet

您可能已经听说过 Comet,因为它最近受到了一定的关注。Comet 有时也称反向 Ajax 或服务器端推技术(server-side push)。其思想很简单:将数据直接从服务器推到浏览器,而不必等到浏览器请求数据。听起来简单,但是如果熟悉 Web 应用程序,尤其是 HTTP 协议,那么您就会知道,这绝不简单。实现 Comet 风格的 Web 应用程序,同时保证在浏览器和服务器上的可伸缩性,这只是在最近几年才成为可能。在本文的后面,我们将看看一些流行的 Java Web 服务器如何支持可伸缩的 Comet 架构,但首先我们来看看为什么要创建 Comet 应用程序,以及用于实现它们的常见设计模式。

使用 Comet 的动机

HTTP 协议的成功毋庸置疑。它是 Internet 上大部分信息交换的基础。然而,它也有一些局限性。特别是,它是无状态、单向的协议。请求被发送到 Web 服务器,服务器处理请求并发回一个响应 — 仅此而已。请求必须由客户机发出,而服务器则只能在对请求的响应中发送数据。这至少会影响很多类型的 Web 应用程序的实用性。典型的例子就是聊天程序。另外还有一些例子,例如比赛的比分、股票行情或电子邮件程序。

HTTP 的这些局限性也是它取得一定成功的原因。请求/响应周期使它成为了经典的模型,即每个连接使用一个线程。只要能够快速为请求提供服务,这种方法就有巨大的可伸缩性。每秒钟可以处理大量的请求,只需使用少量的服务器就可以处理很大数量的用户。对于很多经典的 Web 应用程序,例如内容管理系统、搜索应用程序和电子商务站点等等而言,这非常适合。在以上任何一种 Web 应用程序中,服务器提供用户请求的数据,然后关闭连接,并释放那个线程,使之可以为其他请求服务。如果提供初始数据之后仍可能存在交互,那么将连接保持为打开状态,因此线程就不能释放出来,服务器也就不能为很多用户服务。

但是,如果想在对请求做出响应并发送初始数据之后,仍然保持与用户的交互呢?在 Web 早期,这一点常使用 meta 刷新实现。这将自动指示浏览器在指定秒数之后重新装载页面,从而支持简陋的轮询(polling)。这不仅是一种糟糕的用户体验,而且通常效率非常低下。如果没有新的数据要显示在页面上呢?这时不得不重新呈现同样的页面。如果对页面的更改很少,并且页面的大部分没有变化呢?同样,不管是否有必要,都得重新请求和获取页面上的一切内容。

Ajax 的发明和流行改变了上述状况。现在,服务器可以异步通信,因此不必重新请求整个页面。现在可以进行增量式的更新。只需使用 XMLHttpRequest 轮询服务器。这项技术通常被称作 Comet。这项技术存在一些变体,每种变体具有不同的性能和可伸缩性。我们来看看这些不同风格的 Comet。

Comet 风格

Ajax 的出现使 Comet 成为可能。HTTP 的单向性质可以有效地加以规避。实际上有一些不同的方法可以绕过这一点。您可能已经猜到,支持 Comet 的最容易的方式是轮询(poll) 。使用 XMLHttpRequest 向服务器发出调用,返回后,等待一段固定的时间(通常使用 JavaScript 的 setTimeout 函数),然后再次调用。这是一项非常常见的技术。例如,大多数 webmail 应用程序就是通过这种技术在电子邮件到达时显示电子邮件的。

这项技术有优点也有缺点。在这种情况下,您期望快速返回响应,就像任何其他 Ajax 请求一样。在请求之间必须有一段暂停。否则,连续不断的请求会冲垮服务器,并且这种情况下显然不具有可伸缩性。这段暂停使应用程序产生一个延时。暂停的时间越长,服务器上的新数据就需要越多的时间才能到达客户机。如果缩短暂停时间,又将重新面临冲垮服务器的风险。但是另一方面,这显然是最简单的实现 Comet 的方式。

现在应该指出,很多人认为轮询并不属于 Comet。相反,他们认为 Comet 是对轮询的局限性的一个解决方案。最常见的 “真正的” Comet 技术是轮询的一种变体,即长轮询(long polling)。轮询与长轮询之间的主要区别在于服务器花多长的时间作出响应。长轮询通常将连接保持一段较长的时间 — 通常是数秒钟,但是也可能是一分钟甚至更长。当服务器上发生某个事件时,响应被发送并随即关闭,轮询立即重新开始。

长轮询相对于一般轮询的优点在于,数据一旦可用,便立即从服务器发送到客户机。请求可能等待较长的时间,期间没有任何数据返回,但是一旦有了新的数据,它将立即被发送到客户机。因此没有延时。如果您使用过基于 Web 的聊天程序,或者声称 “实时” 的任何程序,那么它很可能就是使用了这种技术。

长轮询有一种变体,这是第三种风格的 Comet。这通常被称为流(streaming)。按照这种风格,服务器将数据推回客户机,但是不关闭连接。连接将一直保持开启,直到过期,并导致重新发出请求。XMLHttpRequest 规范表明,可以检查 readyState 的值是否为 3 或 Receiving(而不是 4 或 Loaded),并获取正从服务器 “流出” 的数据。和长轮询一样,这种方式也没有延时。当服务器上的数据就绪时,该数据被发送到客户机。这种方式的另一个优点是可以大大减少发送到服务器的请求,从而避免了与设置服务器连接相关的开销和延时。不幸的是,XMLHttpRequest 在不同的浏览器中有很多不同的实现。这项技术只能在较新版本的 Mozilla Firefox 中可靠地使用。对于 Internet Explorer 或 Safari,仍需使用长轮询。

至此,您可能会想,长轮询和流都有一个很大的问题。请求需要在服务器上存在一段较长的时间。这打破了每个请求使用一个线程的模型,因为用于一个请求的线程一直没有被释放。更糟糕的是,除非要发回数据,否则该线程一直处于空闲状态。这显然不具有可伸缩性。幸运的是,现代 Java Web 服务器有很多方式可以解决这个问题。




Java 中的 Comet

现在有很多 Web 服务器是用 Java 构建的。一个原因是 Java 有一个丰富的本地线程模型。因此实现典型的每个连接一个线程的模型便非常简单。该模型对于 Comet 不大适用,但是,Java 对此同样有解决的办法。为了有效地处理 Comet,需要非阻塞 IO,Java 通过它的 NIO 库提供非阻塞 IO。两种最流行的开源服务器 Apache Tomcat 和 Jetty 都利用 NIO 增加非阻塞 IO,从而支持 Comet。然而,这两种服务器中的实现却各不相同。我们来看看 Tomcat 和 Jetty 对 Comet 的支持。

Tomcat 和 Comet

对于 Apache Tomcat,要使用 Comet,主要需要做两件事。首先,需要对 Tomcat 的配置文件 server.xml 稍作修改。默认情况下启用的是更典型的同步 IO 连接器。现在只需将它切换成异步版本,如清单 1 所示。


清单 1. 修改 Tomcat 的 server.xml

<!-- This is the usual Connector, comment it out and add the NIO one --> 
   <!-- Connector URIEncoding="utf-8" connectionTimeout="20000" port="8084" 
protocol="HTTP/1.1" redirectPort="8443"/ --> 
<Connector connectionTimeout="20000" port="8080" protocol="org.apache. 
coyote.http11.Http11NioProtocol" redirectPort="8443"/> 

 




这使 Tomcat 可以处理更多的并发连接,但需要说明的是,其中大多数连接有很多时间都处于空闲状态。利用这一点的最容易的方式是创建一个实现 org.apache.catalina.CometProcessor 接口的 servlet。这显然是 Tomcat 特有的一个接口。清单 2 显示了一个这样的例子。


清单 2. Tomcat Comet servlet

public class TomcatWeatherServlet extends HttpServlet implements CometProcessor { 

    private MessageSender messageSender = null; 
    private static final Integer TIMEOUT = 60 * 1000; 

    @Override 
    public void destroy() { 
        messageSender.stop(); 
        messageSender = null; 

    } 

    @Override 
    public void init() throws ServletException { 
        messageSender = new MessageSender(); 
        Thread messageSenderThread = 
                new Thread(messageSender, "MessageSender[" + getServletContext() 
.getContextPath() + "]"); 
        messageSenderThread.setDaemon(true); 
        messageSenderThread.start(); 

    } 

    public void event(final CometEvent event) throws IOException, ServletException { 
        HttpServletRequest request = event.getHttpServletRequest(); 
        HttpServletResponse response = event.getHttpServletResponse(); 
        if (event.getEventType() == CometEvent.EventType.BEGIN) { 
            request.setAttribute("org.apache.tomcat.comet.timeout", TIMEOUT); 
            log("Begin for session: " + request.getSession(true).getId()); 
            messageSender.setConnection(response); 
            Weatherman weatherman = new Weatherman(95118, 32408); 
            new Thread(weatherman).start(); 
        } else if (event.getEventType() == CometEvent.EventType.ERROR) { 
            log("Error for session: " + request.getSession(true).getId()); 
            event.close(); 
        } else if (event.getEventType() == CometEvent.EventType.END) { 
            log("End for session: " + request.getSession(true).getId()); 
            event.close(); 
        } else if (event.getEventType() == CometEvent.EventType.READ) { 
            throw new UnsupportedOperationException("This servlet does not accept 
data"); 
        } 

    } 
} 

 



CometProcessor 接口要求实现 event 方法。这是用于 Comet 交互的一个生命周期方法。Tomcat 将使用不同的 CometEvent 实例调用。通过检查 CometEvent 的 eventType ,可以判断正处在生命周期的哪个阶段。当请求第一次传入时,即发生 BEGIN 事件。READ 事件表明数据正在被发送,只有当请求为 POST 时才需要该事件。遇到 END 或 ERROR 事件时,请求终止。

在清单 2 的例子中,servlet 使用一个 MessageSender 类发送数据。这个类的实例是在 servlet 的 init 方法中在其自身的线程中创建,并在 servlet 的 destroy 方法中销毁的。清单 3 显示了 MessageSender 。


清单 3. MessageSender

private class MessageSender implements Runnable { 

    protected boolean running = true; 
    protected final ArrayList<String> messages = new ArrayList<String>(); 
    private ServletResponse connection; 
    private synchronized void setConnection(ServletResponse connection){ 
        this.connection = connection; 
        notify(); 
    } 
    public void send(String message) { 
        synchronized (messages) { 
            messages.add(message); 
            log("Message added #messages=" + messages.size()); 
            messages.notify(); 
        } 
    } 
    public void run() { 
        while (running) { 
            if (messages.size() == 0) { 
                try { 
                    synchronized (messages) { 
                        messages.wait(); 
                    } 
                } catch (InterruptedException e) { 
                    // Ignore 
                } 
            } 
            String[] pendingMessages = null; 
            synchronized (messages) { 
                pendingMessages = messages.toArray(new String[0]); 
                messages.clear(); 
            } 
            try { 
                if (connection == null){ 
                    try{ 
                        synchronized(this){ 
                            wait(); 
                        } 
                    } catch (InterruptedException e){ 
                        // Ignore 
                    } 
                } 
                PrintWriter writer = connection.getWriter(); 
                for (int j = 0; j < pendingMessages.length; j++) { 
                    final String forecast = pendingMessages[j] + "<br>"; 
                    writer.println(forecast); 
                    log("Writing:" + forecast); 
                } 
                writer.flush(); 
                writer.close(); 
                connection = null; 
                log("Closing connection"); 
            } catch (IOException e) { 
                log("IOExeption sending message", e); 
            } 
        } 
    } 
} 

 



这个类基本上是样板代码,与 Comet 没有直接的关系。但是,有两点要注意。这个类含有一个 ServletResponse 对象。回头看看清单 2 中的 event 方法,当事件为 BEGIN 时,response 对象被传入到 MessageSender 中。在 MessageSender 的 run 方法中,它使用 ServletResponse 将数据发送回客户机。注意,一旦发送完所有排队等待的消息后,它将关闭连接。这样就实现了长轮询。如果要实现流风格的 Comet,那么需要使连接保持开启,但是仍然刷新数据。

回头看清单 2 可以发现,其中创建了一个 Weatherman 类。正是这个类使用 MessageSender 将数据发送回客户机。这个类使用 Yahoo RSS feed 获得不同地区的天气信息,并将该信息发送到客户机。这是一个特别设计的例子,用于模拟以异步方式发送数据的数据源。清单 4 显示了它的代码。


清单 4. Weatherman

private class Weatherman implements Runnable{ 

    private final List<URL> zipCodes; 
    private final String YAHOO_WEATHER = "http://weather.yahooapis.com/forecastrss?p="; 

    public Weatherman(Integer... zips) { 
        zipCodes = new ArrayList<URL>(zips.length); 
        for (Integer zip : zips) { 
            try { 
                zipCodes.add(new URL(YAHOO_WEATHER + zip)); 
            } catch (Exception e) { 
                // dont add it if it sucks 
            } 
        } 
    } 

   public void run() { 
       int i = 0; 
       while (i >= 0) { 
           int j = i % zipCodes.size(); 
           SyndFeedInput input = new SyndFeedInput(); 
           try { 
               SyndFeed feed = input.build(new InputStreamReader(zipCodes.get(j) 
.openStream())); 
               SyndEntry entry = (SyndEntry) feed.getEntries().get(0); 
               messageSender.send(entryToHtml(entry)); 
               Thread.sleep(30000L); 
           } catch (Exception e) { 
               // just eat it, eat it 
           } 
           i++; 
       } 
   } 

    private String entryToHtml(SyndEntry entry){ 
        StringBuilder html = new StringBuilder("<h2>"); 
        html.append(entry.getTitle()); 
        html.append("</h2>"); 
        html.append(entry.getDescription().getValue()); 
        return html.toString(); 
    } 
} 

 




这个类使用 Project Rome 库解析来自 Yahoo Weather 的 RSS feed。如果需要生成或使用 RSS 或 Atom feed,这是一个非常有用的库。此外,这个代码中只有一个地方值得注意,那就是它产生另一个线程,用于每过 30 秒钟发送一次天气数据。最后,我们再看一个地方:使用该 servlet 的客户机代码。在这种情况下,一个简单的 JSP 加上少量的 JavaScript 就足够了。清单 5 显示了该代码。


清单 5. 客户机 Comet 代码

<%@page contentType="text/html" pageEncoding="UTF-8"%> 
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" 
   "http://www.w3.org/TR/html4/loose.dtd"> 

<html> 
    <head> 
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> 
        <title>Comet Weather</title> 
        <SCRIPT TYPE="text/javascript"> 
            function go(){ 
                var url = "http://localhost:8484/WeatherServer/Weather" 
                var request =  new XMLHttpRequest(); 
                request.open("GET", url, true); 
                request.setRequestHeader("Content-Type","application/x-javascript;"); 
                request.onreadystatechange = function() { 
                    if (request.readyState == 4) { 
                        if (request.status == 200){ 
                            if (request.responseText) { 
                                document.getElementById("forecasts").innerHTML = 
request.responseText; 
                            } 
                        } 
                        go(); 
                    } 
                }; 
                request.send(null); 
            } 
        </SCRIPT> 
    </head> 
    <body> 
        <h1>Rapid Fire Weather</h1> 
        <input type="button" onclick="go()" value="Go!"></input> 
        <div id="forecasts"></div> 
    </body> 
</html> 

 



该代码只是在用户单击 Go 按钮时开始长轮询。注意,它直接使用 XMLHttpRequest 对象,所以这在 Internet Explorer 6 中将不能工作。您可能需要使用一个 Ajax 库解决浏览器差异问题。除此之外,惟一需要注意的是回调函数,或者为请求的 onreadystatechange 函数创建的闭包。该函数粘贴来自服务器的新的数据,然后重新调用 go 函数。

 

分享到:
评论

相关推荐

    Java 实现 Comet 长连接,服务器主动发送消息给客户端

    通过理解和实践 Java 实现的 Comet 长连接,开发者可以构建出高效、实时的 Web 应用,提升用户体验。同时,随着 WebSocket 的普及,它逐渐成为现代实时通信的首选技术,但 Comet 仍然是在某些场景下不可或缺的解决...

    java-comet

    Java-Comet是一种在Java...总结,Java-Comet技术是Java环境中实现服务器向客户端实时推送数据的一种方法,尤其适用于需要实时交互的Web应用。通过理解和掌握Java-Comet,开发者可以创建出更高效、响应更快的Web服务。

    使用Java实现类似Comet风格的web app

    要使用 Java 实现类似 Comet 风格的 Web 应用,你需要对以下几个关键知识点有所了解: 1. **HTTP 协议的限制**: - HTTP 协议是基于请求-响应模型的,意味着客户端(浏览器)需要先发送请求,服务器才能响应。 - ...

    java comet服务器推送使用步骤

    本文将详细介绍如何使用Comet4J框架来实现Java Comet服务器推送。 #### 二、Comet4J简介 Comet4J是一个基于Java的轻量级Comet框架,它能够帮助开发者轻松地实现实时数据推送功能。Comet4J支持多种服务器环境,并且...

    web推送 comet技术

    Comet4J是一个实现了Comet技术的Java框架,它简化了在Java应用中实现服务器推送的复杂性。使用Comet4J,开发者可以创建服务端的推送通道,通过这些通道,服务器能够将数据高效地推送到多个客户端。Comet4J提供了多种...

    面向 Java 开发人员的 Ajax: 使用 Jetty 和 Direct Web Remoting 编写可扩展的 Comet 应用程序

    面向Java开发人员的Ajax技术...总结,使用Jetty和DWR来构建Comet应用,Java开发者可以实现双向通信,提供更具互动性的Web体验。通过学习和实践,你可以创建出高效、可扩展的实时应用程序,满足各种现代Web场景的需求。

    ssm.rar_comet_java comet_java comet推送_聊天 JAVA SSM

    在这个"ssm.rar_comet_java"压缩包中,我们聚焦于Comet技术在Java环境下的应用,特别是用于实现服务器推送功能,如聊天应用。 Comet是一种Web实时通信技术,它通过持久化HTTP连接使得服务器可以主动向客户端推送...

    java 使用 comet4j 主动向客户端推送信息 简单例子

    Java 使用 Comet4j 主动向客户端推送信息是一个常见的实时通信技术,主要应用于构建WebSocket或长轮询等实时交互的应用场景。Comet4j 是一个基于 HTTP 长连接的服务器端框架,它允许服务器端主动向客户端推送数据,...

    Comet Web 应用

    Comet技术,全称为“Comet Web应用”,是一种在Web开发中实现服务器向客户端推送数据的技术,也被称为反向Ajax或服务器端推送。这种技术打破了传统的HTTP请求/响应模式,使得服务器可以在客户端未发起新请求时主动...

    Asp.net Comet(Web)技术

    总的来说,Comet技术为Web应用程序提供了强大的实时推送能力,虽然它并非HTTP协议原生支持的功能,但通过巧妙的编程技巧,可以在Asp.net环境下实现高效、可靠的服务器到客户端的数据推送。在开发实时Web应用时,理解...

    dwr_comet_im.rar_DEMO_comet.tld_java comet

    综上所述,这个压缩包提供了DWR实现Comet技术的一个实践示例,可以帮助开发者理解如何在Java Web应用中构建即时通讯功能。通过学习和分析这个DEMO,我们可以掌握如何配置DWR,使用comet.tld标签库,以及编写客户端和...

    浅析Comet技术在Java Web实时系统开发中的应用.pdf

    在Java Web实时系统开发中,Comet技术可以与AJAX技术结合使用,实现实时的数据更新和推送。AJAX技术可以异步地请求服务器端的数据,并将数据更新到客户端,而Comet技术可以实时地将更新的信息传送到客户端。 因此,...

    DWR+JAVA进行web消息推送dwr-comet.zip

    在本文中,我们将深入探讨如何使用DWR(Direct Web Remoting)与Java技术结合实现Web消息推送功能。DWR是一种JavaScript库,它允许在浏览器和服务器之间进行实时、双向通信,为Web应用提供了类似AJAX的功能,但更加...

    Servlet3.0 异步处理 页面推送 Comet 实例

    Comet是一种Web应用程序架构,用于创建持久连接,允许服务器向客户端(通常是浏览器)实时推送数据。在传统的HTTP协议中,服务器只能在客户端发起请求时发送数据,而Comet通过长时间保持一个HTTP连接开放,使得...

    javaweb实现后台向前台的消息推送 comet4j

    本示例介绍的是如何使用Comet4j这个第三方库来实现后台到前台的消息推送功能。Comet4j是一个专门用于JavaWeb应用的长连接通讯框架,它简化了基于Comet技术的实时通信实现。 首先,我们要理解Comet技术。Comet是一种...

    C#Web即时通讯Comet框架

    "我的示例"(MyDemo)可能是包含在压缩包中的一个实际应用案例,展示了如何使用这个Comet框架来实现Web即时通讯。这个例子可能包括服务器端代码和客户端JavaScript代码,通过创建连接、发送和接收消息,以及处理推送...

    基于Comet属性同步的Java Web实时进度条研究.pdf

    本文研究了基于Comet属性同步的Java Web实时进度条的实现方案。该方案通过结合Comet技术和Java的反射技术,实现了Java Web实时进度条的处理方式。该方案的优势在于纯Java实现、代码简单、无需安装浏览器插件,并具有...

    java 使用 comet4j 主动向客户端推送GPS信息的例子

    基于服务器推送框架 Comet4J ,后台模拟实时生成 gps 坐标信息然后再推送到前端页面显示。...这是客户端主动向服务器发起请求的方式,而采用 comet4j框架来实现正好相反,是服务器主动向客户端来推送消息。

    comet demo 向客户端推送例子

    Tomcat 6.0是Apache软件基金会开发的开源Servlet容器,支持各种Java Web应用的部署,包括Comet技术。 首先,Comet的核心在于保持一个开放的HTTP连接,直到有新的数据需要推送或者连接超时。这种连接状态被称为“挂...

Global site tag (gtag.js) - Google Analytics