1. 程式人生 > >通過HTTP協議實現多執行緒下載

通過HTTP協議實現多執行緒下載

1. 基本原理,每條執行緒從檔案不同的位置開始下載,最後合併出完整的資料。 

2. 使用多執行緒下載的好處 
    下載速度快。為什麼呢?很好理解,以往我是一條執行緒在伺服器上下載。也就是說,對應在伺服器上,有一個我的下載執行緒存在。 
    這時候肯定不只我一個人在下載,伺服器上肯定同時存在多條下載執行緒,在下載伺服器資源。對於 CPU 來說,不可能實現併發執行。 
    CPU 會公平的為這些執行緒劃分時間片,輪流執行,a執行緒十毫秒 , b執行緒十毫秒... 
    假設運用了本文這種手法,意味著我的下載應用,可以同時使用伺服器端的任意多條執行緒同時下載(理論上). 
    假設這個執行緒數目是 50 條,本應用就將更多的得到伺服器 CPU 的照顧超過 50 倍. 
    但是總歸會受本地網路速度的限制。 

3. 每條執行緒要負責下載的資料長度可以用 “下載資料的總長度” 除以 “參與下載的執行緒總數” 來計算。但是要考慮到不能整除的情況。 
    假設有 5 條執行緒參與下載,那麼計算公式應該為 : 
            int block = 資料總長度%執行緒數 == 0? 10/3 : 10/3+1; (不能整除,則加一) 

4. 和資料庫分頁查詢型別。每條執行緒需要知道自己從資料的什麼位置開始下載,下載到什麼位置為止。 
   首先,為每一個執行緒配備一個 id , id 從零開始,為 0 1 2 3... 
   開始位置:執行緒 id 乘以每條執行緒負責下載的資料長度. 
   結束位置:下一個執行緒開始位置的前一個位置。 
   如: 
      int startPosition =  執行緒id * 每條執行緒下載的資料長度 
      int endPosition = (執行緒id + 1) * 每條執行緒下載的資料長度 -1; 
        
5. HTTP 協議的 Range 頭可以指定從檔案的什麼位置開始下載,下載到什麼位置結束。單位為 1byte 
   Range:bytes=2097152-4194304 表示從檔案的 2M 的位置開始下載,下載到 4M 處結束 
   假如 Range 指定要讀取到 檔案的 5104389 的位元組數位置,但是下載的檔案本身只有 4104389 個長度。那麼下載操作自動會在 4104389 處停止。 
   因此不會下載到多餘的無效資料. 

6. 另一個難題是如何按順序將資料寫往本地檔案。因為,執行緒是同步執行的,它們同時在往本地目標檔案寫入資料。
   而執行緒於執行緒之間寫入的資料並沒有按照下載資料本身的順序。若按照普通的 OutputStream 的寫入方式,最後的本地下載檔案將失真。 
   於是我們將用到下面這個類: 
     java.io.RandomAccessFile 
     因為此類同時實現了 DataOutput 和 DataInput 的方法。使他們同時具有寫入和讀取功能。 
     這個類彷彿存在一個類似檔案指標的東西,可以隨意執行檔案的任意一個位置開始讀寫. 
     因此此類的例項支援對隨機訪問檔案的讀取和寫入. 
     
     例如: 
       

Java程式碼  收藏程式碼
  1. File file = new File("1.txt");  
  2.         RandomAccessFile accessFile = new RandomAccessFile(file,"rwd");  
  3.         accessFile.setLength(1024);  
    
    雖然,執行完這段程式碼後,我們還沒有向目標檔案 "1.txt" 寫入任何資料。但是如果此時檢視其大小,已經為 1kb 了。這是我們自己設定的大小。 
    這個操作類似於向這個檔案儲存了一個大型的 byte 陣列。這個陣列將這個檔案撐到指定大小。等待被填滿。 
    既然是這樣,好處就在於,我們可以通過 “索引” 隨機訪問此檔案系統的某個部分。 
    例如,可能這個檔案大小為 500 
    那麼,我的業務需求可能需要第一次 從 300 位置開始寫資料,寫到 350 為止。 
    第二次,我又從 50 開始寫資料,寫到 100 為止。 
    總之,我不是 “一次性” 的 “按順序” 的將這個檔案寫完。 
    那麼,RandomAccessFile 可以支援這種操作。 
     
     API 
      void setLength(long newLength) 
          Sets the length of this file. (設定檔案的預計大小) 
      void seek(long pos) 
          Sets the file-pointer offset, measured from the beginning of this file, at which the next read or write occurs. 
          假設為這個方法傳入 1028 這個引數,表示,將從檔案的 1028 位置開始寫入。 
      void write(byte[] b, int off, int len) 
          Writes len bytes from the specified byte array starting at offset off to this file. 
      write(byte[] b) 
          Writes b.length bytes from the specified byte array to this file, starting at the current file pointer. 
      void writeUTF(String str) 
          Writes a string to the file using modified UTF-8 encoding in a machine-independent manner. 
       String readLine() 
          Reads the next line of text from this file. 

      實驗程式碼: 
     

