<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    聶永的博客

    記錄工作/學(xué)習(xí)的點點滴滴。

    Servlet 3.0筆記之異步請求Comet推送長輪詢(long polling)篇

    Comet另一種形式為長輪詢(long polling),客戶端會與服務(wù)器建立一個持久的連接,直到服務(wù)器端有數(shù)據(jù)發(fā)送過來,服務(wù)器端斷開,客戶端處理完推送的數(shù)據(jù),會再次發(fā)起一個持久的連接,循環(huán)往復(fù)。
    和流(Streaming)區(qū)別主要在于,在一次長連接中,服務(wù)器端只推送一次,然后斷開連接。
    其實現(xiàn)形式大概可分文AJAX長輪詢和JAVASCRIPT輪詢兩種。
    1. AJAX方式請求長輪詢

      服務(wù)器端可以返回原始的數(shù)據(jù),或格式化的JSON、XML、JAVASCRIPT信息,客戶端可擁有更多的處理自由。在形式上和傳統(tǒng)意義上的AJAX GET方式大致一樣,只不過下一次的輪詢需要等到上一次的輪詢數(shù)據(jù)返回處理完方可進行。
      這里給出客戶端的小示范:
      <!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>jQuery 1.5 with long poll</title>
      </head>
      <body>
      <div id="tip"></div>
      <script type="text/javascript" src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.5.min.js"></script>
      <script type="text/javascript">
      $(function (){
      function log(resp){
      $("#tip").html("<b>" + resp + "</b>");
      }

      log("loading");

      // 去除緩存
      $.ajaxSetup({ cache: false });

      function initGet(){
      $.get("getNextTime")
      .success(function(resp){
      log(resp);
      }).error(function(){
      log("ERROR!");
      }).done(initGet);
      }

      initGet();
      });
      </script>
      </body>
      </html>
      基于jQuery 1.5,事件鏈,比以往更簡潔明了,尤其是在done方法中又一次調(diào)用自身,棒極了。
      服務(wù)器端很簡單,每1秒輸出一次當前系統(tǒng)時間:
      /**
      * 簡單模擬每秒輸出一次當前系統(tǒng)時間,精細到毫秒
      *
      * @author yongboy
      * @date 2011-2-11
      * @version 1.0
      */
      @WebServlet("/getNextTime")
      public class GetNextTimeServlet extends HttpServlet {
      private static final long serialVersionUID = 1L;

      protected void doGet(HttpServletRequest request,
      HttpServletResponse response) throws ServletException, IOException {
      try {
      Thread.sleep(1000L);
      } catch (InterruptedException e) {
      e.printStackTrace();
      }

      response.setContentType("text/plain");
      PrintWriter out = response.getWriter();

      out.print(DateFormatUtils.format(System.currentTimeMillis(),
      "yyyy-MM-dd HH:mm:ss SSS"));
      out.flush();

      out.close();
      }
      }
    2. JAVASCRIPT標簽輪詢(Script tag long polling)

      引用的JAVASCRIPT腳本文件是可以跨域的,再加上JSONP,更為強大了。
      這里給出一個演示,實現(xiàn)功能類似上面。
      客戶端清單:
      <!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>jQuery 1.5 with JSONP FOR Comet</title>
      </head>
      <body>
      <div id="tip"></div>
      <script type="text/javascript" src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.5.min.js"></script>
      <script type="text/javascript">
      $(function (){
      function log(resp){
      $("#tip").html("<b>" resp "</b>");
      }

      log("loading");

      function jsonp(){
      $.getJSON('http://192.168.0.99:8080/servlet3/getNextTime2?callback=?').success(function(resp){
      log("now time : " resp);
      }).done(jsonp);

      // 以下方式也是合法的
      /*
      $.getJSON('http://192.168.0.99:8080/servlet3/getNextTime2?callback=?',function(date){
      log(date);
      }).done(jsonp);
      */
      }

      jsonp();
      });
      </script>
      </body>
      </html>

      服務(wù)器端清單:
      /**
      * JSONP形式簡單模擬每秒輸出一次當前系統(tǒng)時間,精細到毫秒
      * @author yongboy
      * @date 2011-2-11
      * @version 1.0
      */
      @WebServlet("/getNextTime2")
      public class GetNextTimeServlet2 extends HttpServlet {
      private static final long serialVersionUID = 1L;

      protected void doGet(HttpServletRequest request,
      HttpServletResponse response) throws ServletException, IOException {
      try {
      Thread.sleep(1000L);
      } catch (InterruptedException e) {
      e.printStackTrace();
      }

      String callback = request.getParameter("callback");

      if(StringUtils.isBlank(callback)){
      callback = "showResult";
      }

      PrintWriter out = response.getWriter();

      StringBuilder resultBuilder = new StringBuilder();
      resultBuilder
      .append(callback)
      .append("('")
      .append(DateFormatUtils.format(System.currentTimeMillis(), "yyyy-MM-dd HH:mm:ss SSS"))
      .append("');");

      out.print(resultBuilder);
      out.flush();

      out.close();
      }
      }

      每次請求時,都會在HTML頭部生成一個JS網(wǎng)絡(luò)地址(實現(xiàn)跨域):
      http://192.168.0.99:8080/servlet3/getNextTime2?callback=jQuery150832738454006945_1297761629067&_=1297761631777
      我們不用指定,jquery會自動生成一個隨機的函數(shù)名。
      請求上面地址服務(wù)器端在有新的數(shù)據(jù)輸出時,生成的回調(diào)JS函數(shù):
      jQuery150832738454006945_1297761629067('2011-02-15 17:20:33 921');
      從下面截圖可以看到這一點。
      jspon snapshot
      生成相應(yīng)內(nèi)容:
      image

       不過,長連接情況下,瀏覽器認為當前JS文件還沒有加載完,會一直顯示正在加載中。
    上面的JSONP例子太簡單,服務(wù)器和客戶端的連接等待時間不過1秒鐘時間左右,若在真實環(huán)境下,需要設(shè)置一個超時時間。
    以往可能會有人告訴你,在客戶端需要設(shè)置一個超時時間,若指定期限內(nèi)服務(wù)器沒有數(shù)據(jù)返回,則需要通知服務(wù)器端程序,斷開連接,然后自身再次發(fā)起一個新的連接請求。
    但在Servlet 3.0 異步連接的規(guī)范中,我們就不用那么勞心費神,那般麻煩了。掉換個兒,讓服務(wù)器端通知客戶端超時啦,趕緊重新連接啊。
    在前面幾篇博客中,演示了一個偽真實場景,博客主添加博客,然后主動以流形式推送給終端用戶。這里采用JSONP跨域長輪詢連接形式。
    客戶端:
    <html>
    <head>
    <title>Comet JSONP %u63A8%u9001%u6D4B%u8BD5</title>
    <meta http-equiv="X-UA-Compatible" content="IE=8" />
    <meta http-equiv="content-type" content="text/html;charset=UTF-8"/>
    <meta name="author" content="yongboy@gmail.com"/>
    <meta name="keywords" content="servlet3, comet, ajax"/>
    <meta name="description" content=""/>
    <link type="text/css" rel="stylesheet" href="css/main.css"/>
    <script type="text/javascript" src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.5.min.js"></script>
    <script type="text/javascript">
    String.prototype.template=function(){
    var args=arguments;
    return this.replace(/\{(\d )\}/g, function(m, i){
    return args[i];
    });
    }
    var html = '<div class="logDiv">'
    '<div class="contentDiv">{0}</div>'
    '<div class="tipDiv">last date : {1}</div>'
    '<div class="clear">&nbsp;</div>'
    '</div>';

    function showContent(json) {
    $("#showDiv").prepend(html.template(json.content, json.date));
    }
    function initJsonp(){
    $.getJSON('http://192.168.0.99/servlet3/getjsonpnew?callback=?').success(function(json){
    if(json.state == 1){
    showContent(json);
    }else{
    initJsonp();
    return;
    }
    }).done(initJsonp)
    .error(function(){
    alert("error!");
    });
    }
    $(function (){
    initJsonp();
    });
    </script>
    </head>
    <body style="margin: 0; overflow: hidden">
    <div id="showDiv" class="inputStyle">loading ...</div>
    </body>
    </html>
    服務(wù)器端接收長輪詢連接請求:
    /**
    * JSONP獲取最新信息
    *
    * @author yongboy
    * @date 2011-2-17
    * @version 1.0
    */
    @WebServlet(urlPatterns = "/getjsonpnew", asyncSupported = true)
    public class GetNewJsonpBlogPosts extends HttpServlet {
    private static final long serialVersionUID = 565698895565656L;
    private static final Log log = LogFactory.getLog(GetNewJsonpBlogPosts.class);

    protected void doGet(HttpServletRequest request,
    HttpServletResponse response) throws ServletException, IOException {
    response.setHeader("Cache-Control", "private");
    response.setHeader("Pragma", "no-cache");
    response.setHeader("Connection", "Keep-Alive");
    response.setHeader("Proxy-Connection", "Keep-Alive");
    response.setContentType("text/javascript;charset=UTF-8");
    response.setCharacterEncoding("UTF-8");

    String timeoutStr = request.getParameter("timeout");

    long timeout;

    if (StringUtils.isNumeric(timeoutStr)) {
    timeout = Long.parseLong(timeoutStr);
    } else {
    // 設(shè)置1分鐘
    timeout = 1L * 60L * 1000L;
    }

    log.info("new request ...");
    final HttpServletResponse finalResponse = response;
    final HttpServletRequest finalRequest = request;
    final AsyncContext ac = request.startAsync(request, finalResponse);
    // 設(shè)置成長久鏈接
    ac.setTimeout(timeout);
    ac.addListener(new AsyncListener() {
    public void onComplete(AsyncEvent event) throws IOException {
    log.info("onComplete Event!");
    NewBlogJsonpListener.ASYNC_AJAX_QUEUE.remove(ac);
    }

    public void onTimeout(AsyncEvent event) throws IOException {
    // 嘗試向客戶端發(fā)送超時方法調(diào)用,客戶端會再次請求/blogpush,周而復(fù)始
    log.info("onTimeout Event!");

    // 通知客戶端再次進行重連
    final PrintWriter writer = finalResponse.getWriter();

    String outString = finalRequest.getParameter("callback")
    + "({state:0,error:'timeout is now'});";
    writer.println(outString);
    writer.flush();
    writer.close();

    NewBlogJsonpListener.ASYNC_AJAX_QUEUE.remove(ac);
    }

    public void onError(AsyncEvent event) throws IOException {
    log.info("onError Event!");
    NewBlogJsonpListener.ASYNC_AJAX_QUEUE.remove(ac);
    }

    public void onStartAsync(AsyncEvent event) throws IOException {
    log.info("onStartAsync Event!");
    }
    });

    NewBlogJsonpListener.ASYNC_AJAX_QUEUE.add(ac);
    }
    }
    很顯然,在有博客數(shù)據(jù)到達時,會通知到已注冊的客戶端。
    注意,推送數(shù)據(jù)之后,需要調(diào)用當前的異步上下文complete()函數(shù):
    /**
    * 監(jiān)聽器單獨線程推送到客戶端
    *
    * @author yongboy
    * @date 2011-2-17
    * @version 1.0
    */
    @WebListener
    public class NewBlogJsonpListener implements ServletContextListener {
    private static final Log log = LogFactory.getLog(NewBlogListener.class);
    public static final BlockingQueue BLOG_QUEUE = new LinkedBlockingQueue();
    public static final Queue ASYNC_AJAX_QUEUE = new ConcurrentLinkedQueue();

    public void contextDestroyed(ServletContextEvent arg0) {
    log.info("context is destroyed!");
    }

    public void contextInitialized(ServletContextEvent servletContextEvent) {
    log.info("context is initialized!");
    // 啟動一個線程處理線程隊列
    new Thread(runnable).start();
    }

    private Runnable runnable = new Runnable() {
    public void run() {
    boolean isDone = true;

    while (isDone) {
    if (!BLOG_QUEUE.isEmpty()) {
    try {
    log.info("ASYNC_AJAX_QUEUE size : "
    + ASYNC_AJAX_QUEUE.size());
    MicBlog blog = BLOG_QUEUE.take();

    if (ASYNC_AJAX_QUEUE.isEmpty()) {
    continue;
    }

    String targetJSON = buildJsonString(blog);

    for (AsyncContext context : ASYNC_AJAX_QUEUE) {
    if (context == null) {
    log.info("the current ASYNC_AJAX_QUEUE is null now !");
    continue;
    }
    log.info(context.toString());
    PrintWriter out = context.getResponse().getWriter();

    if (out == null) {
    log.info("the current ASYNC_AJAX_QUEUE's PrintWriter is null !");
    continue;
    }

    out.println(context.getRequest().getParameter(
    "callback")
    + "(" + targetJSON + ");");
    out.flush();

    // 通知,執(zhí)行完成函數(shù)
    context.complete();
    }
    } catch (Exception e) {
    e.printStackTrace();
    isDone = false;
    }
    }
    }
    }
    };

    private static String buildJsonString(MicBlog blog) {
    Map info = new HashMap();
    info.put("state", 1);
    info.put("content", blog.getContent());
    info.put("date",
    DateFormatUtils.format(blog.getPubDate(), "HH:mm:ss SSS"));

    JSONObject jsonObject = JSONObject.fromObject(info);

    return jsonObject.toString();
    }
    }
    長連接的超時時間,有人貼切的形容為“心跳頻率”,聯(lián)動著兩端,需要根據(jù)環(huán)境設(shè)定具體值。
    JSONP方式無論是輪詢還是長輪詢,都是可以很容易把目標程序融合到第三方網(wǎng)站,當前的個人主頁、博客等很多小掛件大多也是采用這種形式進行跨域獲取數(shù)據(jù)的。

    posted on 2011-02-17 18:03 nieyong 閱讀(8731) 評論(0)  編輯  收藏 所屬分類: Servlet3

    公告

    所有文章皆為原創(chuàng),若轉(zhuǎn)載請標明出處,謝謝~

    新浪微博,歡迎關(guān)注:

    導(dǎo)航

    <2011年2月>
    303112345
    6789101112
    13141516171819
    20212223242526
    272812345
    6789101112

    統(tǒng)計

    常用鏈接

    留言簿(58)

    隨筆分類(130)

    隨筆檔案(151)

    個人收藏

    最新隨筆

    搜索

    最新評論

    閱讀排行榜

    評論排行榜

    主站蜘蛛池模板: 特级无码毛片免费视频尤物| 亚洲精品永久在线观看| 2019中文字幕免费电影在线播放| 亚洲日韩国产一区二区三区在线| 国产精品亚洲mnbav网站| 日本免费中文视频| 亚洲午夜精品一区二区公牛电影院| 亚洲VA综合VA国产产VA中| 国产成人午夜精品免费视频| 免费无码H肉动漫在线观看麻豆| 青青青亚洲精品国产| 在线观看亚洲AV日韩AV| 亚洲国产片在线观看| 久久久久亚洲精品天堂| 亚洲国产精品成人精品无码区| 亚洲国产一区二区三区| 免费jlzzjlzz在线播放视频| 大地资源二在线观看免费高清| 免费不卡在线观看AV| 一区二区三区福利视频免费观看| 二个人看的www免费视频| 思思久久99热免费精品6| xxxxx做受大片在线观看免费| 亚洲乱亚洲乱妇24p| 亚洲AV综合永久无码精品天堂| 亚洲色大成WWW亚洲女子| 亚洲午夜无码毛片av久久京东热| 亚洲一区二区三区免费观看| 精品久久亚洲中文无码| 亚洲人xxx日本人18| 亚洲精品无码久久久久久| 亚洲深深色噜噜狠狠网站| 亚洲一区二区观看播放| 色噜噜噜噜亚洲第一| 尤物视频在线免费观看| 很黄很污的网站免费| 99ee6热久久免费精品6| 免费专区丝袜脚调教视频| 爽爽日本在线视频免费| 亚洲综合亚洲综合网成人| 99久久亚洲精品无码毛片|