JDownload: 一款可以從網絡上下載文件的小程序第四篇(整體架構描述)


一 前言

  時間過得真快,距離本系列博客第一篇的發布已經過去9個月了,本文是該系列的第四篇博客,將對JDownload做一個整體的描述與介紹。恩,先讓筆者把記憶拉回到2017年年初,那會筆者在看Unix環境高級編程這本書,其中有些章節是socket相關的,這引起了我很大的興趣。然后有一天,看着屏幕上正在下載文件的迅雷,突然靈光一閃,要不自己也寫個下載工具吧,正所謂學以致用嘛,然后網上簡單搜索了一下,發現是可行的,於是乎就開始着手實現之。該系列的第一篇博客實現了一個基本的http站點迷你下載工具,第二篇加入了斷點續傳功能,第三篇加入了多線程的功能。本篇將從總體上對JDownload做一個描述,以圖形的方式來展示一下JDownload的工作流程,因此筆者建議讀者先閱讀一下該系列的另外三篇博客:

  • JWebFileTrans: 一款可以從網絡上下載文件的小程序(一)  鏈接請點擊我
  • JWebFileTrans(JDownload): 一款可以從網絡上下載文件的小程序(二)  鏈接請點擊我
  • JWebFileTrans(JDownload): 一款可以從網絡上下載文件的小程序(三),多線程斷點下載  鏈接請點擊我

  GitHub代碼鏈接請點擊我

PS: 本篇博客是博客園用戶“cs小學生”的原創作品,轉載請注明原作者和原文鏈接,謝謝。

二  JDownload執行流程圖

 

   如上圖所示,描述了JDownload從快車官網上下載“快車”軟件的整個執行流程圖,圖中的數字標志了執行的先后順序。諸如6-1,6-2之類的表示這幾個步驟的關系比較緊密,6表示都屬於同一個大操作,后面的1,2,3,4,表示在這個大操作的內部的執行順序。在圖中每一個線程都會經歷6-1,2,3,4的這樣一個執行流過程,但是為了防止圖變得太過稠密而影響美觀,只有線程1完整標志出了6-1,2,3,4這幾個標識符,其他幾個線程也會經歷這幾個過程,但是圖中省略了這幾個標識符。當然對於步驟6,圖中的線程是並行執行的。

  接下來讓我們從標識符1開始,來走一遍整個下載過程:

1.  解析http鏈接

  首先是用戶輸入下載鏈接“http://www.flashget.com/apps/flashget3.7.0.1222cn.exe”給JDownload,然后如圖最上端的矩形框所示,JDownload會從這個鏈接里面解析出3個部分,分別是

  •  URL: www.flashget.com,通過該URL可以得到對應的ip地址
  • 服務端資源路徑:/apps/flashget3.7.0.1222cn.exe
  • 文件名:flashget3.7.0.1222cn.exe

   相信學過計算機網絡這門課程的童鞋應該記得:ip地址和端口號唯一標志了一台計算機上面的一個服務,所謂服務比如80端口對應的是http服務,端口號21對應的是ftp服務等等。想象一下,我們要下載的東西必定存儲在網絡中的某台計算機上,但是這台計算機上很可能存在着很多服務,比如http服務、ftp服務、郵箱服務,由於這些服務是用約定好的端口號來標志的,而我們已經知道下載的東西在http站點上,所以我們向端口號80請求下載文件即可。那么端口號80我們已經知道了,服務器的ip地址怎么找呢?其實linux庫函數getaddrinfo()就具備通過域名來查找ip地址的功能,具體使用請參考APUE.

  前面我們說過,通過GET可以向http站點請求下載文件,我們每一次向服務器請求文件的一部分,然后不斷地請求,最后就可以把整個文件下載下來。所以我們需要知道文件的大小是多少,每一次下載多少字節量的數據,這樣我們就可以知道總共需要下載多少次。文件的大小可以通過HEAD命令來向服務器查詢,每次下載的量我們可以自定義比如500kb等。Get、Head命令都會收到服務器發過來的一個描述文件信息的頭部,而對於Get來說,頭部數據后面緊跟着就是文件的真正數據,這正是我們要下載的目標。

