【HTML】Http分段下載詳解
阿新 • • 發佈:2017-10-31
多線程 ces 數值 alt locks www. 支持 read rand 三.Http協議的頭部Range介紹
Range字段是Http1.1開始新增加的,Http1.1和傳統的Http1.0相比,最大的特點就是解決1.0中不能支持多請求的缺點。判斷一個WEB服務器是否支持分段下載可以通過查看 返回頭是否有Accept-Ranges:Byte 字段。分段下載分為兩種,一種就是一次請求一個字段,一種就是一次請求多個字段。關於超文本傳輸的報文信息,讀者可以通過filter、burp或是瀏覽器的控制臺中查看報文的信息。
Range: bytes=1025- 獲取從1025開始到文件末尾所有的字節
Range: 0-0 獲取第一個字節
Range: -1 獲取最後一個字節
例如,在一個請求頭中有Range:byte=0-1024,那麽表示的意思就是請求數據的前1025個字節。
如果這個分段請求的返回碼是206,並且指示的分段範圍是0-1024,文件的總大小是7877,那麽在響應頭中的數據應該表示為:
Content-Range: bytes 0-1024/7877 例如:在請求頭出現Range:byte:0-1024,2000-3000,表示的含義就是請求前1025個字節信息,和從2000到3000字節的信息。
如果分段請求的返回狀態碼是206,那麽Content-Range的返回值和一次請求單個分段一樣。
一.為什麽需要Http分段下載
在實際的業務開發中,大文件使用Http普通下載非常容易OOM(內存溢出)或是鏈接超時的錯誤,這種情況下應該就應該考慮使用Http的分段下載了。下面筆者為你介紹,Http協議如何實現分段下載。
二.Http協議的結構介紹
在正式開始之前,這裏筆者先介紹一下Http的報文結構。Http的報文結構是由狀態行、頭部、空行、主體組成。在這裏讀者需要註意,GET請求和POST得到的報文結構是不一樣的,GET請求無請求數據,而POST請求有請求數據。關於Http的詳細信息,讀者可以參考Http協議詳解,而我們接下來討論的Range就是請求頭部中的一個字段。
Range字段是Http1.1開始新增加的,Http1.1和傳統的Http1.0相比,最大的特點就是解決1.0中不能支持多請求的缺點。判斷一個WEB服務器是否支持分段下載可以通過查看 返回頭是否有Accept-Ranges:Byte 字段。分段下載分為兩種,一種就是一次請求一個字段,一種就是一次請求多個字段。關於超文本傳輸的報文信息,讀者可以通過filter、burp或是瀏覽器的控制臺中查看報文的信息。
(一)一次請求一個分段
下面看一下Range字段常用表示的寫法:
Range: bytes=0-1024 獲取最前面1025個字節
Range: bytes=-500 獲取最後500個字節
Range: 0-0 獲取第一個字節
Range: -1 獲取最後一個字節
例如,在一個請求頭中有Range:byte=0-1024,那麽表示的意思就是請求數據的前1025個字節。
如果這個分段請求的返回碼是206,並且指示的分段範圍是0-1024,文件的總大小是7877,那麽在響應頭中的數據應該表示為:
Content-Range: bytes 0-1024/7877
(二)一次請求多個分段
多個分段和單個分段相差無幾,只需要在請求頭的分段中添加多個分段區域就可以了。
如果分段請求的返回狀態碼是206,那麽Content-Range的返回值和一次請求單個分段一樣。
四.使用Java實現文件分段下載
接下來筆者結合Java實現一個多線程分段下載的功能。使用Java多線程實現實現這個功能的大致思想,使用多個線程負責文件的子模塊的下載,每個線程都要記錄好下載的結束位置,以便於作為下個線程下載的開始位置。直接上代碼:
多線程的下載類:
import java.io.InputStream; import java.io.RandomAccessFile; import java.net.HttpURLConnection; import java.net.URL; import java.util.concurrent.CountDownLatch; public class MutiThreadDownLoad { /** * 同時下載的線程數 */ private int threadCount; /** * 服務器請求路徑 */ private String serverPath; /** * 本地路徑 */ private String localPath; /** * 線程計數同步輔助 */ private CountDownLatch latch; public MutiThreadDownLoad(int threadCount, String serverPath, String localPath, CountDownLatch latch) { this.threadCount = threadCount; this.serverPath = serverPath; this.localPath = localPath; this.latch = latch; } public void executeDownLoad() { try { URL url = new URL(serverPath); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setConnectTimeout(5000);//設置超時時間 conn.setRequestMethod("GET");//設置請求方式 int code = conn.getResponseCode(); if (code == 200) { //服務器返回的數據的長度,實際上就是文件的長度,單位是字節 int length = conn.getContentLength(); System.out.println("文件總長度:" + length + "字節(B)"); RandomAccessFile raf = new RandomAccessFile(localPath, "rwd"); //指定創建的文件的長度 raf.setLength(length); raf.close(); //分割文件 int blockSize = length / threadCount; for (int threadId = 1; threadId <= threadCount; threadId++) { //第一個線程下載的開始位置 int startIndex = (threadId - 1) * blockSize; int endIndex = startIndex + blockSize - 1; if (threadId == threadCount) { //最後一個線程下載的長度稍微長一點 endIndex = length; } System.out.println("線程" + threadId + "下載:" + startIndex + "字節~" + endIndex + "字節"); new DownLoadThread(threadId, startIndex, endIndex).start(); } } } catch (Exception e) { e.printStackTrace(); } } /** * 內部類用於實現下載 */ public class DownLoadThread extends Thread { /** * 線程ID */ private int threadId; /** * 下載起始位置 */ private int startIndex; /** * 下載結束位置 */ private int endIndex; public DownLoadThread(int threadId, int startIndex, int endIndex) { this.threadId = threadId; this.startIndex = startIndex; this.endIndex = endIndex; } @Override public void run() { try { System.out.println("線程" + threadId + "正在下載..."); URL url = new URL(serverPath); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET"); //請求服務器下載部分的文件的指定位置 conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex); conn.setConnectTimeout(5000); int code = conn.getResponseCode(); System.out.println("線程" + threadId + "請求返回code=" + code); InputStream is = conn.getInputStream();//返回資源 RandomAccessFile raf = new RandomAccessFile(localPath, "rwd"); //隨機寫文件的時候從哪個位置開始寫 raf.seek(startIndex);//定位文件 int len = 0; byte[] buffer = new byte[1024]; while ((len = is.read(buffer)) != -1) { raf.write(buffer, 0, len); } is.close(); raf.close(); System.out.println("線程" + threadId + "下載完畢"); //計數值減一 latch.countDown(); } catch (Exception e) { e.printStackTrace(); } } } }MutiThreadDownLoad
接下來是測試文件:
import java.util.concurrent.CountDownLatch; public class ClientTest{ public static void main(String[] args) { int threadSize = 4; String serverPath = "https://www.baidu.com"; String localPath = "NewsReader.apk"; CountDownLatch latch = new CountDownLatch(threadSize); MutiThreadDownLoad m = new MutiThreadDownLoad(threadSize, serverPath, localPath, latch); long startTime = System.currentTimeMillis(); try { m.executeDownLoad(); latch.await();//等待所有的線程執行完畢 } catch (InterruptedException e) { e.printStackTrace(); } long endTime = System.currentTimeMillis(); System.out.println("全部下載結束,共耗時" + (endTime - startTime) / 1000 + "s"); } }ClientTest
【HTML】Http分段下載詳解