国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學院 > 開發設計 > 正文

[轉載]http協議 文件下載原理及多線程斷點續傳

2019-11-14 23:10:12
字體:
來源:轉載
供稿:網友
[轉載]http協議 文件下載原理及多線程斷點續傳

最近研究了一下關于文件下載的相關內容,覺得還是寫些東西記下來比較好。起初只是想研究研究,但后來發現寫個可重用性比較高的模塊還是很有必要的,我想這也是大多數開發人員的習慣吧。對于HTTP協議,向服務器請求某個文件時,只要發送類似如下的請求即可:GET /Path/FileName HTTP/1.0Host: www.server.com:80Accept: */*User-Agent: GeneralDownloadapplicationConnection: close每行用一個“回車換行”分隔,末尾再追加一個“回車換行”作為整個請求的結束。第一行中的GET是HTTP協議支持的方法之一,方法名是大小寫敏感的,HTTP協議還支持OPTIONS、HAED、POST、PUT、DELETE、TRACE、CONNECT等方法,而GET和HEAD這兩個方法通常被認為是“安全的”,也就是說任何實現了HTTP協議的服務器程序都會實現這兩個方法。對于文件下載功能,GET足矣。GET后面是一個空格,其后緊跟的是要下載的文件從WEB服務器根開始的絕對路徑。該路徑后又有一個空格,然后是協議名稱及協議版本。除第一行以外,其余行都是HTTP頭的字段部分。Host字段表示主機名和端口號,如果端口號是默認的80則可以不寫。Accept字段中的*/*表示接收任何類型的數據。User-Agent表示用戶代理,這個字段可有可無,但強烈建議加上,因為它是服務器統計、追蹤以及識別客戶端的依據。Connection字段中的close表示使用非持久連接。關于HTTP協議更多的細節可以參考RFC2616(HTTP 1.1)。因為我只是想通過HTTP協議實現文件下載,所以也只看了一部分,并沒有看全。如果服務器成功收到該請求,并且沒有出現任何錯誤,則會返回類似下面的數據:HTTP/1.0 200 OKContent-Length: 13057672Content-Type: application/octet-streamLast-Modified: Wed, 10 Oct 2005 00:56:34 GMTAccept-Ranges: bytesETag: "2f38a6cac7cec51:160c"Server: Microsoft-IIS/6.0X-Powered-By: asp.netDate: Wed, 16 Nov 2005 01:57:54 GMTConnection: close不用逐一解釋,很多東西一看幾乎就明白了,只說我們大家都關心內容吧。第一行是協議名稱及版本號,空格后面會有一個三位數的數字,是HTTP協議的響應狀態碼,200表示成功,OK是對狀態碼的簡短文字描述。狀態碼共有5類:1xx屬于通知類;2xx屬于成功類;3xx屬于重定向類;4xx屬于客戶端錯誤類;5xx屬于服務端錯誤類。對于狀態碼,相信大家對404應該很熟悉,如果向一個服務器請求一個不存在的文件,就會得到該錯誤,通常瀏覽器也會顯示類似“HTTP 404 - 未找到文件”這樣的錯誤。Content-Length字段是一個比較重要的字段,它標明了服務器返回數據的長度,這個長度是不包含HTTP頭長度的。換句話說,我們的請求中并沒有Range字段(后面會說到),表示我們請求的是整個文件,所以Content-Length就是整個文件的大小。其余各字段是一些關于文件和服務器的屬性信息。這段返回數據同樣是以最后一行的結束標志(回車換行)和一個額外的回車換行作為結束,即“/r/n/r/n”。而“/r/n/r/n”后面緊接的就是文件的內容了,這樣我們就可以找到“/r/n/r/n”,并從它后面的第一個字節開始,源源不斷的讀取,再寫到文件中了。以上就是通過HTTP協議實現文件下載的全過程。但還不能實現斷點續傳,而實際上斷點續傳的實現非常簡單,只要在請求中加一個Range字段就可以了。假如一個文件有1000個字節,那么其范圍就是0-999,則:Range: bytes=500- 表示讀取該文件的500-999字節,共500字節。Range: bytes=500-599 表示讀取該文件的500-599字節,共100字節。Range還有其它幾種寫法,但上面這兩種是最常用的,對于斷點續傳也足矣了。如果HTTP請求中包含Range字段,那么服務器會返回206(Partial Content),同時HTTP頭中也會有一個相應的Content-Range字段,類似下面的格式:Content-Range: bytes 500-999/1000Content-Range字段說明服務器返回了文件的某個范圍及文件的總長度。這時Content-Length字段就不是整個文件的大小了,而是對應文件這個范圍的字節數,這一點一定要注意。一切好像基本上沒有什么問題了,本來我也是這么認為的,但事實并非如此。如果我們請求的文件的URL是類似http://www.server.com/filename.exe這樣的文件,則不會有問題。但是很多軟件下載網站的文件下載鏈接都是通過程序重定向的,比如pchome的ACDSee的HTTP下載地址是:http://download.pchome.net/php/tdownload2.php?sid=5547&url=/multimedia/viewer/acdc31sr1b051007.exe&svr=1&typ=0這種地址并沒有直接標識文件的位置,而是通過程序進行了重定向。如果向服務器請求這樣的URL,服務器就會返回302(Moved Temporarily),意思就是需要重定向,同時在HTTP頭中會包含一個Location字段,Location字段的值就是重定向后的目的URL。這時就需要斷開當前的連接,而向這個重定向后的服務器發請求。 好了,原理基本上就是這些了。其實裝個Sniffer好好分析一下,很容易就可以分析出來的。不過NetAnts也幫了我一些忙,它的文件下載日志對開發人員還是很有幫助的。

annegu做了一個簡單的Http多線程的下載程序,來討論一下多線程并發下載以及斷點續傳的問題。這個程序的功能,就是可以分多個線程從目標地址上下載數據,每個線程負責下載一部分,并可以支持斷點續傳和超時重連。下載的方法是download(),它接收兩個參數,分別是要下載的頁面的url和編碼方式。在這個負責下載的方法中,主要分了三個步驟。第一步是用來設置斷點續傳時候的一些信息的,第二步就是主要的分多線程來下載了,最后是數據的合并。1、多線程下載:

[java]view plaincopy
  1. publicStringdownload(StringurlStr,Stringcharset){
  2. this.charset=charset;
  3. longcontentLength=0;
  4. ①CountDownLatchlatch=newCountDownLatch(threadNum);
  5. long[]startPos=newlong[threadNum];
  6. longendPos=0;
  7. try{
  8. //從url中獲得下載的文件格式與名字
  9. this.fileName=urlStr.substring(urlStr.lastIndexOf("/")+1,urlStr.lastIndexOf("?")>0?urlStr.lastIndexOf("?"):urlStr.length());
  10. if("".equalsIgnoreCase(this.fileName)){
  11. this.fileName=UUID.randomUUID().toString();
  12. }
  13. this.url=newURL(urlStr);
  14. URLConnectioncon=url.openConnection();
  15. setHeader(con);
  16. //得到content的長度
  17. contentLength=con.getContentLength();
  18. //把context分為threadNum段的話,每段的長度。
  19. this.threadLength=contentLength/threadNum;
  20. //第一步,分析已下載的臨時文件,設置斷點,如果是新的下載任務,則建立目標文件。在第4點中說明。
  21. startPos=setThreadBreakpoint(fileDir,fileName,contentLength,startPos);
  22. //第二步,分多個線程下載文件
  23. ExecutorServiceexec=Executors.newCachedThreadPool();
  24. for(inti=0;i<threadNum;i++){
  25. //創建子線程來負責下載數據,每段數據的起始位置為(threadLength*i+已下載長度)
  26. startPos[i]+=threadLength*i;
  27. /*設置子線程的終止位置,非最后一個線程即為(threadLength*(i+1)-1)
  28. 最后一個線程的終止位置即為下載內容的長度*/
  29. if(i==threadNum-1){
  30. endPos=contentLength;
  31. }else{
  32. endPos=threadLength*(i+1)-1;
  33. }
  34. //開啟子線程,并執行。
  35. ChildThreadthread=newChildThread(this,latch,i,startPos[i],endPos);
  36. childThreads[i]=thread;
  37. exec.execute(thread);
  38. }
  39. try{
  40. //等待CountdownLatch信號為0,表示所有子線程都結束。
  41. ②latch.await();
  42. exec.shutdown();
  43. //第三步,把分段下載下來的臨時文件中的內容寫入目標文件中。在第3點中說明。
  44. tempFileToTargetFile(childThreads);
  45. }catch(InterruptedExceptione){
  46. e.PRintStackTrace();
  47. }
  48. }

首先來看最主要的步驟:多線程下載。首先從url中提取目標文件的名稱,并在對應的目錄創建文件。然后取得要下載的文件大小,根據分成的下載線程數量平均分配每個線程需要下載的數據量,就是threadLength。然后就可以分多個線程來進行下載任務了。在這個例子中,并沒有直接顯示的創建Thread對象,而是用Executor來管理Thread對象,并且用CachedThreadPool來創建的線程池,當然也可以用FixedThreadPool。CachedThreadPool在程序執行的過程中會創建與所需數量相同的線程,當程序回收舊線程的時候就停止創建新線程。FixedThreadPool可以預先新建參數給定個數的線程,這樣就不用在創建任務的時候再來創建線程了,可以直接從線程池中取出已準備好的線程。下載線程的數量是通過一個全局變量threadNum來控制的,默認為5。好了,這5個子線程已經通過Executor來創建了,下面它們就會各自為政,互不干涉的執行了。線程有兩種實現方式:實現Runnable接口;繼承Thread類。ChildThread就是子線程,它作為DownloadTask的內部類,繼承了Thread,它的構造方法需要5個參數,依次是一個對DownloadTask的引用,一個CountDownLatch,id(標識線程的id號),startPosition(下載內容的開始位置),endPosition(下載內容的結束位置)。這個CountDownLatch是做什么用的呢?現在我們整理一下思路,要實現分多個線程來下載數據的話,我們肯定還要把這多個線程下載下來的數據進行合。主線程必須等待所有的子線程都執行結束之后,才能把所有子線程的下載數據按照各自的id順序進行合并。CountDownLatch就是來做這個工作的。CountDownLatch用來同步主線程,強制主線程等待所有的子線程執行的下載操作完成。在主線程中,CountDownLatch對象被設置了一個初始計數器,就是子線程的個數5個,代碼①處。在新建了5個子線程并開始執行之后,主線程用CountDownLatch的await()方法來阻塞主線程,直到這個計數器的值到達0,才會進行下面的操作,代碼②處。對每個子線程來說,在執行完下載指定區間與長度的數據之后,必須通過調用CountDownLatch的countDown()方法來把這個計數器減1。2、在全面開啟下載任務之后,主線程就開始阻塞,等待子線程執行完畢,所以下面我們來看一下具體的下載線程ChildThread。

[java]view plaincopy
  1. publicclassChildThreadextendsThread{
  2. publicstaticfinalintSTATUS_HASNOT_FINISHED=0;
  3. publicstaticfinalintSTATUS_HAS_FINISHED=1;
  4. publicstaticfinalintSTATUS_HTTPSTATUS_ERROR=2;
  5. privateDownloadTasktask;
  6. privateintid;
  7. privatelongstartPosition;
  8. privatelongendPosition;
  9. privatefinalCountDownLatchlatch;
  10. //privateRandomaccessFiletempFile=null;
  11. privateFiletempFile=null;
  12. //線程狀態碼
  13. privateintstatus=ChildThread.STATUS_HASNOT_FINISHED;
  14. publicChildThread(DownloadTasktask,CountDownLatchlatch,intid,longstartPos,longendPos){
  15. super();
  16. this.task=task;
  17. this.id=id;
  18. this.startPosition=startPos;
  19. this.endPosition=endPos;
  20. this.latch=latch;
  21. try{
  22. tempFile=newFile(this.task.fileDir+this.task.fileName+"_"+id);
  23. if(!tempFile.exists()){
  24. tempFile.createNewFile();
  25. }
  26. }catch(IOExceptione){
  27. e.printStackTrace();
  28. }
  29. }
  30. publicvoidrun(){
  31. System.out.println("Thread"+id+"run...");
  32. HttpURLConnectioncon=null;
  33. InputStreaminputStream=null;
  34. BufferedOutputStreamoutputStream=null;
  35. longcount=0;
  36. longthreadDownloadLength=endPosition-startPosition;
  37. try{
  38. outputStream=newBufferedOutputStream(newFileOutputStream(tempFile.getPath(),true));
  39. }catch(FileNotFoundExceptione2){
  40. e2.printStackTrace();
  41. }
  42. ③for(intk=0;k<10;k++){
  43. if(k>0)
  44. System.out.println("Nowthread"+id+"isreconnect,startpositionis"+startPosition);
  45. try{
  46. //打開URLConnection
  47. con=(HttpURLConnection)task.url.openConnection();
  48. setHeader(con);
  49. con.setAllowUserInteraction(true);
  50. //設置連接超時時間為10000ms
  51. ④con.setConnectTimeout(10000);
  52. //設置讀取數據超時時間為10000ms
  53. con.setReadTimeout(10000);
  54. if(startPosition<endPosition){
  55. //設置下載數據的起止區間
  56. con.setRequestProperty("Range","bytes="+startPosition+"-"
  57. +endPosition);
  58. System.out.println("Thread"+id+"startPositionis"+startPosition);
  59. System.out.println("Thread"+id+"endPositionis"+endPosition);
  60. //判斷httpstatus是否為HTTP/1.1206PartialContent或者200OK
  61. //如果不是以上兩種狀態,把status改為STATUS_HTTPSTATUS_ERROR
  62. ⑤if(con.getResponseCode()!=HttpURLConnection.HTTP_OK
  63. &&con.getResponseCode()!=HttpURLConnection.HTTP_PARTIAL){
  64. System.out.println("Thread"+id+":code="
  65. +con.getResponseCode()+",status="
  66. +con.getResponseMessage());
  67. status=ChildThread.STATUS_HTTPSTATUS_ERROR;
  68. this.task.statusError=true;
  69. outputStream.close();
  70. con.disconnect();
  71. System.out.println("Thread"+id+"finished.");
  72. latch.countDown();
  73. break;
  74. }
  75. inputStream=con.getInputStream();
  76. intlen=0;
  77. byte[]b=newbyte[1024];
  78. while((len=inputStream.read(b))!=-1){
  79. outputStream.write(b,0,len);
  80. count+=len;
  81. ⑥startPosition+=len;
  82. //每讀滿4096個byte(一個內存頁),往磁盤上flush一下
  83. if(count%4096==0){
  84. ⑦outputStream.flush();
  85. }
  86. }
  87. System.out.println("countis"+count);
  88. if(count>=threadDownloadLength){
  89. status=ChildThread.STATUS_HAS_FINISHED;
  90. }
  91. ⑧outputStream.flush();
  92. outputStream.close();
  93. inputStream.close();
  94. con.disconnect();
  95. }else{
  96. status=ChildThread.STATUS_HAS_FINISHED;
  97. }
  98. System.out.println("Thread"+id+"finished.");
  99. latch.countDown();
  100. break;
  101. }catch(IOExceptione){
  102. try{
  103. ⑨outputStream.flush();
  104. ⑩TimeUnit.SECONDS.sleep(getSleepSeconds());
  105. }catch(InterruptedExceptione1){
  106. e1.printStackTrace();
  107. }catch(IOExceptione2){
  108. e2.printStackTrace();
  109. }
  110. continue;
  111. }
  112. }
  113. }
  114. }

在ChildThread的構造方法中,除了設置一些從主線程中帶來的id, 起始位置之外,就是新建了一個臨時文件用來存放當前線程的下載數據。臨時文件的命名規則是這樣的:下載的目標文件名+”_”+線程編號。現在讓我們來看看從網絡中讀數據是怎么讀的。我們通過URLConnection來獲得一個http的連接。有些網站為了安全起見,會對請求的http連接進行過濾,因此為了偽裝這個http的連接請求,我們給httpHeader穿一件偽裝服。下面的setHeader方法展示了一些非常常用的典型的httpHeader的偽裝方法。比較重要的有:Uer-Agent模擬從UbuntuFirefox瀏覽器發出的請求;Referer模擬瀏覽器請求的前一個觸發頁面,例如從skycn站點來下載軟件的話,Referer設置成skycn的首頁域名就可以了;Range就是這個連接獲取的流文件的起始區間。

[java]view plaincopy
  1. privatevoidsetHeader(URLConnectioncon){
  2. con.setRequestProperty("User-Agent","Mozilla/5.0(X11;U;linuxi686;en-US;rv:1.9.0.3)Gecko/2008092510Ubuntu/8.04(hardy)Firefox/3.0.3");
  3. con.setRequestProperty("Accept-Language","en-us,en;q=0.7,zh-cn;q=0.3");
  4. con.setRequestProperty("Accept-Encoding","aa");
  5. con.setRequestProperty("Accept-Charset","ISO-8859-1,utf-8;q=0.7,*;q=0.7");
  6. con.setRequestProperty("Keep-Alive","300");
  7. con.setRequestProperty("Connection","keep-alive");
  8. con.setRequestProperty("If-Modified-Since","Fri,02Jan200917:00:05GMT");
  9. con.setRequestProperty("If-None-Match","/"1261d8-4290-df64d224/"");
  10. con.setRequestProperty("Cache-Control","max-age=0");
  11. con.setRequestProperty("Referer","http://www.dianping.com");
  12. }

另外,為了避免線程因為網絡原因而阻塞,設置了ConnectTimeout和ReadTimeout,代碼④處。setConnectTimeout設置的連接的超時時間,而setReadTimeout設置的是讀取數據的超時時間,發生超時的話,就會拋出socketTimeout異常,兩個方法的參數都是超時的毫秒數。這里對超時的發生,采用的是等候一段時間重新連接的方法。整個獲取網絡連接并讀取下載數據的過程都包含在一個循環之中(代碼③處),如果發生了連接或者讀取數據的超時,在拋出的異常里面就會sleep一定的時間(代碼⑩處),然后continue,再次嘗試獲取連接并讀取數據,這個時間可以通過setSleepSeconds()方法來設置。我們在迅雷等下載工具的使用中,經常可以看到狀態欄會輸出類似“連接超時,等待*秒后重試”的話,這個就是通過ConnectTimeout,ReadTimeout來實現的。連接建立好之后,我們要檢查一下返回響應的狀態碼。常見的Http Response Code有以下幾種:a) 200 OK 一切正常,對GET和POST請求的應答文檔跟在后面。b) 206 Partial Content 客戶發送了一個帶有Range頭的GET請求,服務器完成。c) 404 Not Found 無法找到指定位置的資源。這也是一個常用的應答。d) 414 Request URI Too Long URI太長。e) 416 Requested Range Not Satisfiable 服務器不能滿足客戶在請求中指定的Range頭。f) 500 Internal Server Error 服務器遇到了意料不到的情況,不能完成客戶的請求。g) 503 Service Unavailable 服務器由于維護或者負載過重未能應答。例如,Servlet可能在數據庫連接池已滿的情況下返回503。在這些狀態里面,只有200與206才是我們需要的正確的狀態。所以在代碼⑤處,進行了狀態碼的判斷,如果返回不符合要求的狀態碼,則結束線程,返回主線程并提示報錯。假設一切正常,下面我們就要考慮從網絡中讀數據了。正如我之前在分析MySQL的數據庫驅動中看的一樣,網絡中發送數據都是以數據包的形式來發送的,也就是說不管是客戶端向服務器發出的請求數據,還是從服務器返回給客戶端的響應數據,都會被拆分成若干個小型數據包在網絡中傳遞,等數據包到達了目的地,網絡接口會依據數據包的編號來組裝它們,成為完整的比特數據。因此,我們可以想到在這里也是一樣的,我們用inputStream的read方法來通過網卡從網絡中讀取數據,并不一定一次就能把所有的數據包都讀完,所以我們要不斷的循環來從inputStream中讀取數據。Read方法有一個int型的返回值,表示每次從inputStream中讀取的字節數,如果把這個inputStream中的數據讀完了,那么就返回-1。Read方法最多可以有三個參數,byte b[]是讀取數據之后存放的目標數組,off標識了目標數組中存儲的開始位置,len是想要讀取的數據長度,這個長度必定不能大于b[]的長度。public synchronized int read(byte b[], int off, int len);我們的目標是要把目標地址的內容下載下來,現在分了5個線程來分段下載,那么這些分段下載的數據保存在哪里呢?如果把它們都保存在內存中是非常糟糕的做法,如果文件相當之大,例如是一個視頻的話,難道把這么大的數據都放在內存中嗎,這樣的話,萬一連接中斷,那前面下載的東西就都沒有了?我們當然要想辦法及時的把下載的數據刷到磁盤上保存下來。當用bt下載視頻的時候,通常都會有個臨時文件,當視頻完全下載結束之后,這個臨時文件就會被刪除,那么下次繼續下載的時候,就會接著上次下載的點繼續下載。所以我們的outputStream就是往這個臨時文件來輸出了。OutputStream的write方法和上面InputStream的read方法有類似的參數,byte b[]是輸出數據的來源,off標識了開始位置,len是數據長度。public synchronized void write(byte b[], int off, int len) throws IOException;在往臨時文件的outputStream中寫數據的時候,我會加上一個計數器,每滿4096個比特就往文件中flush一下(代碼⑦處)。對于輸出流的flush,有些要注意的地方,在程序中有三個地方調用了outputStream.flush()。第一個是在循環的讀取網絡數據并往outputStream中寫入的時候,每滿4096個byte就flush一下(代碼⑦處);第二個是循環之后(代碼⑧處),這時候正常的讀取寫入操作已經完成,但是outputStream中還有沒有刷入磁盤的數據,所以要flush一下才能關閉連接;第三個就是在異常中的flush(代碼⑨處),因為如果發生了連接超時或者讀取數據超時的話,就會直接跑到catch的exception中去,這個時候outputStream中的數據如果不flush的話,重新連接的時候這部分數據就會丟失了。另外,當拋出異常,重新連接的時候,下載的起始位置也要重新設置,所以在代碼⑥處,即每次從inputStream中讀取數據之后,startPosition就要重新設置,count標識了已經下載的字節數。3、現在每個分段的下載線程都順利結束了,也都創建了相應的臨時文件,接下來在主線程中會對臨時文件進行合并,并寫入目標文件,最后刪除臨時文件。這部分很簡單,就是一個對所有下載線程進行遍歷的過程。這里outputStream也有兩次flush,與上面類似,不再贅述。