Java程式碼  
收藏程式碼
  1. publicstaticvoid main(String[] args) throws Exception {  
  2.  File file = new File("1.txt");   
  3. RandomAccessFile accessFile = new RandomAccessFile(file,"rwd");  
  4.  /* 設定檔案為 3 個位元組大小 */
  5.  accessFile.setLength(3);  
  6.  /* 向第二個位置寫入 '2' */
  7.  accessFile.seek(1);  
  8.  accessFile.write("2
    ".getBytes());  
  9.  /* 向第一個位置寫入 '1' */
  10. accessFile.seek(0); accessFile.write("1".getBytes());   
  11. /* 向第三個位置寫入 '3' */
  12.  accessFile.seek(2);   
  13. accessFile.write("3".getBytes()); accessFile.close();   
  14. // 期待檔案的內容為 :123
  15.  }   


    
    以上實驗成功,雖然我們寫入字串的順序為 "2"、"1"、"3",但是因為設定了檔案偏移量的關係,檔案最終儲存的資料為 : 123 
    另一個疑問,寫完這三個資料,檔案的大小已經為 3 個位元組大小了。已經撐滿了寫入的資料,那麼我們繼續往裡面放資料會有什麼效果? 
    
    /* 向超出大小的第四個位元組位置寫入資料 */ 
    accessFile.seek(3); 
    accessFile.write("400".getBytes()); 
    
    以上程式碼無論 seek 方法指定的檔案指標偏移量以及存入的資料,都已經超出了最開始為檔案設定的 3 個位元組的大小。 
    按照我的猜測,至少 “accessFile.seek(3)” 位置會丟擲 "ArrayIndexOutOfBoundsException" 異常,表示下標越界。 
    而,單獨執行 "accessFile.write("400".getBytes())" 應該可以成功。因為這個需求屬於合理的,應該有執行它的機制。 
    實驗結果是兩句程式碼都是成功的。貌似是說明,檔案隱含的大型的位元組陣列,可以自動撐大。 
    
    但是要注意的問題是,必須要保證所設定的檔案大小的每一個位置都具有合法的資料,至少不能為空。 
    例如: 
        /* 向第三個位置寫入 '3'  */ 
        accessFile.seek(2); 
        accessFile.write("3".getBytes()); 
        
        accessFile.seek(5); 
        accessFile.write("400".getBytes()); 
    那麼結合之前的程式碼,最後的結果為: 
        123口口400 
    在空白的兩個位置處出現了亂碼。這是理所應當的。 
    
    另外,假設我們為檔案指定了一百個長度: 
        accessFile.setLength(100); 
    而,實際上,我們只為其前五個位置設定了值。那麼理所當然的是,檔案儲存的資料,最後會綴上 95 個亂碼。 
    
7. 準備工作應該十分充分了。接下來上程式碼。 


   
Java程式碼  收藏程式碼
  1. import java.io.File;    
  2. import java.io.IOException;    
  3. import java.io.InputStream;    
  4. import java.io.RandomAccessFile;    
  5. import java.net.HttpURLConnection;    
  6. import java.net.URL;    
  7. /**  
  8.  * 多執行緒方式檔案下載  
  9.  */
  10. publicclass MulThreadDownload {    
  11.     /* 下載的URL */
  12.     private URL downloadUrl;    
  13.     /* 用於儲存的本地檔案 */
  14.     private File localFile;    
  15.     /* 沒條執行緒下載的資料長度 */
  16.     privateint block;    
  17.     publicstaticvoid main(String[] args) {    
  18.         /* 可以為網路上任意合法下載地址 */
  19.         String downPath = "http://192.168.1.102:8080/myvideoweb/down.avi";    
  20.         MulThreadDownload threadDownload = new MulThreadDownload();    
  21.         /* 開 10 條執行緒下載下載 */
  22.         try {    
  23.             threadDownload.download(downPath, 10);    
  24.         } catch (Exception e) {    
  25.             e.printStackTrace();    
  26.         }    
  27.     }    
  28.     /**  
  29.      * 多執行緒檔案下載  
  30.      *   
  31.      * @param path 下載地址  
  32.      * @param threadCount 執行緒數  
  33.      */
  34.     publicvoid download(String path, int threadCount) throws Exception {    
  35.         downloadUrl = new URL(path);    
  36.         HttpURLConnection conn = (HttpURLConnection) downloadUrl    
  37.                 .openConnection();    
  38.         /* 設定 GET 請求方式 */
  39.         conn.setRequestMethod("GET");    
  40.         /* 設定響應時間超時為 5 秒 */
  41.         conn.setConnectTimeout(5 * 1000);    
  42.         /* 獲取本地檔名 */
  43.         String filename = parseFilename(path);    
  44.         /* 獲取下載檔案的總大小 */
  45.         int dataLen = conn.getContentLength();    
  46.         if (dataLen < 0) {    
  47.             System.out.println("獲取資料失敗");    
  48.             return;    
  49.         }    
  50.         /* 建立本地目標檔案,並設定其大小為準備下載檔案的總大小 */
  51.         localFile = new File(filename);    
  52.         RandomAccessFile accessFile = new RandomAccessFile(localFile, "rwd");    
  53.         /* 這時候,其實本地目錄下,已經建立好了一個大小為下載檔案的總大小的檔案 */
  54.         accessFile.setLength(dataLen);    
  55.         accessFile.close();    
  56.         /* 計算每條執行緒要下載的資料大小 */
  57.         block = dataLen % threadCount == 0 ? dataLen / threadCount : dataLen / threadCount + 1;    
  58.         /* 啟動執行緒下載檔案 */
  59.         for (int i = 0; i < threadCount; i++) {    
  60.             new DownloadThread(i).start();    
  61.         }    
  62.     }    
  63.     /**  
  64.      * 解析檔案  
  65.      */
  66.     private String parseFilename(String path) {    
  67.         return path.substring(path.lastIndexOf("/") + 1);    
  68.     }    
  69.     /**  
  70.      * 內部類: 檔案下載執行緒類  
  71.      */
  72.     privatefinalclass DownloadThread extends Thread {    
  73.         /* 執行緒 id */
  74.         privateint threadid;    
  75.         /* 開始下載的位置 */
  76.         privateint startPosition;    
  77.         /* 結束下載的位置 */
  78.         privateint endPosition;    
  79.         /**  
  80.          * 新建一個下載執行緒  
  81.          * @param threadid 執行緒 id  
  82.          */
  83.         public DownloadThread(int threadid) {    
  84.             this.threadid = threadid;    
  85.             startPosition = threadid * block;    
  86.             endPosition = (threadid + 1) * block - 1;    
  87.         }    
  88.         @Override
  89.         publicvoid run() {    
  90.             System.out.println("執行緒 '" + threadid + "'啟動下載..");    
  91.             RandomAccessFile accessFile = null;    
  92.             try {    
  93.                 /* 設定從本地檔案的什麼位置開始寫入資料 ,"rwd" 表示對檔案具有讀寫刪許可權 */
  94.                 accessFile = new RandomAccessFile(localFile, "rwd");    
  95.                 accessFile.seek(startPosition);    
  96.                 HttpURLConnection conn = (HttpURLConnection) downloadUrl.openConnection();    
  97.                 conn.setRequestMethod("GET");    
  98.                 conn.setReadTimeout(5 * 1000);    
  99.                 /* 為 HTTP 設定 Range 屬性,可以指定伺服器返回資料的範圍 */
  100.                 conn.setRequestProperty("Range""bytes=" + startPosition + "-"
  101.                         + endPosition);    
  102.                 /* 將資料寫往本地檔案 */
  103.                 writeTo(accessFile, conn);    
  104.                 System.out.println("執行緒 '" + threadid + "'完成下載");    
  105.             } catch (IOException e) {    
  106.                 e.printStackTrace();    
  107.             } finally {    
  108.                 try {    
  109.                     if(accessFile != null) {    
  110.                         accessFile.close();    
  111.                     }    
  112.                  } catch (IOException ex) {    
  113.                      ex.printStackTrace();    
  114.                  }    
  115.             }    
  116.         }    
  117.         /**  
  118.          * 將下載資料寫往本地檔案  
  119.          */
  120.         privatevoid writeTo(RandomAccessFile accessFile,    
  121.                 HttpURLConnection conn){    
  122.             InputStream is = null;    
  123.             try {    
  124.                 is = conn.getInputStream();    
  125.                 byte[] buffer = newbyte[1024];    
  126.                 int len = -1;    
  127.                 while ((len = is.read(buffer)) != -1) {    
  128.                     accessFile.write(buffer, 0, len);    
  129. 相關推薦

    通過HTTP協議實現執行下載

    1. 基本原理,每條執行緒從檔案不同的位置開始下載,最後合併出完整的資料。  2. 使用多執行緒下載的好處      下載速度快。為什麼呢?很好理解,以往我是一條執行緒在伺服器上下載。也就是說,對應在伺服器上,有一個我的下載執行緒存在。      這時候肯定不只我一個人

    libcurl實現執行下載

           libcurl官網(http://curl.haxx.se/)是一個很強大網路功能的庫,支援當前DICT, FILE, FTP, FTPS, Gopher, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS,

    java使用 網路連線+RandomAccessFile + io 執行實現執行下載檔案並顯示實時網速

    下載檔案的時候,一個大檔案切成很多片,用多執行緒下載,速度會快很多 閱讀程式碼的時候注意檢視程式碼裡面的註釋 想用多執行緒下載檔案,則, 第一:得了解  RandomAccessFile  類,這是個隨機訪問檔案類,裡面可以設定 訪問的 開始地址和結束地址,且該類可讀可

    Android實現執行下載檔案,支援斷點

    本篇部落格主要介紹多執行緒去下載檔案,以下載多個app為例。不管去下在app,音視訊等檔案,實現起來都一樣。篇幅有點長,先貼張美女圖看看 正在下載的效果圖 下載完成效果圖 小編的下載路徑是放在sd卡的絕對路徑中,方便驗證! 工程目錄圖 介紹下每

    Android 實現執行下載檔案+斷點續傳

                                Android 多執行緒下載檔案+斷點續傳       在專案快要結束的時候,發現了app沒有版本更新的功能,於是找到一些過去的資料,在app上應用完成了版本更新,現在記錄一下apk的下載,也就是如何通過多執行緒將ap

    Android實現執行下載並顯示通知

    1、前言 相信大家都使用過一些可以下載檔案的 App,在下載列表中通常都會顯示一個進度條實時地更新下載進度,現在的下載都是斷點重傳的,也就是在暫停後,重新下載會依照之前進度接著下載。 我們這個下載的案例是有多個執行緒同時下載一個任務,並能提供多個檔案同時下載

    http檔案批量執行下載之winform

    這裡簡單的介紹一種從http獲取檔案然後下載到本地的方法,開始我用單執行緒下載,檔案多的情況下速度太慢了,後來就採用多執行緒,這裡琢磨了好久才整出來一個。這裡的部分程式碼是在部落格園找到的,具體是在哪裡不是很清楚了,搜尋關鍵詞大概是http下載檔案,感謝下。 1.關於多執行

    使用Apache HttpClient實現執行下載的小例子

      網上類似的文章很多,參考了很多人的,大部分人都是用URLConnection寫的。 原理一:HTTP多執行緒下載原理 1、傳送一個含有Rang頭的Head請求,如果返回狀態碼為206,則允許多執行緒下載 原理二:多執行緒下載原理 1、使用HttpClient的Head請求獲取請求檔案的資訊 2、傳送一個

    通過閉鎖方式實現執行同時併發測試

    閉鎖是一種同步工具類,可以延遲執行緒的進度直到其達到終止狀態。形象一點就是,閉鎖就是一扇關閉的大門,在閉鎖達到結束條件之前,這扇門是關閉的,沒有任何執行緒可以通過。而一旦條件達到,就像開閘洩洪一樣,萬馬奔騰,瞬間達到高併發。在此我希望模擬高併發的瞬間,而不是依次的啟動執行緒

    Java通過DelayQueue的實現執行任務的阻塞佇列

    2)例項程式碼,實現任務元素的延遲佇列://定義延遲佇列中的任務元素,可以通過extends實現不同的run方法; class DelayedTask implements Runnable,Delayed{ private final int delta; //差值訊號:用於記錄任務執行緒的延

    使用xutils實現執行下載

    這個開源專案在github可以下載到。 HttpUtils http = new HttpUtils(); /** * 2 進行下載 * url 下載的路徑 * target 存放目標地址 *

    實現執行下載同一個檔案

    原理: 示例程式碼: public class LoadFile { private static int threadCount = 3;// 下載的執行緒數量 public static

    java執行下載http協議檔案

    package com.wild.http; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream

    Http執行下載實現

    a、對於網路上的一個資源,首先發送一個請求,從返回的Content-Length中回去需要下載檔案的大小,然後根據檔案大小建立一個檔案。 b、根據執行緒數和檔案大小,為每個執行緒分配下載的位元組區間,然後每個執行緒向伺服器傳送請求,獲取這段位元組區間的檔案內容

    Java執行下載原理與實現

    多執行緒下載原理 客戶端要下載一個檔案, 首先請求伺服器,伺服器將這個檔案傳送給客戶端,客戶端儲存到本地, 完成了一個下載的過程. 多執行緒下載的思想是客戶端開啟多個執行緒同時下載,每個執行緒只負責下載檔案的一部分, 當所有執行緒下載完成的時候,檔案下載完畢.

    通過實現runnable實現執行操作

    第一步:建立一個抽象類,實現runnable介面。 public abstract class ThreadRun implements Runnable { @Override public void run() { doSomeThing();

    OkHttp實現執行併發下載的筆記

             打個廣告,不瞭解OkHttp的話可以先看下  http://blog.csdn.net/brycegao321/article/details/51830525             需求:  手機拍攝若干張照片, 在wifi連線下上傳到伺服器。  

    基於TCP協議實現Linux下客戶端與伺服器之間的通訊,實現執行程序伺服器

    TCP是TCP/IP協議族中一個比較重要的協議,這是一種可靠、建立連結、面向位元組流的傳輸,工作在傳輸層。和TCP相對的不可靠、無連結、面向資料報的協議UDP,瞭解UDP客戶端與伺服器之間通訊請戳UDP協議實現的伺服器與客戶端通訊 TCP協議建立連線 首

    Java執行下載技術實現

    多執行緒下載 多執行緒下載技術,簡單的說就是把要下載的檔案分成幾塊,由不同的執行緒來負責每一塊資料的下載任務。 技術要點 RandomAccessFile: Java中用來實現隨機訪問檔案的類 http Range請求頭 具體思路 1、檔案分塊。 檔案分塊大小(blockSize)= (

    linux 下基於特定通訊協議利用執行同步通訊機制實現的串列埠通訊

    </pre><pre name="code" class="cpp">/** *@Title:利用多執行緒同步通訊機制實現串列埠通訊 *@Introduce:主要完成根據特定的通訊協議實現串列埠與PC上特定串列埠 * 通訊軟體的通訊。測試版,只