Java 实现 Comet 风格的 Web 应用的分析

         技术文档链接地址以下:javascript

       http://www.ibm.com/developerworks/cn/web/wa-cometjava/html

      Comet的别称反向 Ajax 或服务器端推技术(server-side push),我理解的定义是:使用http长链接从后台主动推送数据到前台的技术。java

      如今ajax很是流行,他解决了网页所有刷新才能更新内容的方式,但它在一些使用场景中并不适用,好比,股票走势的图实时更新,我的通知的实时更新。这些须要实时更新的模块使用ajax的定时刷新方式,体验不好,当时我就想有没有一种方式,若是后台数据发生变化主动将变化信息传给前台相应的模块中去呢,我google之,发现了Comet。它的使用特色是,一般的ajax请求当数据从后台返回数据后与后台的链接就会关闭,而Comet则一直创建着链接,后台程序则保存了全部请求者的HttpServletResponse,若是后台数据发生变化就会经过这个链接传到前台,这就会出现一个问题,若是100个用户须要推送数据,后台代码必然须要保存着100个用户的HttpServletResponse,若是是1000个用户甚至更多呢,这是一个问题,还有一个问题就是虽然前台刷新的次数少了,但后台须要处理的就相应增多,这样是否符合最优原则,第三个问题是他依赖于运行程序的容器。tomcat,glassfish等的处理方式有些区别,至于Comet是否值得使用还得经过实验得出结论。jquery

    我用百度的echarts作了一个简单的Comet长链接例子,在后台Java程序中我设置了一个定时器,后台会定时向前台推送数据,实现了基本功能。长链接不关闭对浏览器来讲也是很大的开销。下面是例子的代码。web

(1) 接受后台请求的servletajax

package tbktest;

import java.io.IOException;
import java.util.Timer;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.catalina.CometEvent;
import org.apache.catalina.CometProcessor;



public class CometServlet extends HttpServlet implements CometProcessor {
    private static final long serialVersionUID = 1L;
    private static final Integer TIMEOUT = 60 * 1000;  
    Timer timer = new Timer();
    public CometServlet() {
        super();
    }


    public void event(CometEvent event) throws IOException, ServletException {
            HttpServletRequest request = event.getHttpServletRequest();  
            HttpServletResponse response = event.getHttpServletResponse();  
            
            try {
                    if (event.getEventType() == CometEvent.EventType.BEGIN) {  
                        request.setAttribute("org.apache.tomcat.comet.timeout", TIMEOUT);  
                        log("Begin for session: " + request.getSession(true).getId());  
                        MessageSender pMessageSender = new MessageSender();
                        pMessageSender.setConnection(response);

                        timer.schedule(pMessageSender, 5000,50000);
                          
                    } 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");  
                    }  
                
            } catch (Exception e) {
                e.printStackTrace();
            }
            
    
        
    }


}

(2) servlet所用到的类apache

package tbktest;

import java.io.OutputStream;
import java.util.ArrayList;
import java.util.TimerTask;

import javax.servlet.ServletResponse;

public class MessageSender extends TimerTask {
    private static int mOne = 1;
    private static int mTwo = 1;
    private static int mThree = 1;
    private static int mFour = 1;
    private static int mFive = 1;
    private static int mSix = 1;
    private static int mSeven = 1;
    private static int mEigt = 1;
    private static int mNigh = 1;
    private static int mTen = 1;
    private static int mTel = 1;
    private static int mEs = 1;
    
    protected boolean running = true;  
  
    protected final ArrayList messages = new ArrayList();  
    
    protected  ArrayList<ServletResponse> connection = new ArrayList<ServletResponse>();  
  
  
    public synchronized void setConnection(ServletResponse connection) {  
        this.connection.add(connection);
        notify();  
    }  
  
    // 发送信息  
    public void send() {  
        // 同步队列,加入发送信息  
        synchronized (messages) { 
             mOne = mOne+1;
             mTwo = mTwo+1;
             mThree = mThree+1;
             mFour = mFour+1;
             mFive = mFive+1;
             mSix = mSix+1;
             mSeven = mSeven+1;
             mEigt = mEigt+1;
             mNigh = mNigh+1;
             mTen = mTen+1;
             mTel = mTel+1;
             mEs = mEs+1;
             
             messages.add(mOne);
             messages.add(mTwo);
             messages.add(mThree);
             messages.add(mFour);
             messages.add(mFive);
             messages.add(mSix);
             messages.add(mSeven);
             messages.add(mEigt);
             messages.add(mNigh);
             messages.add(mTen);
             messages.add(mTel);
             messages.add(mEs);
            // 唤醒  
            messages.notify();  
        }  
    }  
  