[java]view plaincopy
  1. privatevoidtempFileToTargetFile(ChildThread[]childThreads){
  2. try{
  3. BufferedOutputStreamoutputStream=newBufferedOutputStream(
  4. newFileOutputStream(fileDir+fileName));
  5. //遍歷所有子線程創建的臨時文件,按順序把下載內容寫入目標文件中
  6. for(inti=0;i<threadNum;i++){
  7. if(statusError){
  8. for(intk=0;k<threadNum;k++){
  9. if(childThreads[k].tempFile.length()==0)
  10. childThreads[k].tempFile.delete();
  11. }
  12. System.out.println("本次下載任務不成功,請重新設置線程數。");
  13. break;
  14. }
  15. BufferedInputStreaminputStream=newBufferedInputStream(
  16. newFileInputStream(childThreads[i].tempFile));
  17. System.out.println("Nowisfile"+childThreads[i].id);
  18. intlen=0;
  19. longcount=0;
  20. byte[]b=newbyte[1024];
  21. while((len=inputStream.read(b))!=-1){
  22. count+=len;
  23. outputStream.write(b,0,len);
  24. if((count%4096)==0){
  25. outputStream.flush();
  26. }
  27. //b=newbyte[1024];
  28. }
  29. inputStream.close();
  30. //刪除臨時文件
  31. if(childThreads[i].status==ChildThread.STATUS_HAS_FINISHED){
  32. childThreads[i].tempFile.delete();
  33. }
  34. }
  35. outputStream.flush();
  36. outputStream.close();
  37. }catch(FileNotFoundExceptione){
  38. e.printStackTrace();
  39. }catch(IOExceptione){
  40. e.printStackTrace();
  41. }
  42. }

4、最后,說說斷點續傳,前面為了實現斷點續傳,在每個下載線程中都創建了一個臨時文件,現在我們就要利用這個臨時文件來設置斷點的位置。由于臨時文件的命名方式都是固定的,所以我們就專門找對應下載的目標文件的臨時文件,臨時文件中已經下載的字節數就是我們需要的斷點位置。startPos是一個數組,存放了每個線程的已下載的字節數。

[java]view plaincopy
  1. //第一步,分析已下載的臨時文件,設置斷點,如果是新的下載任務,則建立目標文件。
  2. privatelong[]setThreadBreakpoint(StringfileDir2,StringfileName2,
  3. longcontentLength,long[]startPos){
  4. Filefile=newFile(fileDir+fileName);
  5. longlocalFileSize=file.length();
  6. if(file.exists()){
  7. System.out.println("file"+fileName+"hasexists!");
  8. //下載的目標文件已存在,判斷目標文件是否完整
  9. if(localFileSize<contentLength){
  10. System.out.println("Nowdownloadcontinue...");
  11. //遍歷目標文件的所有臨時文件,設置斷點的位置,即每個臨時文件的長度
  12. FiletempFileDir=newFile(fileDir);
  13. File[]files=tempFileDir.listFiles();
  14. for(intk=0;k<files.length;k++){
  15. StringtempFileName=files[k].getName();
  16. //臨時文件的命名方式為:目標文件名+"_"+編號
  17. if(tempFileName!=null&&files[k].length()>0
  18. &&tempFileName.startsWith(fileName+"_")){
  19. intfileLongNum=Integer.parseInt(tempFileName
  20. .substring(tempFileName.lastIndexOf("_")+1,
  21. tempFileName.lastIndexOf("_")+2));
  22. //為每個線程設置已下載的位置
  23. startPos[fileLongNum]=files[k].length();
  24. }
  25. }
  26. }
  27. }else{
  28. //如果下載的目標文件不存在,則創建新文件
  29. try{
  30. file.createNewFile();
  31. }catch(IOExceptione){
  32. e.printStackTrace();
  33. }
  34. }
  35. returnstartPos;
  36. }

