雖然 Microsoft 提供的 WinInet API(InternetOpen、InternetConnect、FTPGetFile 等)能輕鬆實作基本 FTP 下載,但要達成多線程下載與斷點續傳,WinInet 的功能顯得力不從心。直接使用 Socket 依據 FTP 協定開發,雖然較繁瑣,卻能獲得最大彈性與控制權。本文將逐步解析完整開發流程,開發環境以 BCB 為例,VC++ 開發者可自行調整。
FTP 使用兩條連線:
| 指令 | 說明 |
|---|---|
| USER / PASS | 使用者帳號與密碼驗證 |
| TYPE | 設定傳輸類型(ASCII / Binary),如 TYPE I 代表二進位模式 |
| SIZE | 取得伺服器端檔案大小 |
| REST | 設定檔案傳輸續傳位移點(斷點續傳關鍵) |
| CWD / PWD | 變更 / 查詢目前工作目錄 |
| RETR | 下載檔案(從 REST 指定位移開始) |
| PASV | 進入被動模式,伺服器回傳 IP 與連接埠供客戶端連接資料通道 |
| PORT | 主動模式,客戶端指定 IP 與連接埠供伺服器連接 |
| QUIT | 結束連線 |
USER sandy
PASS sandy
TYPE I
SIZE sandy.txt
REST 100
CWD infor/
PWD temp/
RETR sandy.txt
PASV
PORT 192,168,1,81,4,50 // IP: 192.168.1.81, Port: 4*256+50=1074
作者採用雙檔案法:
.nam):預先配置與原檔同大小的空間(填 0),下載資料直接以 fseek 定位寫入對應位移。.san):記錄下載狀態,格式如下:D:\download\namelock.avi // 本地儲存路徑
364544 // 檔案總大小
5 // 線程數
0 // 已下載總位元組數
0-72908 // 線程1 任務範圍
72908-145816 // 線程2
145816-218724 // 線程3
218724-291632 // 線程4
291632-364544 // 線程5
下載過程中,各線程完成區塊後更新設定檔中的任務範圍與已下載總量。若某線程提前完成,會自動分擔最忙碌線程的剩餘任務(動態負載平衡),避免重複下載需注意緩衝區大小邊界。
使用全域旗標 FileLocked 搭配 Sleep(10) 簡易互斥(生產環境建議使用 Critical Section 或 Mutex)。
為避免頻繁讀寫設定檔,定義結構體在記憶體中維護狀態:
typedef struct FromToImpl {
DWORD from; // 任務起始位移
DWORD to; // 任務結束位移
} m_fromTo;
typedef struct InforImpl {
CString fileLoad; // 儲存路徑
DWORD fileSize; // 檔案大小
int threadCnt; // 線程數
DWORD alreadyDownloadCnt;// 已下載總量
FromToImpl *fromToImpl; // 各線程任務陣列
} m_inforImpl;
下載期間常駐記憶體,僅於啟動時載入、結束時寫回,大幅提升效能。
bool DealFile(string fileName) {
FILE *file = fopen(fileName.c_str(), "r+b");
if (!file) return false;
fseek(file, 0, SEEK_END);
DWORD fileSize = ftell(file);
fseek(file, 0, SEEK_SET);
const int MAX_BUFFER_LEN = 8192;
char *buffer = new char[MAX_BUFFER_LEN];
DWORD pos = 0, readLen;
do {
readLen = fread(buffer, 1, MAX_BUFFER_LEN, file);
if (readLen > 0) {
pos += readLen;
// 處理讀取資料(此處略)
}
} while (pos < fileSize);
delete[] buffer;
fclose(file);
return true;
}
本文完整揭露了基於 Socket 實作 FTP 多線程斷點續傳的核心技術:協定指令流程、雙檔案儲存策略、動態負載平衡、多線程同步與記憶體優化。這不僅能幫助理解 FlashGet 等下載工具的原理,對開發可重用網路元件也有極大參考價值。VC++ 開發者可參考邏輯移植至 MFC 或純 Win32 專案中。