夜鹰教程网-程序员的加油站
 当前位置:文章中心 >> Java_Jsp_Jdk_eclipse_tomcat
使用 Java 实现 Comet 风格的 Web 应用2
夜鹰教程网 来源:www.yyjcw.com 日期:2016-11-22 20:28:06
探索 Comet 开发的不同实现。看看 Jetty 和 Tomcat 之类的流行 Java? Web 服务器如何支持 Comet 应用程序,并了解如何为不同的服务器编程。最后,了解 Java 中有关 Comet 的标准化建议,这些建议是即将到来的 Servlet 3.0 和 JavaEE 6 规范的一部分。

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 实例调用。通过检查 CometEventeventType,可以判断正处在生命周期的哪个阶段。当请求第一次传入时,即发生 BEGIN 事件。READ 事件表明数据正在被发送,只有当请求为 POST 时才需要该事件。遇到 ENDERROR 事件时,请求终止。

在清单 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 函数。

现在,我们看过了一个简单的 Comet 应用程序在 Tomcat 上是什么样的。有两件与 Tomcat 密切相关的事情要做:一是配置它的连接器,二是在 servlet 中实现一个特定于 Tomcat 的接口。您可能想知道,将该代码 “移植” 到 Jetty 有多大难度。接下来我们就来看看这个问题。

Jetty 和 Comet

Jetty 服务器使用稍微不同的技术来支持 Comet 的可伸缩的实现。Jetty 支持被称作 continuations 的编程结构。其思想很简单。请求先被暂停,然后在将来的某个时间点再继续。规定时间到期,或者某种有意义的事件发生,都可能导致请求继续。当请求被暂停时,它的线程被释放。

可以使用 Jetty 的 org.mortbay.util.ajax.ContinuationSupport 类为任何 HttpServletRequest 创建 org.mortbay.util.ajax.Continuation 的一个实例。这种方法与 Comet 有很大的不同。但是,continuations 可用于实现逻辑上等效的 Comet。清单 6 显示清单 2 中的 weather servlet “移植” 到 Jetty 后的代码。


清单 6. Jetty Comet servlet
public class JettyWeatherServlet extends HttpServlet {
    private MessageSender messageSender = null;
    private static final Integer TIMEOUT = 5 * 1000;
    public void begin(HttpServletRequest request, HttpServletResponse response) 
throws IOException, ServletException {
        request.setAttribute("org.apache.tomcat.comet", Boolean.TRUE);
        request.setAttribute("org.apache.tomcat.comet.timeout", TIMEOUT);
        messageSender.setConnection(response);
        Weatherman weatherman = new Weatherman(95118, 32408);
        new Thread(weatherman).start();
    }
    public void end(HttpServletRequest request, HttpServletResponse response) 
throws IOException, ServletException {
        synchronized (request) {
            request.removeAttribute("org.apache.tomcat.comet");
            Continuation continuation = ContinuationSupport.getContinuation
(request, request);
            if (continuation.isPending()) {
                continuation.resume();
            }
        }
    }
    public void error(HttpServletRequest request, HttpServletResponse response) 
throws IOException, ServletException {
        end(request, response);
    }
    public boolean read(HttpServletRequest request, HttpServletResponse response) 
throws IOException, ServletException {
        throw new UnsupportedOperationException();
    }
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) 
throws IOException, ServletException {
        synchronized (request) {
            Continuation continuation = ContinuationSupport.getContinuation
(request, request);
            if (!continuation.isPending()) {
                begin(request, response);
            }
            Integer timeout = (Integer) request.getAttribute
("org.apache.tomcat.comet.timeout");
            boolean resumed = continuation.suspend(timeout == null ? 10000 : 
timeout.intValue());

            if (!resumed) {
                error(request, response);
            }
        }
    }
    public void setTimeout(HttpServletRequest request, HttpServletResponse response, 
int timeout) throws IOException, ServletException,
            UnsupportedOperationException {
        request.setAttribute("org.apache.tomcat.comet.timeout", new Integer(timeout));
    }
}