5、測試

[java]view plaincopy
  1. publicclassDownloadStartup{
  2. privatestaticfinalStringencoding="utf-8";
  3. publicstaticvoidmain(String[]args){
  4. DownloadTaskdownloadManager=newDownloadTask();
  5. StringurlStr="http://apache.freelamp.com/velocity/tools/1.4/velocity-tools-1.4.z
  6. downloadManager.setSleepSeconds(5);
  7. downloadManager.download(urlStr,encoding);
  8. }
  9. }

測試從apache下載一個velocity的壓縮包,臨時文件保留,看一下下載結果:

另:在測試從skycn下載軟件的過程中,碰到了一個錯誤:java.io.IOException: Server returned HTTP response code: 416 for URL: http://www.skycn.com/上網查了一下:416 Requested Range Not Satisfiable 服務器不能滿足客戶在請求中指定的Range頭,于是把threadNum改為1就可以了。這個下載功能現在只是完成了很基礎的一部分,最初的初衷就是為了演練一下CountdownLatch。CountdownLatch就是一個計數器,就像一個攔截的柵欄,用await()方法來把柵欄關上,線程就跑不下去了,只有等計數器減為0的時候,柵欄才會自動打開,被暫停的線程才會繼續運行。CountdownLatch的應用場景可以有很多,分段下載就是一個很好的例子。附件1是對應的java文件。


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 龙里县| 桃源县| 晋江市| 那坡县| 新绛县| 德州市| 托克逊县| 都江堰市| 昌都县| 玛曲县| 四平市| 沾化县| 大港区| 乐都县| 山阳县| 镇原县| 柳州市| 根河市| 彭山县| 永清县| 儋州市| 康定县| 壶关县| 金乡县| 英超| 刚察县| 五峰| 西盟| 阿图什市| 西畴县| 敦煌市| 雅江县| 仪征市| 元氏县| 紫金县| 疏附县| 梨树县| 桐庐县| 寿阳县| 河北区| 琼海市|