原創博客,轉載請聯系博主!
本項目已托管到本人Git遠程庫:https://github.com/yue9944882/Snow
項目目標
Major Functionality
開發環境: CentOS7-Qt4
實現一個基於LINUX的多線程下載器,功能上仿造迅雷,主要有如下幾個功能:
(僅限HTTP協議)多線程下載遠程資源
(暫停/繼續功能)斷點續傳
項目主要技術
Major Technique
POSIX線程及其協作
TCP協議套接字編程
Qt界面實現
Qt 信號槽機制SIGNAL/SLOT
// Linux信號處理
項目構思
Major Architecture
貫穿整個使用過程的Qt的主界面對全局的若干個隊列進行操作,從而實現系統協作,其過程中使用若個同步鎖調配線程之間的協作與競爭。
為什麼這麼設計?
這麼設計最大的原因是為了實現POSIX線程與Qt封裝類之間的互動,POSIX線程是基於LINUX下的C語言實現的,其調用創建的入口必須是C-style聲明的函數,如果直接將這些函數聲明為Qt控件繼承類內靜態函數會破壞其封裝性(個人實踐證明,這麼做也是不可行的)。經過幾次徹底失敗之後,這個方法也是目前不多可行解決辦法之一。
為什麼使用POSIX線程?
Qt是可移植的項目環境,如果使用linux下獨有的FORK/VFORK系列函數,會局限程序運行環境,POSIX標准下的線程更通用,更廣泛。至於為什麼沒有使用QThread,我只能說不想用- -|||
下圖所示是整體模塊之間的聯系:
網絡及動態顯示控件部分架構 如下圖所示:
邏輯任務線程不直接參與下載,附屬線程封裝進邏輯任務中,對其他任務不可見,動態控件隊列隨著用戶的操作長度和順序會不斷發生改變,而邏輯下載任務隊列只會不斷生長,並且通過索引與動態控件一對一對應。
Qt主界面應用類架構 分別在與之對應的 *.ui 文件中定義,這裡暫不贅述,各個類之間的溝通是通過Qt的SIGNAL/SLOT 信號槽機制完成的。
POSIX鎖類架構 如下圖所示:
--- Declaration ------ global.h :extern聲明外部全局變量
| |
| --- global_t.h :C風格結構體定義,主要用於POSIX線程傳參
| |
| --- global_f.h :extern聲明外部全局函數
| |
| --- missionbar.h missioncheck.h :C++風格聲明動態控件
| |
| --- mainwindow.h newdialog.h :C++風格聲明靜態控件
|
|
|
--- Defination ----- global.cpp :全局變量定義
| |
| --- global_f.cpp :POSIX線程及日志系統- C風格函數定義
| |
| --- main.cpp :程序入口,程序環境初始化..
| |
| --- *.cpp :Qt庫繼承類定義
|
|
--- Makefile ------ SnowLINUX.pro :QMake 腳本
|
--- Makefile :自動化編譯腳本
代碼實現
Implementation
網絡下載部分
1. TCP套接字:
由於多線程下載器需要保證文件的完整性,我們選擇TCP協議下的套接字進行下載,每次下載首先發送一個HTTP-HEAD請求得到文件的長度和完整名稱並且聲明不使用gzip格式壓縮,否則無法多線程寫入!得到了文件完整的長度之後,根據線程數量為文件劃分段落,然後由若干個POSIX線程使用隨機上長的端口號和服務器80號端口進行TCP連接,再使用Linux內核提供的接口進行下載,每個任務維護一個更新寫入進程鎖,以更新當前進度/速度的實時信息,並競爭全局鎖刷新全局統計變量。
2. 無鎖數據結構:
使用Linux的原子文件讀/寫操作而不是標准文件操作,以保證多線程寫入的原子性和完整性,pwrite/pread函數是我們的最終選擇,由於讀寫原子性,文件描述符不需要鎖類保護同步性。
3. 日志系統
日志系統原本的設計是通過Linux-signal庫進行定時的任務日志更新,但是這樣給CPU帶來了太多額外的任務消耗,下載的速度也會造成不同程度減少,更重要的是這樣實現會破壞Qt繼承類的封裝性,因為signal_handler風格的函數必須是全局函數。我們采用的是”單次”日志,在用戶有需要的時候觸發日志記錄系統,相關函數見 global_f.cpp中的:
Init_log(),write_log(),read_log()系列函數,日志格式暫不贅述,為純ASCii編碼文件。
Qt類與全局鎖部分
1. 全局競爭鎖:
時間鎖( timeMutex )是為了主界面定時調用函數在獲取任務隊列中不斷異步更新進度的任務的執行時間,時間鎖是為了保證主界面和動態控件之間數據的同步,其中動態控件的數據由動態控件封裝的鎖保證其內部POSIX線程刷新數據的同步性。
表鎖(tableMutex) 是為了保證我們在進行刪除/重啟任務而導致全局表修改的同時,程序不會因為定時刷新曲線而錯誤訪問任務表導致的程序崩潰。
狀態鎖( finishMutex ) 是為了防止動態控件和任務表之間相互通過記錄對方索引而相互訪問時表內容修改導致的程序崩潰。
2. 全局協作鎖:
在啟動新任務的時候彈出的小QDialog窗口是個獨立的窗口,其內部空間的SIGNAL分別綁定到了主界面的槽(SLOT)和動態控件的槽中,然而兩邊的槽默認是同時進行調用的,而我們必須要求其順序,不然程序會崩潰。例如,在我們選擇文件路徑後開始下載任務,我們需要先調用主界面的槽來更新表做預備工作,然後才能創建動態控件及下載任務.
程序操作
Demostration
操作方式非常簡明: