JWebFileTrans(JDownload): 一款可以從網絡上下載文件的小程序(二)


一  前言

  本文是上一篇博客JWebFileTrans:一款可以從網絡上下載文件的小程序(一)的續集。此篇博客主要在上一篇的基礎上加入了斷點續傳的功能,用戶在下載中途停止下載后,下次可以讀取斷點文件,接着上次已經下載的部分文件繼續下載。另外將程序名從JWebFileTrans更改為JDownload,並從github的utility repository中獨立出來專門維護,后續會添加多線程、ftp下載等功能。JDownload的github鏈接請點擊JDownload源代碼 。

  另外時隔三個月后,我按照上一篇博客里面的四個下載鏈接再次測試的時候發現失敗了,原因在於其中有的http鏈接已經發生變化,比如stable目錄下的hbase從1.2.4升級成1.2.5了。然后我把其中有個鏈接中的1.2.4改成1.2.5結果也無法下載,后來發現是改成1.2.5后的鏈接是對的,但是服務器會把這個鏈接重定向到真正的下載鏈接。由於JDownload在設計過程中並沒有考慮到可能出現的重定向問題,所以對於此類鏈接暫時無法下載,但是在未來可能會考慮增加此類功能。所以大家在測試的時候選擇好正確的http鏈接,確保此鏈接當前存在,並且是真正的下載鏈接而不是重定向的鏈接,這樣才會測試成功。

  PS: 本文是github用戶junhuster,以及微博用戶http://weibo.com/junhuster  的原創作品,轉載請注明原作者和博客出處,謝謝。

二 斷點續傳功能展示

  測試鏈接 http://www.flashget.com/apps/flashget3.7.0.1222cn.exe ,這是快車下載軟件的官網下載鏈接,注意,如果讀者也用快車鏈接來做實驗的話,請先到快車官網檢查下最新的下載鏈接,因為諸如軟件升級改版本號等問題,就會導致本文給出的鏈接失效。在實驗的過程中,作者在虛擬機Ubuntu linux的終端shell里面先提交一個腳本從快車官網下載,等下載到1M左右的時候,終端輸入crtl-z中斷程序的執行。然后再次運行另外一個腳本,這個腳本會告訴JDownload 去讀取斷點文件,然后接着上次未完成的下載點繼續下載。下載成功后,把快車拷貝到windows 10操作系統里,然后點擊測試證明下載的文件可以正確執行。實驗過程的截圖如下:

  

  

 

  如上圖,從第一張圖中的終端可以看出,中途筆者執行了crtl-z中斷了執行,圖中后綴.jbp .jbp0是斷點文件,斷點續傳依賴這些斷點數據來繼續下載,.part0是未下載完成的快車軟件。在第二張圖中讀者可以看到作者在中斷了上一次的執行后,再一次執行另外一個腳本shell1.sh,(這兩個腳本請參加作者的github JDownload的test目錄),開始繼續上次的下載,直到完成。最后一張圖是把在ubuntu中下載的快車軟件復制的windows里面執行的畫面,以確保下載的軟件沒有出現錯誤。顯然從圖中可以看出安裝程序可以正常執行。

三 基本思路

  關於下載的功能設計部分請讀者參考作者的上一篇博客,或者直接參考github代碼,鏈接在前言中。本文主要描述如何在上一篇博客的基礎上加入斷點續傳的功能。

  其實斷點續傳的功能挺簡單的,無非就是記錄一下上次下載的位置,然后再次下載的時候從那個位置開始就行了。從上一篇博客可以了解到,在下載一個文件的時候,我們會按照一定的規則把這個文件划分為N等分,每一次向服務器請求1/N的數據。所以顯然為了支持斷點續傳,我們需要記錄該文件被等分的數目N, 因為是續傳我們還要記錄上一次已經下載了多少個等分。中斷下載后再一次鏈接時需要知道服務器的鏈接(或者ip),因此這些信息也是需要記錄的。因此在單線程下載的時候只需要用一個文件記錄這些信息就行了。然而未來要假如多線程的支持,那個時候一個文件會同時由M個線程同時下載,每一個線程下載這個文件的第i到第j個等分,對於每一個線程來說,都有可能下載中斷重新續傳的可能性,所以每一個線程所下載的那部分文件都需要記錄一份斷點信息。而且應該還有一份單獨的信息記錄總共文件被分成了幾份來下載(例如4個線程就相當於把文件分成了四份來下載),這樣下載中斷后,下載就可以挨個讀取所有的斷點文件來續傳。

  由以上描述我們可以設計出如下的數據結構:

  下面這個描述的是總體斷點的信息情況

 1 typedef struct break_point{
 2 
 3     long file_size;
 4     long num_of_part_file;
 5     long size_of_one_piece;
 6     long total_num_of_piece_of_whole_file;
 7     char server_ip[128+1];
 8     int  server_port;
 9 
10 }break_point;

  各個字段的含義是:

  • file_size: 文件大小,以字節為單位
  • num_of_part_file: 文件被分為多少份來下載,比如四個線程來下載就是四份
  • size_of_one_piece: 前文說過,一個大文件被分為很多小的等分,這個字段就表示每一個等分的大小,也是以字節為單位
  • total_num_of_piece_of_whole_file: 文件總共有多少等分
  • server_ip: 服務器ip地址
  • server_port: 服務器端口號

  下面這個是描述的是每一個線程下載的那部分文件對應的斷點信息,當前實際上只有一個線程,未來會加入多線程的支持

