Http多線程下載與斷點續傳分析

          Http多線程下載與斷點續傳分析

              找了很多天的工作,真是找得有點郁悶,也就惰了下來!發現現在的簡歷真是不值錢。上次面試的時候本來投的是“高級程序員”職位,筆試也筆試,面試也面了。本來還是信心滿滿的. 不過后來在談到薪水的時候,被貶到了初級程序員,給的高級程序員標準我還達不到,中級程序員的標準也需要一定條件--中級證書,郁悶的是我也沒有!最后被定位為初級程序員!還真是有點打擊人。

              不過我到現在也不知道初中高級程序員的標準究竟在哪里,平時總是盡量讓自己提高,也很少去考濾到證書的重要性!總之最后就是泡湯了

              好了,口水就吐到這里吧,轉入正題!投簡歷歸投簡歷,剩余時間總是喜歡做一點自己喜歡做的事。

              有時候發現最快樂的事就是靜靜的聽著音樂,敲著代碼,寫一些東西與朋友一起分享,一起研究。

              上次的 - “Mp3在線搜索工具”還有很多可以改進的地方,也得到一些朋友的建議,非常感謝。這個版本中加入了斷點續傳的功能,使用了XML文件保存任務列表及狀態信息,并且支持多線程分段下載, 提高下載速度,在這一個版本中,我把它叫做: JLoading 因為我還想不出一個更好聽一點或更酷一些的名字,而且我還想讓他可以下載一些其它文件。程序不想做大,但想做得極致一些,比如從速度上。歡迎交流學習, huliqing(huliqing@live.com)

          Jloading完整程序下載

          Jloading源碼下載(僅供學習研究使用,有任何問題請與本人聯系)





          協議針對于Http,先談一下簡單原理。因為代碼太多,在這里只取重點分析。

              如果你對http協議比較了解,那么你應該已經知道原理了,只要在請求頭中加入以下代碼就可以只請求部分數據: Content-Range: bytes 20000-40000/47000 ,
          即從第20000字節請求到第40000個字節,(文件長度是47000字節).知道了這一點之后,請求數據就非常容易了,
          只要利用Java中的URL,先探測數據的長度,然后再將這個長度分片段進行多段程下載就可以了,以下是我的實現方式:
                  // 對文件進行分塊
                  try {
                      totalBytes 
          = new URL(url).openConnection().getContentLength();
                      
          if (totalBytes == -1) state = STATE_NONE;
                  } 
          catch (IOException ioe) {
                      
          return;
                  }
                  
          // 創建分塊,并創建相應的負責下載的線程
                  long pieceSize = (long) Math.ceil((double) totalBytes / (double) threads);
                  
          long pStart = 0;
                  
          long pEnd = 0;
                  tasksAll.clear();
                  tasks.clear();
                  
          for (int i = 0; i < threads; i++) {
                      
          if (i == 0) {
                          pStart 
          = pieceSize * i;
                      }
                      
          if (i == threads - 1) {
                          pEnd 
          = totalBytes;
                      } 
          else {
                          pEnd 
          = pStart + pieceSize;
                      }
                      Piece piece 
          = new Piece(pStart, pStart, pEnd);
                      tasksAll.add(piece);
                      tasks.add(piece);
                      pStart 
          = pEnd + 1;
                  }

            程序根據線程數目劃分成相應的分片信息(Piece)并放入任務列表中,由線程池中的線程去負責下載,每個線程負責一個片段(Piece),當線程下載完自己的分片之后并不立即消毀,而是到任務隊列中繼續獲取任務,
          若任務池為空,則將自己放入空閑池中并等待新任務,其他線程在發現有空閑線程時,則將自己所負責的任務分片再進行切割,并放入到任務隊列中,同時喚醒空閑線程幫助下載,這樣不會出現懶惰線程,也可以實現動態增刪線程的功能,注意的是一些線程同步的問題。
          public void run() {
                  
          while (!dl.isOk()) {
                      
                      
          // 暫停任務
                      synchronized (this) {
                          
          if (dl.isPaused()) {
                              
          try {
                                  
          this.wait();
                              } 
          catch (InterruptedException e) {
                              }
                          }
                      }
                      
                      
          // 中斷停止
                      if (Thread.interrupted() || dl.isStopped()) {
                          
          return;
                      }
                      
                      
          // 等待獲取任務
                      Piece piece;
                      
          synchronized (tasks) {
                          
          while (tasks.isEmpty()) {
                              
          if (dl.isOk()) return;
                              
          try {
                                  tasks.wait();
                                  
          //System.out.println(this.getName() + ":wait");
                              } catch (InterruptedException ie) {
                                  
          //System.out.println(this.getName() + 
                                  
          //        ":InterruptedException:" + ie.getMessage());
                              }
                          }
                          piece 
          = tasks.remove(0);
                          dl.removeFreeLoader(
          this);
                          
          //System.out.println(this.getName() + ":loading");
                      }
                      
          try {
                          URL u 
          = new URL(dl.getURL());
                          URLConnection uc 
          = u.openConnection();
                          
          // 設置斷點續傳位置
                          uc.setAllowUserInteraction(true);
                          uc.setRequestProperty(
          "Range""bytes=" + piece.getPos() + "-" + piece.getEnd());
                          in 
          = new BufferedInputStream(uc.getInputStream());

                          out 
          = new RandomAccessFile(dl.getFileProcess(), "rw");
                          out.seek(piece.getPos()); 
          // 設置指針位置

                          
          long start;
                          
          long end;
                          
          int len = 0;
                          
          while (piece.getPos() < piece.getEnd()) {
                              start 
          = System.currentTimeMillis();
                              len 
          = in.read(buff, 0, buff.length);
                              
          if (len == -1break;
                              out.write(buff, 
          0, len);
                              end 
          = System.currentTimeMillis();
                              timeUsed 
          += end - start;    // 累計時間使用
                              
                              
          long newPos = piece.getPos() + len;
                              
                              
          // 如果該區段已經完成,如果該線程負責的區域已經完成,或出界
                              if (newPos > piece.getEnd()) {
                                  piece.setPos(piece.getEnd());   
                                  
          long offset = newPos - piece.getEnd();
                                  
          long trueReads = (len - offset + 1);
                                  dl.growReadBytes(trueReads);    
          // 修正偏移量
                                  dl.setOffsetTotal(dl.getOffsetTotal() + trueReads);
                                  readBytes 
          += trueReads;
                                  
          //System.out.println(this.getName() + ":read=" + trueReads);
                              } else {
                                  dl.growReadBytes(len);
                                  piece.setPos(piece.getPos() 
          + len);
                                  readBytes 
          += len;
                                  
          //System.out.println(this.getName() + ":read=" + len);
                              }
                              
          // 如果存在空閑的任務線程,則切割出新的區域至任務隊列中。由空閑
                              
          // 的線程輔助下載
                              if (dl.isFreeLoader()) {
                                  Piece newPiece 
          = piece.cutPiece();
                                  
          if (newPiece != null) {
                                      
          synchronized (tasks) {
                                          dl.addTask(newPiece);
                                          dl.setRepairCount(dl.getRepairCount() 
          + 1); // 增加切割次數
                                          tasks.notifyAll();  // 喚醒等待任務中的空閑線程
                                      }
                                  }
                                  
                              }
                              
          // 暫停任務
                              synchronized (this) {
                                  
          if (dl.isPaused()) {
                                      
          try {
                                          
          this.wait();
                                      } 
          catch (InterruptedException e) {
                                      }
                                  }
                              }
                              
                              
          // 中斷停止
                              if (Thread.interrupted() || dl.isStopped()) {
                                  in.close();
                                  out.close();
                                  
          return;
                              }
                              
          //System.out.println(this.getName() + ":read:" + dl.getReadBytes());
                          }
                          out.close();
                          in.close(); 
                          dl.addFreeLoader(
          this);
                          
          //System.out.println("切割次數:" + dl.getRepairCount());
                          if (dl.isOk()) dl.processWhenOk();
                      } 
          catch (IOException e) {
                          
          //System.out.println(this.getName() + ":無法讀取數據");
                      }
                  }
              }

          這里使用了RandomAccessFile進行保存本地文件,使用RandomAccessFile可以快速移動指針。另外一個就是需要在運行過程中,記得定時保存分片信息,以免在程序意外崩潰的情況下無法正常保存狀態信息。
          程序可能還存在一些不足并且還有很多可以改進的地方。

          以下是狀態文件Config的XML結構,這里記錄了每個任務的運行狀態,piece作為每個分段的狀態,在程序重啟之后載入這個配置文件,并在下載完成之后刪除這一文件.另外主目錄還有一個task.xml文件,用于記錄所有下載任務,及狀態文件的位置。
          <?xml version="1.0" encoding="UTF-8" standalone="no"?>
          <file id="1207786769343_4046.6321122755676" 
              length
          ="3895507" 
              name
          ="讀你  36首經典精選" 
              save
          ="C:\Documents and Settings\huliqing.TBUY-HULIQING\桌面\dist\musics\讀你  36首經典精選[1].mp3" 
              threads
          ="12">
          <urls>
              
          <url src="http://zlq.zust.edu.cn/Uploadfiles/wlhx/20071224220653927.mp3"/>
          </urls>
          <pieces>
              
          <piece end="324626" pos="20767" start="0"/>
              
          <piece end="649253" pos="419439" start="324627"/>
              
          <piece end="973880" pos="892414" start="649254"/>
              
          <piece end="1298507" pos="1068702" start="973881"/>
              
          <piece end="1623134" pos="1318124" start="1298508"/>
              
          <piece end="1947761" pos="1706453" start="1623135"/>
              
          <piece end="2272388" pos="1987815" start="1947762"/>
              
          <piece end="2597015" pos="2535705" start="2272389"/>
              
          <piece end="2921642" pos="2671690" start="2597016"/>
              
          <piece end="3246269" pos="3176315" start="2921643"/>
              
          <piece end="3570896" pos="3522551" start="3246270"/>
              
          <piece end="3895507" pos="3678693" start="3570897"/>
          </pieces>




          - huliqing@huliqing.name
          - http://www.huliqing.name

          posted on 2008-04-10 08:38 huliqing 閱讀(18885) 評論(27)  編輯  收藏 所屬分類: Swing

          評論

          # re: Http多線程下載與斷點續傳分析[未登錄] 2008-04-10 09:07 Frank

          一直在看你的blog,今天又有新的收獲 ,先謝謝了 哦
            回復  更多評論   

          # re: Http多線程下載與斷點續傳分析 2008-04-10 09:16 huliqing

          @Frank
          呵呵,歡迎互相交流學習!  回復  更多評論   

          # re: Http多線程下載與斷點續傳分析[未登錄] 2008-04-10 09:18 Frank

          你的程序下載的時候很快,但合并文件的時候沒有用到多線程,所以下載完畢后,文件不能立即就使用  回復  更多評論   

          # re: Http多線程下載與斷點續傳分析 2008-04-10 09:28 ljwan12

          提供下載速度,單靠多線程是不行的,比如你選擇的一個下載源,他的速度本來就很慢,這樣你即使使用很多線程連接,速度也不會有多大的提高。
          我認為有兩種方法可以提高:
          (1)、使用P2P,但此對于你的這個估計沒多大效果,因為這個小軟件的使用人數不多,同時下載某一個文件的概率太小。
          (2)、選擇更快的下載源,你的程序是通過百度MP3搜索查找歌曲,你可以通過程序來判斷某一個下載源的速度,選擇最快的一個。本人認為這種實現對于你的這個程序比較有效。
          迅雷下載速度很快,他結合了很多種技術。
          本人的一點建議,僅供參考!!  回復  更多評論   

          # re: Http多線程下載與斷點續傳分析 2008-04-10 09:35 huliqing

          @Frank
          我使用的是多個RandomAccessFile同時利用一個File對象寫同一個文件,操作系統本身會同步對文件的寫操作。在利用多個File對象寫同一個文件時,遇到一些問題, 另外我認為速度的瓶頸主要還是來自網絡,本地文件讀寫應該不會是什么大問題。:)  回復  更多評論   

          # re: Http多線程下載與斷點續傳分析 2008-04-10 09:40 huliqing

          @ljwan12
          <urls>
          <url src="http://zlq.zust.edu.cn/Uploadfiles/wlhx/20071224220653927.mp3"/>
          </urls>
          呵呵,我在配置文件中預留這一個,我想,在有可能的情況下讓程序從多個下載源中進行文件下載。有時候我們會搜到很多同文件而不同URL的源地址。利用這個應該可以更好的提升速度  回復  更多評論   

          # re: Http多線程下載與斷點續傳分析 2008-04-10 09:40 Paul Lin

          多謝樓主分享!  回復  更多評論   

          # re: Http多線程下載與斷點續傳分析 2008-04-10 10:01 netnova

          中級程序員的標準也需要一定條件--中級證書
          什么公司,這么土。  回復  更多評論   

          # re: Http多線程下載與斷點續傳分析[未登錄] 2008-04-10 10:02 Samuel

          1、在選擇需要下載的歌曲的時候,不能支持雙擊加入下載列表。也沒有右鍵,這樣挺不方便的  回復  更多評論   

          # re: Http多線程下載與斷點續傳分析 2008-04-10 10:11 太陽

          繼續關注樓主的作品, 大家一起出謀劃策  回復  更多評論   

          # re: Http多線程下載與斷點續傳分析 2008-04-10 14:48 ljwan12

          @huliqing
          你的想法,在其他的很多下載軟件中就是這么實現的,但是這個實現起來就比較復雜了,你還得建立自己的后臺服務器來保存這些資源的一些信息,另外還有一個更加重要的是你需要有一套很好的機制來判斷是否是完全一樣的!!!
          可以參考一些開源下載軟件的代碼,他們實現的都比較好!比如電驢  回復  更多評論   

          # re: Http多線程下載與斷點續傳分析[未登錄] 2008-04-10 18:47 huliqing

          謝謝大家的意見,作這一個只是研究或者學習一些技術,還無意做大,只是與大家共同研究學習一下,畢竟個人能力,時間與精力還是有限,呵呵!  回復  更多評論   

          # re: Http多線程下載與斷點續傳分析[未登錄] 2008-04-11 16:55 TY

          那公司也是真夠土了.  回復  更多評論   

          # re: Http多線程下載與斷點續傳分析 2008-04-15 00:33 endswel

          后臺機制,其實也不是什么。 他們都是算軟件的MD5碼或者其他一些不可逆的算法。 不過對于大軟件的MD5碼,你會算死的。
          不過也有解決方案。 截取你對軟件的部分信息。 取軟件的3~5段來進行計算。  回復  更多評論   

          # 你好 2008-07-06 11:23 landwang

          你好,最近在做一個項目。用到斷點續傳方面的東西,也在研究。希望和你探討一下。huliqing@live.com是你msn地址嗎?
          我的msn:landwang2001@hotmail.com  回復  更多評論   

          # re: Http多線程下載與斷點續傳分析 2008-08-16 07:14 pig

          樓主考慮過網絡斷開以后又恢復會出現什么情況嗎?期待答復......  回復  更多評論   

          # re: Http多線程下載與斷點續傳分析 2009-07-24 13:16 liouxiao

          現在百度的mp3搜索頁面改了,不是在<a href="...">中直接給出下載鏈接,導致工具無法抓取到有用的下載地址。

          可以使用Mozilla的Javascript庫Rhino執行頁面里內嵌的Javascript代碼,得到最終的下載地址。需要改動的類是biz.tbuy.huliqing.jloading.ext.mp3.CodeFilter,改動的方法是getMp3Address

          具體代碼已經發給你的郵箱了。  回復  更多評論   

          # re: Http多線程下載與斷點續傳分析 2009-07-25 15:56 huliqing

          @liouxiao
          謝謝.已經收到郵件  回復  更多評論   

          # re: Http多線程下載與斷點續傳分析 2009-08-30 10:31 BLOVE

          博主,你好,我是第一次來能不能把getMp3Address方法代碼再分享下,謝謝.
            回復  更多評論   

          # re: Http多線程下載與斷點續傳分析 2009-08-31 10:07 huliqing

          @BLOVE
          參照一下原來的getMp3Address,再比較一下百度現有頁面代碼,應該很快可以寫出來,或者聯系一下上面的liouxiao吧,已經有很長一段時間沒有寫這個軟件,代碼目前還真找不到在哪里了。呵呵!  回復  更多評論   

          # re: Http多線程下載與斷點續傳分析 2009-12-11 16:19 Moryk

          仔細看看,也是老帖了.想問樓主
          這樣會不會耗費服務器資源呢?多線程下載開多個SOCKET連接DNS,會影響很大的吧.  回復  更多評論   

          # re: Http多線程下載與斷點續傳分析[未登錄] 2010-05-25 10:35 M

          你好。。能發個demo給我學習學習嗎?
          myx8178633@163.com
          謝了。。  回復  更多評論   

          # re: Http多線程下載與斷點續傳分析 2012-01-09 17:11 VARCHAR

          有這個demo沒有 發一個吧 謝謝 QQ:874729211  回復  更多評論   

          # re: Http多線程下載與斷點續傳分析 2012-03-22 14:41 zhagnming

          畢業設計和你這個有關系 不錯 參考一下 很好  回復  更多評論   

          # re: Http多線程下載與斷點續傳分析[未登錄] 2013-04-06 15:31 小天

          尼瑪,你程序不能用呢,我 win7
          java version "1.7.0_09"
          Java(TM) SE Runtime Environment (build 1.7.0_09-b05)
          Java HotSpot(TM) 64-Bit Server VM (build 23.5-b02, mixed mode)  回復  更多評論   

          # re: Http多線程下載與斷點續傳分析 2013-04-18 12:56 梁翠怡

          博主您好,最近在做一個項目。用到斷點續傳方面的東西。希望您能指導一下或發個完整源碼給我,謝謝您,這是我的郵箱:2565776764@qq.com  回復  更多評論   

          # re: Http多線程下載與斷點續傳分析 2013-08-02 10:31 rambo_xue

          怎么搜不到東西  回復  更多評論   

          導航

          統計

          公告

          文章原創,歡迎轉載
          ——轉載請注明出處及原文鏈接

          隨筆分類(60)

          隨筆檔案(33)

          最新評論

          評論排行榜

          主站蜘蛛池模板: 会同县| 大邑县| 文登市| 扶风县| 昌都县| 兰溪市| 巫山县| 故城县| 富裕县| 宿州市| 思茅市| 九龙城区| 密山市| 大竹县| 石泉县| 府谷县| 渭源县| 青龙| 吉木萨尔县| 新化县| 宁都县| 香港 | 河源市| 南平市| 大庆市| 新营市| 正阳县| 太原市| 龙口市| 宣威市| 泗阳县| 绵阳市| 台南市| 五常市| 南漳县| 信宜市| 平乐县| 乐至县| 丹巴县| 雅安市| 西充县|