    public void run() {  
        send();
        // 线程启动  
        log("start");  
  
            if (messages.size() == 0) {  
                try {  
                    synchronized (messages) {  
                        log("MessageSender wait[空闲状态,线程等待]");  
                        // 释放锁  
                        messages.wait();  
                    }  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                    // Ignore  
                }  
            }  
            String pendingMessages = null;  
            synchronized (messages) {  
                  
                // 导出发送的信息至数组  
                pendingMessages = messages.toString();  
                // 清空信息队列  
                messages.clear();  
            }  
            try {  
                if (connection == null) {  
                    try {  
                        synchronized (this) {  
                            // 等待注入HTTP RESPONSE  
                            wait();  
                        }  
                    } catch (InterruptedException e) {  
                        // Ignore  
                        e.printStackTrace();  
                    }  
                }  
                  
                for(int i=0;i<connection.size();i++){
                     OutputStream out = connection.get(i).getOutputStream();  
                         final String forecast = pendingMessages;  
                         out.write(forecast.getBytes());  
                         out.flush();  
                         connection.get(i).flushBuffer();  
                         System.out.println(pendingMessages);
                }
                // 输出流操做  
               
            } catch (Exception e) {  
                log("IOExeption sending message", e);  
            }   
        }
  
  
    // 中止  
    public void stop() {  
        running = false;  
    }  
  
    // 日志  
    private void log(Object obj) {  
        System.out.println(obj);  
    }  
  
    // 日志  
    private void log(Object obj, Throwable e) {  
        System.out.println(obj);  
        e.printStackTrace();  
    }  
}
View Code

(3) html页面须要引入百度的echarts相关js文件数组

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>ECharts</title>
    <!--Step:1 Import a module loader, such as esl.js or require.js-->
    <!--Step:1 引入一个模块加载器,如esl.js或者require.js-->
    <script src="js/esl.js"></script>
     <script src="js/jquery-1.9.0.min.js"></script>
</head>

<body>
    <!--Step:2 Prepare a dom for ECharts which (must) has size (width & hight)-->
    <!--Step:2 为ECharts准备一个具有大小(宽高)的Dom-->
    <div id="main" style="height:500px;border:1px solid #ccc;padding:10px;"></div>
    
    <script type="text/javascript">
    $(document).ready(function(){ 
            try {
                    var request = new XMLHttpRequest();
                } catch (e) {
                    alert("Browser doesn't support window.XMLHttpRequest");
                }
                request.open("GET", "CometServlet", true);
                request.send(null);                          
                var pos = 0;
                request.onreadystatechange = function () {
                    if (request.readyState === 3) {
                         var mdata = new Array();
                        mdata = request.responseText;
                        require.config({
                            paths:{ 
                                echarts:'./js/echarts',
                                'echarts/chart/bar' : './js/echarts',
                                'echarts/chart/line': './js/echarts'
                            }
                        });
                        
                        // Step:4 require echarts and use it in the callback.
                        // Step:4 动态加载echarts而后在回调函数中开始使用,注意保持按需加载结构定义图表路径
                        require(
                            [
                                'echarts',
                                'echarts/chart/bar',
                                'echarts/chart/line'
                            ],
                            function(ec) {
                                var myChart = ec.init(document.getElementById('main'));
                                var option = {
                                    tooltip : {
                                        trigger: 'axis'
                                    },
                                    legend: {
                                        data:['蒸发量','降水量']
                                    },
                                    toolbox: {
                                        show : true,
                                        feature : {
                                            mark : true,
                                            dataView : {readOnly: false},
                                            magicType:['line', 'bar'],
                                            restore : true,
                                            saveAsImage : true
                                        }
                                    },
                                    calculable : true,
                                    xAxis : [
                                        {
                                            type : 'category',
                                            data : ['1月','2月','3月','4月','5月','6月','7月','8月','9月','10月','11月','12月']
                                        }
                                    ],
                                    yAxis : [
                                        {
                                            type : 'value',
                                            splitArea : {show : true}
                                        }
                                    ],
                                    series : [
                                        {
                                            name:'蒸发量',
                                            type:'bar',
                                            data:mdata
                                        },
                                        {
                                            name:'降水量',
                                            type:'bar',
                                            data:[]
                                        }
                                    ]
                                };
                                
                                myChart.setOption(option);
                            }
                        );
                    }
                    
                };

    //$.ajax({
       // type : "get",
       // url: "CometServlet",
      // dataType:"text",
       // success: function(data) {
            // = data;
            
            
   // });
    });
    // Step:3 conifg ECharts's path, link to echarts.js from current page.
    // Step:3 为模块加载器配置echarts的路径,从当前页面连接到echarts.js,定义所需图表路径
    
    </script>
</body>
</html>
View Code
相关文章
相关标签/搜索