1      sprintf(send_buffer,"GET %s",path);
2      strcat(send_buffer," HTTP/1.1\r\n");
3      strcat(send_buffer,"host: ");
4      strcat(send_buffer,host_ip);
5      strcat(send_buffer," : ");
6      strcat(send_buffer,port);
7      strcat(send_buffer,buffer_range);
8      strcat(send_buffer, "\r\nKeep-Alive: 200");
9      strcat(send_buffer,"\r\nConnection: Keep-Alive\r\n\r\n");

  上面的代碼便是構造Get命令的代碼,其中的path就是我們解析出來的服務端資源路徑,host_ip就是通過getaddrinfo(url)得到的ip地址,buffer_range表示要下載的文件的范圍,比如第1字節到第1000字節。這些格式化的信息要通過Linux send()函數發送給服務端,然后我們通過recv()函數接收服務端發送過來的響應。

2.  創建斷點文件

  什么是斷點文件呢?以大家最熟悉的迅雷為例,沒有下載完的文件,下次可以啟動迅雷接着下載,而不用從頭開始下載,其實是迅雷記住了上次下載中斷時的一些信息,而我們的斷點文件就是用來還原上次下載中斷時的現場,以便我們可以繼續上次結束的地方接着下載,也即斷點續傳。

  順着流程圖中的數字2我們來到一個矩形小方框,從框里的內容我們可以得知我們需要對文件進行分片,所謂的分片也即把整個文件分成N等分,然后其中的每m個等分組成一個task,這樣的話就會有N/m個task,后續將會創建一些線程,每一個線程負責下載其中的若干task. 為了完成這件事情,我們首先來到數字3標志的執行流:向服務器發起連接,發送HEAD請求、服務器響應請求向客戶端發送信息。根據這些信息,JDownlaod就可以對文件進行分片,然后創建斷點文件了。

  順着圖中數字4標志的箭頭,我們為快車創建了一個斷點文件:flashget.exe.jbp. 正如上文所述,斷點文件時用來恢復下載現場用的,因此要記錄一下關鍵信息。最開頭的部分記錄了文件的大小、被分成了多少個task(后續每一個線程會下載其中的1個或多個task)、每一個分片的大小、分片的數目。緊接着后面挨個描述了每一個task(或者part)的信息,以part-n為例,該task包含了第(n-1)*6到第n*6個分片,當前已經下載完成了1個分片,該task包含一個168字節的數據,168不足一個分片的大小,因此單獨拿出來描述(圖中的數據只是一個示意,並不是真實數據)。當然只有最后一個task才會出現不足一個分片的情況。顯然有了這些信息,不管下載的過程何時中斷,以及中斷多少次,我們都可以恢復現場。

  斷點文件創建完畢后,我們沿着圖中的數字5表示的箭頭,在本地磁盤創建同名文件,為后續接收socket發來的數據做准備。

3.  多線程下載

    從圖中我們可以看出,創建了3個線程用於下載文件,以線程1為例,從數字6-1處可以看到,先從斷點文件里讀取part-0處的信息,得知需要下載第1到6個分片,而且當前並沒有已經下載完畢的分片。於是沿着6-2,線程向服務端挨個請求這6個分片的數據。收到數據后沿着6-3將文件數據寫到對應的offset處。這里多個線程是並發寫入文件的,由於每一個線程寫入的范圍並沒有交集,所以不需要用鎖來保護數據的一致性。從圖中可以看出線程1將分片1,2寫入了文件中,緊接着沿着數字6-4線程應該更新斷點文件,具體是更新當前已經下載了多少個分片的那個字段,這樣如果文件沒有下載完畢,下次重新啟動的時候可以從接着已經下載的分片繼續下載,而不用重復下載。

  當然,未下載完成的文件,下次繼續下載時就不用執行數字1,2,3標志的過程了,而是直接讀取斷點文件,創建線程,每個線程從斷點文件里面讀取自己分配到的task,如果該task分配已下載完畢則忽略,否則接着上次下載的地方繼續下載。

三  結束語

    在前幾篇博客里面,我們以文字和代碼片段的形式敘述了JDownload的實現過程,而本篇博客以圖形的方式展示了JDownload的整體概貌,並且順着圖形走了一條完整的下載路徑。主要涉及到下載鏈接的解析、斷點文件的設計、多線程的運用。在未來可能會考慮添加FTP的支持。

    時間過得真快啊,距離寫關於JDownload的第一篇博客已經過去了9個月。

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


免責聲明!

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



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