文件在下載的過程中,手動暫停或異常時,下載被中斷,此時如果需要恢復(fù)下載,又不想重新下載的話,那么就需要實現(xiàn)斷點續(xù)傳了,斷點續(xù)傳的意思就是,恢復(fù)下載時,文件從被中斷的位置繼續(xù)下載,而無需重新將文件重新下載,最大的好處便是節(jié)省時間以及網(wǎng)絡(luò)產(chǎn)生的流量了。要實現(xiàn)文件下載的斷點續(xù)傳,就必須首先需要明白Http斷點續(xù)傳的原理。
Http請求頭Range是斷點續(xù)傳的核心。
什么是Range
當(dāng)用戶在聽一首歌的時候,如果聽到一半(網(wǎng)絡(luò)下載了一半),網(wǎng)絡(luò)斷掉了,用戶需要繼續(xù)聽的時候,文件服務(wù)器不支持?jǐn)帱c的話,則用戶需要重新下載這個文件。而Range支持的話,客戶端應(yīng)該記錄了之前已經(jīng)讀取的文件范圍,網(wǎng)絡(luò)恢復(fù)之后,則向服務(wù)器發(fā)送讀取剩余Range的請求,服務(wù)端只需要發(fā)送客戶端請求的那部分內(nèi)容,而不用整個文件發(fā)送回客戶端,以此節(jié)省網(wǎng)絡(luò)帶寬。
HTTP1.1規(guī)范的Range的約定
服務(wù)端通過請求頭Range:bytes=start-end來判斷是否做Range請求,如果這個值存在并且有效,將返回206的響應(yīng)碼給客戶端,告知這個請求支持?jǐn)帱c續(xù)傳。Range指定的是一個閉合區(qū)間范圍(也可以是多個區(qū)間范圍,不需要連續(xù)),Range請求頭的格式規(guī)范如下:
Range: bytes = start-end,start2-end2,start3-end3,...,startN-endN
start-end 是一個閉合區(qū)間,即包含start到end的部分,所以下一個請求應(yīng)該以end+1開頭
例如:
Range: bytes=0-100 (0到100字節(jié)的數(shù)據(jù))
Range: bytes=40- (40字節(jié)以后的數(shù)據(jù))
Range: bytes=-500(最后一個500字節(jié)的數(shù)據(jù))
Range: bytes=0-0,-1 (第一個和最后一個字節(jié))
Range: bytes=500-600,800-999 (同時指定兩個范圍)
響應(yīng)頭
Content-Range:bytes 0-100/3103
服務(wù)器響應(yīng)了前(0-100)個字節(jié)的數(shù)據(jù),該資源一共有(3103)個字節(jié)大小。
Content-Length:101
表示這次請求,服務(wù)器響應(yīng)了101個字節(jié)數(shù)據(jù),我們通常也通過Content-Length去獲取整個資源文件的大小,作為分段下載的基礎(chǔ)
以HttpUrlConnection為例
URL url = new URL(fileUrl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(10 * 1000);
connection.setRequestProperty("Accept-Ranges", "bytes");
connection.setRequestProperty("Connection", "Keep-Alive");
//加上這個頭部,則可以防止出現(xiàn)getContentLength()為-1的問題
connection.setRequestProperty("Accept-Encoding", "identity");
//必須加這個頭部,否則無法返回正常支持?jǐn)帱c續(xù)傳的響應(yīng)碼206,這里取值范圍是從第一個字節(jié)到結(jié)束,所以是請求的是整個資源文件
connection.setRequestProperty("Range", "bytes=0-");
connection.connect();
//假如此請求支持?jǐn)帱c續(xù)傳的方式將返回206響應(yīng)碼,否則資源請求成功的時候返回正常的200
int code = connection.getResponseCode() ;
//獲取本次服務(wù)器響應(yīng)的字節(jié)長度
long fileLength = connection.getContentLength();
實現(xiàn)多線程斷點續(xù)傳的基本思路
RandomAccessFile
多線程如何操作同一個文件,分段寫入呢?了解一下RandomAccessFile
RandomAccessFile類的主要功能是完成隨機(jī)讀取功能,可以讀取指定位置的內(nèi)容。
之前的File類只是針對文件本身進(jìn)行操作的,而如果要想對文件內(nèi)容進(jìn)行操作,則可以使用RandomAccessFile類,此類屬于隨機(jī)讀取類,可以隨機(jī)讀取一個文件中指定位置的數(shù)據(jù)
構(gòu)造方法
public RandomAccessFile(File file, String mode)throws FileNotFoundException
public RandomAccessFile(String name, String mode) throws FileNotFoundException
mode 文件的打開模式
r:讀模式
w:只寫
rw:讀寫,如果使用此模式,如果此文件不存在,則會自動創(chuàng)建。
指定讀寫的位置
randomAccessFile.seek(startIndex);
HTTP請求頭Range
首先,訪問服務(wù)器資源,獲取資源文件的長度,即ContentLength,然后將ContentLength進(jìn)行劃分,假設(shè)ContentLength=1000,開辟4個線程去分段下載,每段下載250個字節(jié),那么每個線程的Range請求頭部應(yīng)該為
Thread1 Range:bytes=0-249
Thread2 Range:bytes=250-499
Thread3 Range:bytes=500-749
Thread4 Range:bytes=750-999
區(qū)間范圍由自己定義即可,不一定非要按等分
當(dāng)每個線程開始分段下載,訪問RandomAccessFile的開始位置就應(yīng)該是首字節(jié)的位置,如Thread2應(yīng)該如下設(shè)置
//Thread2
randomAccessFile.seek(250);
手動停止或出現(xiàn)中斷時,應(yīng)該保存上次寫入的最后一個字節(jié)位置,以作為下一次請求的開始。以上面作為例子,假設(shè)線程Thread2中斷時,寫入的最后一個字節(jié)的位置為350,那么恢復(fù)下載的時候,Thread2請求頭就應(yīng)該為
Range:bytes=351-499
此時Thread2訪問RandomAccessFile的開始位置應(yīng)該是351,即
//Thread2
randomAccessFile.seek(351);