这里最需要注意的是,该结构与 Tomcat 版本的代码非常类似。beginreadenderror 方法都与 Tomcat 中相同的事件匹配。该 servlet 的 service 方法被覆盖为在请求第一次进入时创建一个 continuation 并暂停该请求,直到超时时间已到,或者发生导致它重新开始的事件。上面没有显示 initdestroy 方法,因为它们与 Tomcat 版本是一样的。该 servlet 使用与 Tomcat 相同的 MessageSender。因此不需要修改。注意 begin 方法如何创建 Weatherman 实例。对这个类的使用与 Tomcat 版本中也是完全相同的。甚至客户机代码也是一样的。只有 servlet 有更改。虽然 servlet 的变化比较大,但是与 Tomcat 中的事件模型仍是一一对应的。

希望这足以鼓舞人心。虽然完全相同的代码不能同时在 Tomcat 和 Jetty 中运行,但是它是非常相似的。当然,JavaEE 吸引人的一点是可移植性。大多数在 Tomcat 中运行的代码,无需修改就可以在 Jetty 中运行,反之亦然。因此,毫不奇怪,下一个版本的 Java Servlet 规范包括异步请求处理(即 Comet 背后的底层技术)的标准化。 我们来看看这个规范:Servlet 3.0 规范。

复制链接 网友评论 收藏本文 关闭此页
上一条: 使用 Java 实现 Comet 风格的 Web…  下一条: 使用 Java 实现 Comet 风格的 Web 应用…
夜鹰教程网成立于2008年,目前已经运营了将近 13 年,发布了大量关于 html5/css3/C#/asp.net/java/python/nodejs/mongodb/sql server/android/javascript/mysql/mvc/easyui/vue/echarts原创教程。 我们一直都在坚持的是:认证负责、一丝不苟、以工匠的精神来打磨每一套教程,让读者感受到作者的用心。我们默默投入的时间,确保每一套教程都是一件作品,而不是呆板的文字和视频! 目前我们推出在线辅导班试运营,模式为一对一辅导,教学工具为QQ。我们的辅导学科包括 java 、android原生开发、webapp开发、商城开发、C#和asp.net开发,winform和物联网开发、web前端开发,但不仅限于此。 普通班针对的是国内学员,例如想打好基础的大学生、想转行的有志青年、想深入学习的程序员、想开发软件的初学者或者业余爱好者等。 就业办针对即将毕业上岗的大四学生,或者打算转行的初级开发工程师。 留学生班针对的是在欧美、加拿大、澳洲、日本、韩国、新加坡等地留学的中国学子,目的是让大家熟练地掌握编程技能,按时完成老师布置的作业,并能顺利地通过考试。 详细咨询QQ:1416759661   夜鹰教程网  基于角色的权限管理系统(c-s/b-s)。
  夜鹰教程网  基于nodejs的聊天室开发视频教程
  夜鹰教程网  Git分布式版本管理视频教程
  夜鹰教程网  MVC+EasyUI视频教程
  夜鹰教程网  在线考试系统视频教程
  夜鹰教程网  MongoDB视频教程。
  夜鹰教程网 Canvas视频教程
  夜鹰教程网 报表开发视频教程
  热点推荐
初学教程:Java Socket网络编程浅…
Java随机数总结
java读取文本文件内容并获取文件大…
字节流和字符流Java
java常用图片读写程序
怎样成为一个优秀的Java程序员
C++程序员转Java容易吗?难点在哪…
思科培训与华为培训在教材和内容方…
Java注释的使用和定义
使用 Java 实现 Comet 风格的 Web…
利用Java实现zip压缩/解压缩
Java Web三层架构的配置详解
Java Socket编程之我见
使用 Java 实现 Comet 风格的 Web…
两种J2ME网络编程的方法之一
  最近更新
short、int、long、float、double…
二进制(原码、反码、补码)
python时间模块详解
java 解决split分割空值不能得到的…
如何打开eclipse安卓开发代码提示…
php实现大文件断点续传功能
java实现断点续传功能
Windows Server 2016新功能
MyEclipse 8.5 汉化方法
jquery实现无刷新分页
什么是JSON?
什么是回调函数
jQuery 1.7下载
jquery加载XML文档
什么是MVC(三层架构)

关于我们 | 网站建设 | 技术辅导 | 常见问题 | 联系我们 | 友情链接

夜鹰教程网 版权所有 www.yyjcw.com All rights reserved 备案号:蜀ICP备08011740号3