Java實現的斷點續傳功能
阿新 • • 發佈:2019-01-06
import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.RandomAccessFile; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors; /* * Encode:UTF-8 * * Author:zhiming.xu * * 多執行緒的斷點下載程式,根據輸入的url和指定執行緒數,來完成斷點續傳功能。 * * 每個執行緒支負責某一小段的資料下載;再通過RandomAccessFile完成資料的整合。 */ public class MultiTheradDownLoad { private String filepath = null; private String filename = null; privateString tmpfilename = null; private int threadNum = 0; private CountDownLatch latch = null;//設定一個計數器,程式碼內主要用來完成對快取檔案的刪除 private long fileLength = 0l; private long threadLength = 0l; private long[] startPos;//保留每個執行緒下載資料的起始位置。 private long[] endPos;//保留每個執行緒下載資料的截止位置。 private booleanbool = false; private URL url = null; //有參建構函式,先構造需要的資料 public MultiTheradDownLoad(String filepath, int threadNum) { this.filepath = filepath; this.threadNum = threadNum; startPos = new long[this.threadNum]; endPos = new long[this.threadNum]; latch = new CountDownLatch(this.threadNum); } /* * 組織斷點續傳功能的方法 */ public void downloadPart() { File file = null; File tmpfile = null; HttpURLConnection httpcon = null; //在請求url內獲取檔案資源的名稱;此處沒考慮檔名為空的情況,此種情況可能需使用UUID來生成一個唯一數來代表檔名。 filename = filepath.substring(filepath.lastIndexOf('/') + 1, filepath .contains("?") ? filepath.lastIndexOf('?') : filepath.length()); tmpfilename = filename + "_tmp"; try { url = new URL(filepath); httpcon = (HttpURLConnection) url.openConnection(); setHeader(httpcon); fileLength = httpcon.getContentLengthLong();//獲取請求資源的總長度。 file = new File(filename); tmpfile = new File(tmpfilename); threadLength = fileLength / threadNum;//每個執行緒需下載的資源大小。 System.out.println("fileName: " + filename + " ," + "fileLength= " + fileLength + " the threadLength= " + threadLength); if (file.exists() && file.length() == fileLength) { System.out .println("the file you want to download has exited!!"); return; } else { setBreakPoint(startPos, endPos, tmpfile); ExecutorService exec = Executors.newCachedThreadPool(); for (int i = 0; i < threadNum; i++) { exec.execute(new DownLoadThread(startPos[i], endPos[i], this, i, tmpfile, latch)); } latch.await();//當你的計數器減為0之前,會在此處一直阻塞。 exec.shutdown(); } } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } if (file.length() == fileLength) { if (tmpfile.exists()) { System.out.println("delect the temp file!!"); tmpfile.delete(); } } } /* * 斷點設定方法,當有臨時檔案時,直接在臨時檔案中讀取上次下載中斷時的斷點位置。沒有臨時檔案,即第一次下載時,重新設定斷點。 * * rantmpfile.seek()跳轉到一個位置的目的是為了讓各個斷點儲存的位置儘量分開。 * * 這是實現斷點續傳的重要基礎。 */ private void setBreakPoint(long[] startPos, long[] endPos, File tmpfile) { RandomAccessFile rantmpfile = null; try { if (tmpfile.exists()) { System.out.println("the download has continued!!"); rantmpfile = new RandomAccessFile(tmpfile, "rw"); for (int i = 0; i < threadNum; i++) { rantmpfile.seek(8 * i + 8); startPos[i] = rantmpfile.readLong(); rantmpfile.seek(8 * (i + 1000) + 16); endPos[i] = rantmpfile.readLong(); System.out.println("the Array content in the exit file: "); System.out.println("thre thread" + (i + 1) + " startPos:" + startPos[i] + ", endPos: " + endPos[i]); } } else { System.out.println("the tmpfile is not available!!"); rantmpfile = new RandomAccessFile(tmpfile, "rw"); //最後一個執行緒的截止位置大小為請求資源的大小 for (int i = 0; i < threadNum; i++) { startPos[i] = threadLength * i; if (i == threadNum - 1) { endPos[i] = fileLength; } else { endPos[i] = threadLength * (i + 1) - 1; } rantmpfile.seek(8 * i + 8); rantmpfile.writeLong(startPos[i]); rantmpfile.seek(8 * (i + 1000) + 16); rantmpfile.writeLong(endPos[i]); System.out.println("the Array content: "); System.out.println("thre thread" + (i + 1) + " startPos:" + startPos[i] + ", endPos: " + endPos[i]); } } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { try { if (rantmpfile != null) { rantmpfile.close(); } } catch (IOException e) { e.printStackTrace(); } } } /* * 實現下載功能的內部類,通過讀取斷點來設定向伺服器請求的資料區間。 */ class DownLoadThread implements Runnable { private long startPos; private long endPos; private MultiTheradDownLoad task = null; private RandomAccessFile downloadfile = null; private int id; private File tmpfile = null; private RandomAccessFile rantmpfile = null; private CountDownLatch latch = null; public DownLoadThread(long startPos, long endPos, MultiTheradDownLoad task, int id, File tmpfile, CountDownLatch latch) { this.startPos = startPos; this.endPos = endPos; this.task = task; this.tmpfile = tmpfile; try { this.downloadfile = new RandomAccessFile(this.task.filename, "rw"); this.rantmpfile = new RandomAccessFile(this.tmpfile, "rw"); } catch (FileNotFoundException e) { e.printStackTrace(); } this.id = id; this.latch = latch; } @Override public void run() { HttpURLConnection httpcon = null; InputStream is = null; int length = 0; System.out.println("the thread " + id + " has started!!"); while (true) { try { httpcon = (HttpURLConnection) task.url.openConnection(); setHeader(httpcon); //防止網路阻塞,設定指定的超時時間;單位都是ms。超過指定時間,就會丟擲異常 httpcon.setReadTimeout(20000);//讀取資料的超時設定 httpcon.setConnectTimeout(20000);//連線的超時設定 if (startPos < endPos) { //向伺服器請求指定區間段的資料,這是實現斷點續傳的根本。 httpcon.setRequestProperty("Range", "bytes=" + startPos + "-" + endPos); System.out .println("Thread " + id + " the total size:---- " + (endPos - startPos)); downloadfile.seek(startPos); if (httpcon.getResponseCode() != HttpURLConnection.HTTP_OK && httpcon.getResponseCode() != HttpURLConnection.HTTP_PARTIAL) { this.task.bool = true; httpcon.disconnect(); downloadfile.close(); System.out.println("the thread ---" + id + " has done!!"); latch.countDown();//計數器自減 break; } is = httpcon.getInputStream();//獲取伺服器返回的資源流 long count = 0l; byte[] buf = new byte[1024]; while (!this.task.bool && (length = is.read(buf)) != -1) { count += length; downloadfile.write(buf, 0, length); //不斷更新每個執行緒下載資源的起始位置,並寫入臨時檔案;為斷點續傳做準備 startPos += length; rantmpfile.seek(8 * id + 8); rantmpfile.writeLong(startPos); } System.out.println("the thread " + id + " total load count: " + count); //關閉流 is.close(); httpcon.disconnect(); downloadfile.close(); rantmpfile.close(); } latch.countDown();//計數器自減 System.out.println("the thread " + id + " has done!!"); break; } catch (IOException e) { e.printStackTrace(); } finally { try { if (is != null) { is.close(); } } catch (IOException e) { e.printStackTrace(); } } } } } /* * 為一個HttpURLConnection模擬請求頭,偽裝成一個瀏覽器發出的請求 */ private void setHeader(HttpURLConnection con) { con.setRequestProperty( "User-Agent", "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.3) Gecko/2008092510 Ubuntu/8.04 (hardy) Firefox/3.0.3"); con.setRequestProperty("Accept-Language", "en-us,en;q=0.7,zh-cn;q=0.3"); con.setRequestProperty("Accept-Encoding", "aa"); con.setRequestProperty("Accept-Charset", "ISO-8859-1,utf-8;q=0.7,*;q=0.7"); con.setRequestProperty("Keep-Alive", "300"); con.setRequestProperty("Connection", "keep-alive"); con.setRequestProperty("If-Modified-Since", "Fri, 02 Jan 2009 17:00:05 GMT"); con.setRequestProperty("If-None-Match", "\"1261d8-4290-df64d224\""); con.setRequestProperty("Cache-Control", "max-age=0"); con.setRequestProperty("Referer", "http://www.skycn.com/soft/14857.html"); } }