1 typedef struct break_point_part
2 {
3     long start_num_of_piece_of_this_part_file;
4     long end_num_of_piece_of_this_part_file;
5     long size_of_last_incomplete_piece;
6     long alread_download_num_of_piece;
7 }break_point_of_part;

  各個字段的含義是:

  • start_num_of_piece_of_this_part_file: 該部分文件對應的起始等分數,也即前文提到的第i個等分
  • end_num_of_piece_of_this_part_file: 分到的結束等分數,也即前文提到的第j個等分
  • size_of_last_incomplete_piece: 文件不一定能夠完全均分,最后一份取余數
  • alread_download_num_of_piece:已經下載了的等分數目

 

  假如,我們一開始啟用了四個線程來下載,那么就會生成一份break_point信息,四份break_point_of_part信息。

  在一開始的時候我們向服務器查詢要下載的文件的大小,然后根據自己代碼中每一次下載分片的大小,將文件記為N等分,這N等分將分給M個線程,每一個線程下載其中的N/M個等分。根據這些信息就可以創建出相應的斷點文件。然后每一個線程在每一次下載成功后就更新一下對應的斷點文件,主要是更新已經下載的等分數目。在中斷下載后,再一次下載的時候,程序首先會讀取break_point斷點文件,得到總共有多少個break_point_of_part斷點文件,然后挨個讀取break_point_of_part斷點文件,解析之,然后繼續上次未完成的地方繼續下載。

  由上所述,我們可以設計出如下幾個函數:

1 int Http_create_breakpoint_file(char *file_name, FILE **fp_breakpoint, long file_size, long num_of_part_file, long size_of_one_piece,
2                                                                                        long total_num_of_piece_of_whole_file,
3                                                                                        char *server_ip, int server_port);
4 int Http_create_breakpoint_part_file(char *file_name, FILE **fp_breakpoint_part, int part_num, long start_num_of_piece_of_this_part_file,
5                                                                                  long end_num_of_piece_of_this_part_file,
6                                                                                  long size_of_last_incompelet_piece,
7                                                                                  long alread_download_num_of_piece);
8 int Update_breakpoint_part_file(FILE *fp_breakpoint_part, int num_of_piece_tobe_added);
9 int Delete_breakpoint_file(char *file_name, FILE *fp);

 

四 代碼實現

  下載部分的代碼請參考上一篇博客或者github源代碼。這里主要描述斷點相關函數。

  首先是Http_create_breakpoint_file:

 1 int Http_create_breakpoint_file(char *file_name, FILE **fp_breakpoint, long file_size, long num_of_part_file, long size_of_one_piece,
 2                                                                                        long total_num_of_piece_of_whole_file,
 3                                                                                        char *server_ip, int server_port){
 4     /**
 5      ** check argument error
 6      */
 7      if(file_name==NULL || fp_breakpoint==NULL){
 8 
 9         printf("Http_create_breakpoint_file: argument error\n");
10         exit(0);
11      }
12 
13      char *break_point_file_name=(char *)malloc((strlen(file_name)+4+1)*sizeof(char));
14      if(NULL==break_point_file_name){
15 
16         printf("Http_create_breakpoint_file: malloc failed\n");
17         exit(0);
18 
19      }
20      strcpy(break_point_file_name,file_name);
21      strcat(break_point_file_name,".jbp");
22 
23      if(access(break_point_file_name,F_OK)==0){
24         int ret=remove(break_point_file_name);
25         if(ret!=0){
26             perror("Http_create_breakpoint_file,remove,\n");
27             exit(0);
28         }
29      }
30 
31      *fp_breakpoint=fopen(break_point_file_name,"w+");
32      if(NULL==*fp_breakpoint){
33         printf("Http_create_breakpoint_file: fopen failed\n");
34         exit(0);
35      }
36 
37      unsigned char *break_point_buffer=(unsigned char *)malloc(sizeof(break_point)+1000);
38      if(NULL==break_point_buffer){
39             printf("Http_create_breakpoint_file: malloc failed\n");
40             exit(0);
41      }
42 
43      ((break_point *)break_point_buffer)->file_size=file_size;
44      ((break_point *)break_point_buffer)->num_of_part_file=number_of_part_file;
45      ((break_point *)break_point_buffer)->size_of_one_piece=size_of_one_piece;
46      ((break_point *)break_point_buffer)->total_num_of_piece_of_whole_file=total_num_of_piece_of_whole_file;
47      ((break_point *)break_point_buffer)->server_port=server_port;
48 
49      memcpy(((break_point *)break_point_buffer)->server_ip, server_ip, strlen(server_ip));
50      ((break_point *)break_point_buffer)->server_ip[strlen(server_ip)]='\0';
51 
52      
53      int ret_fwrite=fwrite(break_point_buffer,sizeof(break_point),1,*fp_breakpoint);
54      fflush(*fp_breakpoint);
55 
56      if(ret_fwrite!=1){
57             printf("Http_create_breakpoint_file: fwrite failed \n");
58             exit(0);
59      }
60 
61      if(break_point_file_name!=NULL){
62         free(break_point_file_name);
63      }
64 
65      return 1;
66 }

  然后是Http_create_breakpoint_part_file:

 1 int Http_create_breakpoint_part_file(char *file_name, FILE **fp_breakpoint_part, int part_num, long start_num_of_piece_of_this_part_file,
 2                                                                                  long end_num_of_piece_of_this_part_file,
 3                                                                                  long size_of_last_incomplete_piece,
 4                                                                                  long alread_download_num_of_piece){
 5     if(file_name==NULL || fp_breakpoint_part==NULL || part_num<0){
 6         printf("Http_create_breakpoint_part_file, argument error\n");
 7         exit(0);
 8     }
 9 
10     char buffer_for_part_num[6];
11     sprintf(buffer_for_part_num, "%d",part_num);
12     int part_num_str_len=strlen(buffer_for_part_num);
13     char *break_point_part_file_name=(char *)malloc((strlen(file_name)+4+part_num_str_len+1)*sizeof(char));
14     if(break_point_part_file_name==NULL){
15         printf("Http_create_breakpoint_part_file,malloc failed\n");
16         exit(0);
17     }
18 
19     strcpy(break_point_part_file_name,file_name);
20     strcat(break_point_part_file_name,".jbp");
21     strcat(break_point_part_file_name,buffer_for_part_num);
22 
23     if(access(break_point_part_file_name,F_OK)==0){
24         int ret=remove(break_point_part_file_name);
25         if(ret!=0){
26             perror("Http_create_breakpoint_part_file,remove");
27             exit(0);
28         }
29     }
30 
31     *fp_breakpoint_part=fopen(break_point_part_file_name, "w+");
32     if(*fp_breakpoint_part==NULL){
33         printf("Http_create_breakpoint_part_file,fopen failed\n");
34         exit(0);
35     }
36 
37     break_point_of_part bpt;
38     bpt.start_num_of_piece_of_this_part_file=start_num_of_piece_of_this_part_file;
39     bpt.end_num_of_piece_of_this_part_file=end_num_of_piece_of_this_part_file;
40     bpt.size_of_last_incomplete_piece=size_of_last_incomplete_piece;
41     bpt.alread_download_num_of_piece=alread_download_num_of_piece;
42 
43 
44     int ret=fwrite(&bpt, sizeof(break_point_of_part), 1, *fp_breakpoint_part);
45     if(ret!=1){
46         printf("Http_create_breakpoint_part_file,fwrite, break_point_of_part,error\n");
47         exit(0);
48     }
49 
50     
51     fflush(*fp_breakpoint_part);
52 
53 
54     if(break_point_part_file_name!=NULL){
55         free(break_point_part_file_name);
56     }
57 
58     return 0;
59 
60 }

  接下來是int Update_breakpoint_part_file:

 1 int Update_breakpoint_part_file(FILE *fp_breakpoint_part, int num_of_piece_tobe_added){
 2 
 3     if(fp_breakpoint_part==NULL || num_of_piece_tobe_added<1){
 4         printf("Update_breakpoint_part_file,argument error\n");
 5         exit(0);
 6     }
 7 
 8     break_point_of_part *bpt=(break_point_of_part *)malloc(sizeof(break_point_of_part));
 9     if(bpt==NULL){
10         printf("Update_breakpoint_part_file,malloc failed\n");
11         exit(0);
12     }
13     fseek(fp_breakpoint_part, 0, SEEK_SET);
14     int ret_fread=fread(bpt, sizeof(break_point_of_part), 1, fp_breakpoint_part);
15 
16     int start_num=bpt->start_num_of_piece_of_this_part_file;
17     int end_num=bpt->end_num_of_piece_of_this_part_file;
18     bpt->alread_download_num_of_piece=bpt->alread_download_num_of_piece+num_of_piece_tobe_added;
19     if((bpt->alread_download_num_of_piece)<=(bpt->end_num_of_piece_of_this_part_file-bpt->start_num_of_piece_of_this_part_file+1+1)){   
20         fseek(fp_breakpoint_part, 0, SEEK_SET);
21         int ret=fwrite(bpt, sizeof(break_point_of_part), 1, fp_breakpoint_part);
22         if(ret!=1){
23             printf("Update_breakpoint_part_file,fwrite failed\n");
24             exit(0);
25         }
26         fflush(fp_breakpoint_part);
27     }else{
28         printf("Update_breakpoint_part_file, num_of_piece_tobe_added not correct\n");
29         exit(0);
30     }
31 
32 }

  最后是Delete_breakpoint_file,文件下載成功后,這些斷點文件應該刪除

 1 int Delete_breakpoint_file(char *file_name,FILE *fp){
 2 
 3     if(file_name==NULL || fp==NULL){
 4         printf("Delete_breakpoint_file, argument error\n");
 5         exit(0);
 6     }
 7 
 8     break_point *bp=(break_point *)malloc(sizeof(break_point));
 9     fseek(fp, 0, SEEK_SET);
10     int ret=fread(bp, sizeof(break_point), 1, fp);
11     if(ret!=1){
12         printf("Delete_breakpoint_file,fread failed\n");
13         exit(0);
14     }
15     int num=bp->num_of_part_file;
16 
17     char *buffer=(char *)malloc((strlen(file_name)+4+100));
18     for(int i=0;i<num;i++){
19 
20         char buffer_part[6];
21         sprintf(buffer_part, "%d",i);
22         strcpy(buffer,file_name);
23         strcat(buffer,".jbp");
24         strcat(buffer,buffer_part);
25 
26         if(access(buffer, F_OK)==0){
27             if(remove(buffer)!=0){
28                 perror("Delete_breakpoint_file,remove .jbp_num");
29                 exit(0);
30             }
31         }
32 
33     }
34 
35     
36 
37     fclose(fp);
38     strcpy(buffer, file_name);
39     strcat(buffer, ".jbp");
40     
41     if(access(buffer, F_OK)==0){
42         if(remove(buffer)!=0){
43             perror("Delete_breakpoint_file,remove .jbp");
44             exit(0);
45         }
46     }
47 
48     if(buffer!=NULL){
49         free(buffer);
50     }
51 
52     return 0;
53 }

 

注意,每一次重新續傳讀取斷點文件的時候模式不要設錯了,筆者之前每次讀取斷點文件以“w+”模式打開,結果每次下載完后,文件都是錯誤的,原因是w+模式打開文件,會把文件清零,這樣自然就出錯了。

 

五 結束語

  自此整篇文章就結束了,更詳細的信息請訪問筆者的github鏈接。

      聯系方式:https://github.com/junhuster/

         http://weibo.com/junhuster/

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM