利用HTTP協議實現檔案下載的多執行緒斷點續傳
作者 盧培培 http://blog.csdn.net/goodname008/archive/2006/01/02/568668.aspx
最近研究了一下關於檔案下載的相關內容,覺得還是寫些東西記下來比較好。起初只是想研究研究,但後來發現寫個可重用性比較高的模組還是很有必要的,我想這也是大多數開發人員的習慣吧。
對於HTTP協議,向伺服器請求某個檔案時,只要傳送類似如下的請求即可:
GET /Path/FileName HTTP/1.0
Host: www.server.com:80
Accept: */*
User-Agent: GeneralDownloadApplication
Connection: 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 OK
Content-Length: 13057672
Content-Type: application/octet-stream
Last-Modified: Wed, 10 Oct 2005 00:56:34 GMT
Accept-Ranges: bytes
ETag: "2f38a6cac7cec51:160c"
Server: Microsoft-IIS/6.0
X-Powered-By: ASP.NET
Date: Wed, 16 Nov 2005 01:57:54 GMT
Connection: 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/1000
Content-Range欄位說明伺服器返回了檔案的某個範圍及檔案的總長度。這時Content-Length欄位就不是整個檔案的大小了,而是對應檔案這個範圍的位元組數,這一點一定要注意。
一切好像基本上沒有什麼問題了,本來我也是這麼認為的,但事實並非如此。如果我們請求的檔案的URL是類似http://www.server.com/filename.exe這樣的檔案,則不會有問題。但是很多軟體下載網站的檔案下載連結都是通過程式重定向的,比如pchome的ACDSee的HTTP下載地址是:
這種地址並沒有直接標識檔案的位置,而是通過程式進行了重定向。如果向伺服器請求這樣的URL,伺服器就會返回302(Moved Temporarily),意思就是需要重定向,同時在HTTP頭中會包含一個Location欄位,Location欄位的值就是重定向後的目的URL。這時就需要斷開當前的連線,而向這個重定向後的伺服器發請求。
好了,原理基本上就是這些了。其實裝個Sniffer好好分析一下,很容易就可以分析出來的。不過NetAnts也幫了我一些忙,它的檔案下載日誌對開發人員還是很有幫助的。
我在寫這段程式時,一開始也僅僅是實現了檔案下載的功能,後來又考慮到回撥、HTTP重定向、多執行緒斷點續傳、統計BPS、允許獲得HTML錯誤頁等功能,程式碼越寫越長,不過還好,不是很恐怖,但要全部貼出來也不合適,所以只揀了些關鍵的。
介面是這樣設計的:
#ifndef __HTTP_DOWNLOAD_FILE_H__
#define __HTTP_DOWNLOAD_FILE_H__
#ifdef HTTPDOWNLOADFILE_EXPORTS
#define HTTPDOWNLOADFILE_API __declspec(dllexport)
#else
#define HTTPDOWNLOADFILE_API /*__declspec(dllimport)*/
#endif
#ifdef __cplusplus
extern "C" {
#endif
// HTTP Download File Error Codes
#define HTTPDF_OK 0x00000000
#define HTTPDF_ERROR_SOCKET 0x00000001
#define HTTPDF_ERROR_URL 0x00000002
#define HTTPDF_ERROR_CONNECT 0x00000003
#define HTTPDF_ERROR_REQUEST 0x00000004
#define HTTPDF_ERROR_FILE_IO 0x00000005
#define HTTPDF_ERROR_TIMEOUT 0x00000006
#define HTTPDF_ERROR_HTTP 0x00000007
#define HTTPDF_ERROR_BUFFER_SIZE 0x00000008
#define HTTPDF_ERROR_USER_CANCELED 0x00000009
#define HTTPDF_ERROR_INVALID_PARAMETER 0x0000000A
// HTTP Download File Flags
#define HTTPDF_FLAG_HTTP_ERROR_PAGE 0x00000001
#define HTTPDF_FLAG_CALLBACK 0x00000002
#define HTTPDF_FLAG_GET_FILE_SIZE_ONLY 0x00000004
#define HTTPDF_FLAG_DELETE_INVALID_FILE 0x00000008
#define HTTPDF_FLAG_RESUME_POSITION 0x00000010
#define HTTPDF_FLAG_END_POSITION 0x00000020
#define HTTPDF_FLAG_TIMEOUT 0x00000040
// HTTP Download File Status Codes
#define HTTPDF_STATUS_CONNECTING 0x00000001
#define HTTPDF_STATUS_DOWNLOADING 0x00000002
// HTTP Response Status Codes (from wininet.h)
#define HTTP_STATUS_CONTINUE 100 // OK to continue with request
#define HTTP_STATUS_SWITCH_PROTOCOLS 101 // server has switched protocols in upgrade header
#define HTTP_STATUS_OK 200 // request completed
#define HTTP_STATUS_CREATED 201 // object created, reason = new URI
#define HTTP_STATUS_ACCEPTED 202 // async completion (TBS)
#define HTTP_STATUS_PARTIAL 203 // partial completion
#define HTTP_STATUS_NO_CONTENT 204 // no info to return
#define HTTP_STATUS_RESET_CONTENT 205 // request completed, but clear form
#define HTTP_STATUS_PARTIAL_CONTENT 206 // partial GET furfilled
#define HTTP_STATUS_AMBIGUOUS 300 // server couldn't decide what to return
#define HTTP_STATUS_MOVED 301 // object permanently moved
#define HTTP_STATUS_REDIRECT 302 // object temporarily moved
#define HTTP_STATUS_REDIRECT_METHOD 303 // redirection w/ new access method
#define HTTP_STATUS_NOT_MODIFIED 304 // if-modified-since was not modified
#define HTTP_STATUS_USE_PROXY 305 // redirection to proxy, location header specifies proxy to use
#define HTTP_STATUS_REDIRECT_KEEP_VERB 307 // HTTP/1.1: keep same verb
#define HTTP_STATUS_BAD_REQUEST 400 // invalid syntax
#define HTTP_STATUS_DENIED 401 // access denied
#define HTTP_STATUS_PAYMENT_REQ 402 // payment required
#define HTTP_STATUS_FORBIDDEN 403 // request forbidden
#define HTTP_STATUS_NOT_FOUND 404 // object not found
#define HTTP_STATUS_BAD_METHOD 405 // method is not allowed
#define HTTP_STATUS_NONE_ACCEPTABLE 406 // no response acceptable to client found
#define HTTP_STATUS_PROXY_AUTH_REQ 407 // proxy authentication required
#define HTTP_STATUS_REQUEST_TIMEOUT 408 // server timed out waiting for request
#define HTTP_STATUS_CONFLICT 409 // user should resubmit with more info
#define HTTP_STATUS_GONE 410 // the resource is no longer available
#define HTTP_STATUS_LENGTH_REQUIRED 411 // the server refused to accept request w/o a length
#define HTTP_STATUS_PRECOND_FAILED 412 // precondition given in request failed
#define HTTP_STATUS_REQUEST_TOO_LARGE 413 // request entity was too large
#define HTTP_STATUS_URI_TOO_LONG 414 // request URI too long
#define HTTP_STATUS_UNSUPPORTED_MEDIA 415 // unsupported media type
#define HTTP_STATUS_RETRY_WITH 449 // retry after doing the appropriate action.
#define HTTP_STATUS_SERVER_ERROR 500 // internal server error
#define HTTP_STATUS_NOT_SUPPORTED 501 // required not supported
#define HTTP_STATUS_BAD_GATEWAY 502 // error response received from gateway
#define HTTP_STATUS_SERVICE_UNAVAIL 503 // temporarily overloaded
#define HTTP_STATUS_GATEWAY_TIMEOUT 504 // timed out waiting for gateway
#define HTTP_STATUS_VERSION_NOT_SUP 505 // HTTP version not supported
typedef struct tagDOWNLOADFILEPROGRESS
{
DWORD dwStatus;
DWORD dwHTTPStatusCode;
LPCTSTR lpszHostName;
LPCTSTR lpszFileRealURL;
WORD nPort;
LPCTSTR lpszFileName;
DWORD dwFileSize;
DWORD dwBytesWritten;
DWORD dwContentLength;
DWORD dwResumePos;
DWORD dwTimeElapsed;
LPVOID lpParameter;
} DOWNLOADFILEPROGRESS, *LPDOWNLOADFILEPROGRESS;
typedef BOOL (CALLBACK* LPFNHTTPDOWNLOADFILE)(LPDOWNLOADFILEPROGRESS lpProgress);
typedef struct tagHTTPDOWNLOADFILEINFO
{
DWORD cbSize;
DWORD dwFlags;
LPCTSTR lpszFileURL;
LPTSTR lpszFileSavePath;
DWORD dwPathLen;
LPCTSTR lpszFileErrorPage;
DWORD dwResumePos;
DWORD dwEndPos;
DWORD dwTimeOut;
DWORD dwHTTPStatusCode;
DWORD dwFileSize;
DWORD dwBytesWritten;
DWORD dwError;
LPVOID lpParameter;
LPVOID lpReserved;
LPFNHTTPDOWNLOADFILE pfnCallback;
} HTTPDOWNLOADFILEINFO, *LPHTTPDOWNLOADFILEINFO;
HTTPDOWNLOADFILE_API BOOL HTTPDownloadFile(LPHTTPDOWNLOADFILEINFO lpDownloadFileInfo);
#ifdef __cplusplus
}
#endif
#endif /* __HTTP_DOWNLOAD_FILE_H__ */
DLL只有一個匯出函式,即HTTPDownloadFile,該函式需要一個HTTPDOWNLOADFILEINFO結構的引數,結構的各個成員如下:
cbSize
該結構的大小,以位元組為單位。
必須正確設定該成員,否則會返回HTTPDF_ERROR_INVALID_PARAMETER錯誤。
dwFlags
標誌位的組合,可以是如下各標誌的邏輯加(|):
值
含義
HTTPDF_FLAG_HTTP_ERROR_PAGE
接收HTTP錯誤頁,如果指定該選項,則lpszFileErrorPage為儲存錯誤頁的路徑及檔名。在下載檔案過程中如果發生HTTP錯誤,則將伺服器返回的HTTP錯誤頁的內容寫入到該檔案中。
HTTPDF_FLAG_CALLBACK
指明pfnCallback成員有效,此時pfnCallback是指向回撥函式的指標,不能為空,否則會返回HTTPDF_ERROR_INVALID_PARAMETER錯誤。
HTTPDF_FLAG_GET_FILE_SIZE_ONLY
只獲得檔案大小,而不下載檔案。
HTTPDF_FLAG_DELETE_INVALID_FILE
刪除未下載成功的檔案。如果在下載過程中出現任何錯誤,則刪除本地下載的不完整的檔案。
HTTPDF_FLAG_RESUME_POSITION
指明dwResumePos成員有效,此時dwResumePos為繼續下載檔案的位置,即斷點位置。這樣將從該位置開始下載檔案,直到檔案結束。(斷點續傳)
HTTPDF_FLAG_END_POSITION
指明dwEndPos成員有效。此時dwResumePos和dwEndPos共同構成了一個區間,這個區間指明瞭要下載檔案的某一部分。這樣可以使多個執行緒同時下載檔案的各個部分。只有設定了HTTPDF_FLAG_RESUME_POSITION時,設定HTTPDF_FLAG_END_POSITION才有效,否則將忽略該標誌及dwEndPos成員。
HTTPDF_FLAG_TIMEOUT
指明dwTimeOut成員有效,dwTimeOut成員為超時時間,單位為毫秒(ms)。
lpszFileURL
以NULL結尾的字串,表示要下載的檔案的URL。不能為空。
lpszFileSavePath
以NULL結尾的字串,表示檔案儲存到本地的路徑。不能為空。
如果最後一個字元為“/”則將檔案儲存到該路徑下,檔名與伺服器上的檔名相同,否則將按該字串指定的檔名儲存。如果僅指定了路徑,則函式返回後該緩衝區儲存檔案路徑及檔名。如果緩衝區的大小不夠儲存檔案路徑及檔名,則函式呼叫失敗,此時dwPathLen為需要的緩衝區大小,以字元(TCHAR)為單位。
dwPathLen
lpszFileSavePath指向的緩衝區的大小,以字元(TCHAR)為單位。
lpszFileErrorPage
以NULL結尾的字串,表示儲存HTTP錯誤頁的檔案路徑及檔名。設定HTTPDF_FLAG_HTTP_ERROR_PAGE時才有效。
dwResumePos
繼續下載檔案的位置,即斷點位置。設定HTTPDF_FLAG_RESUME_POSITION時才有效。
dwEndPos
檔案的結束位置。同時設定HTTPDF_FLAG_RESUME_POSITION和HTTPDF_FLAG_END_POSITION時才有效,與dwResumePos構成一個區間,表示下載檔案的某一部分。
dwTimeOut
超時時間,以毫秒(ms)為單位。設定HTTPDF_FLAG_TIMEOUT時才有效。預設為60秒。
dwHTTPStatusCode
函式返回時,該成員儲存HTTP響應狀態碼。
dwFileSize
函式返回時,該成員儲存檔案大小。
dwBytesWritten
函式返回時,該成員儲存成功下載並寫入檔案的位元組數。
dwError
函式返回時,如果下載失敗,將該成員儲存錯誤碼。
pfnCallback
指向LPFNHTTPDOWNLOADFILE型別的函式的地址。設定HTTPDF_FLAG_CALLBACK函式時有效。
lpParameter
使用者自定義函式。用於傳給回撥函式。
lpReserved
保留。必須為空。
檔案下載成功時,函式返回TRUE。當函式返回FALSE時,表示檔案下載失敗,dwError成員儲存了錯誤碼:
值
含義
HTTPDF_OK
無錯誤,函式成功返回。
HTTPDF_ERROR_SOCKET
套接字(Socket)錯誤。
HTTPDF_ERROR_URL
非法的URL。或者lpszFileURL指定的檔案無法使用HTTP協議下載。
HTTPDF_ERROR_CONNECT
刪除未下載成功的檔案。如果在下載過程中出現任何錯誤,則刪除本地下載的不完整的檔案。
HTTPDF_ERROR_REQUEST
向伺服器發請求時失敗。
HTTPDF_ERROR_FILE_IO
檔案輸入輸出錯誤。
HTTPDF_ERROR_TIMEOUT
超時錯誤。
HTTPDF_ERROR_HTTP
HTTP錯誤。dwHTTPStatusCode儲存具體錯誤碼。
HTTPDF_ERROR_BUFFER_SIZE
緩衝區大小錯誤。lpszFileSavePath指向的緩衝不夠儲存檔案路徑及檔名。
HTTPDF_ERROR_USER_CANCELED
使用者取消了下載操作。
HTTPDF_ERROR_INVALID_PARAMETER
無效的引數。
pfnCallback指向LPFNHTTPDOWNLOADFILE型別的函式的地址,也就是回撥函式的指標。該回撥函式是這樣定義的:
BOOL WINAPI DownloadProgress(LPDOWNLOADFILEPROGRESS lpProgress);
DOWNLOADFILEPROGRESS結構的成員如下:
dwStatus
當前的下載狀態。
HTTPDF_STATUS_CONNECTING 表示正在連線伺服器。
HTTPDF_STATUS_DOWNLOADING 表示正在下載檔案。
dwHTTPStatusCode
同HTTPDOWNLOADFILEINFO結構的dwHTTPStatusCode成員。
lpszHostName
以NULL結尾的字串,表示伺服器的主機名稱。
lpszFileRealURL
以NULL結尾的字串,表示檔案在網路中的真實URL。
一般情況下和HTTPDOWNLOADFILEINFO結構的lpszFileURL相同,但當發生重定向時,該字串為檔案在網路中的真實URL。
nPort
伺服器埠號。通常情況下為80。
lpszFileName
以NULL結尾的字串,表示檔案儲存到本地的路徑及檔名。
dwFileSize
檔案大小。以位元組為單位。
dwBytesWritten
當前已經下載併成功寫入到檔案的位元組數。
dwContentLength
請求檔案的大小,以位元組為單位。
如果請求下載整個檔案,則dwContentLength與dwFileSize相同,否則會受到dwResumePos和dwEndPos的影響。
dwResumePos
同HTTPDOWNLOADFILEINFO結構的dwResumePos成員。
dwTimeElapsed
從下載檔案開始到當前經過的時間。以毫秒(ms)為單位。
與dwBytesWritten結合可以統計BPS。
lpParameter
使用者自定義引數。同HTTPDOWNLOADFILEINFO結構的lpParameter成員。
HTTPDownloadFile函式主要就是按照HTTP協議,通過Windows Sockets Functions(Windows套接字函式)直接向HTTP伺服器傳送請求,使得HTTP伺服器與客戶端進行良好的通訊,從而實現檔案下載功能。
其中,建立檔案的程式碼片斷如下:
// 建立檔案並將接收到的檔案內容儲存到本地
m_hFile = CreateFile(m_szFileName, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, NULL, NULL);
if (m_hFile == INVALID_HANDLE_VALUE)
{
m_pDownloadFileInfo->dwError = HTTPDF_ERROR_FILE_IO;
return FALSE;
}
正如大家看到的,檔案是以FILE_SHARE_WRITE的方式開啟的,這使得多個執行緒可以同時下載相同檔案的不同部分並寫入到本地。而HTTPDownloadFile函式本身是以同步方式執行的,也就是說當下載的檔案稍微大一些時,程式就會阻塞在這裡。之所以沒有提供非同步執行的選項,是因為當程式不同時,通常非同步執行的處理邏輯也會不同,因此這部分最好還是交給客戶程式設計師去做。
我為這個函式寫了一個簡單的呼叫例程,是個控制檯程式,介面不是很華麗,但卻是一個完整的多執行緒斷點續傳的例子。例程基本上仿照了NetAnts,同時啟動5個執行緒分別下載檔案的5個不同部分,任何一個執行緒出錯後都會進行重試並從斷點處繼續下載,最多重試5次。程式碼片斷如下:
HANDLE hEvent = NULL;
BOOL WINAPI DownloadProgress(LPDOWNLOADFILEPROGRESS lpProgress)
{
// 顯示檔案下載進度
if (lpProgress->dwStatus == HTTPDF_STATUS_CONNECTING)
{
_tprintf(_T("Thread %d Status: Connecting.../n"), lpProgress->lpParameter);
_tprintf(_T("URL: %s/n"), lpProgress->lpszFileRealURL);
_tprintf(_T("Host: %s/tPort: %d/n"), lpProgress->lpszHostName, lpProgress->nPort);
_tprintf(_T("Parameter: %d/n"), (DWORD)lpProgress->lpParameter);
}
else if (lpProgress->dwStatus == HTTPDF_STATUS_DOWNLOADING)
{
_tprintf(_T("Thread %d Status: Downloading.../n"), lpProgress->lpParameter);
_tprintf(_T("HTTP Status Code: %d/n"), lpProgress->dwHTTPStatusCode);
_tprintf(_T("FileName: %s/tFileSize: %d/n"), lpProgress->lpszFileName, lpProgress->dwFileSize);
_tprintf(_T("ResumePos: %d/t/tContentLength: %d/n"), lpProgress->dwResumePos, lpProgress->dwContentLength);
_tprintf(_T("Time Elapsed: %d/t/tBytes: %d/n"), lpProgress->dwTimeElapsed, lpProgress->dwBytesWritten);
_tprintf(_T("Average BPS: %d B/S/n"), lpProgress->dwBytesWritten / (lpProgress->dwTimeElapsed / 1000 + 1));
}
_tprintf(_T("/n"));
return TRUE;
}
DWORD WINAPI DownloadThread(LPVOID lpParameter)
{
HTTPDOWNLOADFILEINFO stDownloadFileInfo = *(HTTPDOWNLOADFILEINFO *)lpParameter;
// 通知主執行緒: 已完成引數複製, 可以啟動其它執行緒
SetEvent(hEvent);
// 下載檔案, 出錯後在斷點處繼續下載, 最多重試5次
DWORD dwTime = GetTickCount();
BOOL bResult = FALSE;
for (int i = 0; i < 5 && !bResult; i++)
{
stDownloadFileInfo.dwResumePos += stDownloadFileInfo.dwBytesWritten;
bResult = HTTPDownloadFile(&stDownloadFileInfo);
}
TCHAR szMessage[1024] = {0};
_stprintf(szMessage, _T("執行緒 %d 從 %d 到 %d./n寫入 %d 位元組./n錯誤: %d./n重試: %d./n耗時: %d ms."),
stDownloadFileInfo.lpParameter,
stDownloadFileInfo.dwResumePos,
stDownloadFileInfo.dwEndPos,
stDownloadFileInfo.dwBytesWritten,
stDownloadFileInfo.dwError,
i - 1,
GetTickCount() - dwTime);
MessageBox(NULL, szMessage, _T("完成"), MB_OK);
if (!bResult)
{
return -1;
}
return 0;
}
int main(int argc, char* argv[])
{
// 獲得伺服器上的檔案大小
TCHAR szFileURL[] = _T("http://localhost/download/wmp9.exe");
TCHAR szFileSavePath[MAX_PATH] = _T("c://");
HTTPDOWNLOADFILEINFO stDownloadFileInfo;
ZeroMemory(&stDownloadFileInfo, sizeof(stDownloadFileInfo));
stDownloadFileInfo.cbSize = sizeof(stDownloadFileInfo);
stDownloadFileInfo.dwFlags = HTTPDF_FLAG_GET_FILE_SIZE_ONLY | HTTPDF_FLAG_TIMEOUT;
stDownloadFileInfo.dwTimeOut = 30 * 1000;
stDownloadFileInfo.lpszFileURL = szFileURL;
stDownloadFileInfo.lpszFileSavePath = szFileSavePath;
stDownloadFileInfo.dwPathLen = sizeof(szFileSavePath) / sizeof(TCHAR);
BOOL bResult = HTTPDownloadFile(&stDownloadFileInfo);
if (!bResult)
{
_tprintf(_T("Get file size error: %d/n"), stDownloadFileInfo.dwError);
return -1;
}
// 檔案緩衝
HANDLE hFile = CreateFile(szFileSavePath, GENERIC_WRITE, NULL, NULL, OPEN_ALWAYS, NULL, NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
_tprintf(_T("File Error./n"));
return -1;
}
SetFilePointer(hFile, stDownloadFileInfo.dwFileSize, 0, FILE_BEGIN);
if (!SetEndOfFile(hFile))
{
_tprintf(_T("File Error./n"));
return -1;
}
CloseHandle(hFile);
const int nSegmentCount = 1;
HANDLE hThread[nSegmentCount] = {0};
DWORD dwSegmentSize = (stDownloadFileInfo.dwFileSize / (sizeof(hThread) / sizeof(hThread[0])));
stDownloadFileInfo.dwFlags &= ~HTTPDF_FLAG_GET_FILE_SIZE_ONLY;
stDownloadFileInfo.dwFlags |= HTTPDF_FLAG_RESUME_POSITION | HTTPDF_FLAG_END_POSITION | HTTPDF_FLAG_CALLBACK;
stDownloadFileInfo.pfnCallback = DownloadProgress;
// 多執行緒下載
DWORD dwTime = GetTickCount();
hEvent = CreateEvent(NULL, FALSE, FALSE, _T("ThreadReady"));
for (int i = 0; i < sizeof(hThread) / sizeof(hThread[0]); i++)
{
stDownloadFileInfo.lpParameter = (LPVOID)(i + 1);
stDownloadFileInfo.dwResumePos = i * dwSegmentSize;
if (i == sizeof(hThread) / sizeof(hThread[0]) - 1)
{
stDownloadFileInfo.dwEndPos = stDownloadFileInfo.dwFileSize - 1;
}
else
{
stDownloadFileInfo.dwEndPos = stDownloadFileInfo.dwResumePos + dwSegmentSize - 1;
}
hThread[i] = (HANDLE)_beginthreadex(NULL,
0,
(unsigned int (__stdcall *)(void *))DownloadThread,
&stDownloadFileInfo,
0,
NULL);
WaitForSingleObject(hEvent, INFINITE);
}
CloseHandle(hEvent);
// 等待所有下載執行緒結束
WaitForMultipleObjects(sizeof(hThread) / sizeof(hThread[0]), hThread, TRUE, INFINITE);
for (int j = 0; j < sizeof(hThread) / sizeof(hThread[0]); j++)
{
if (hThread[j])
{
DWORD dwExitCode = 0;
GetExitCodeThread(hThread[j], &dwExitCode);
_tprintf(_T("ExitCode of Thread %d: %d/n"), j + 1, dwExitCode);
CloseHandle(hThread[j]);
}
}
_tprintf(_T("Download Finished: %d ms/n"), GetTickCount() - dwTime);
return 0;
}
Main函式中的nSegmentCount就是檔案的分片數量,同時也是要啟動的執行緒數。HTTPDownloadFile函式和呼叫例程的完整原始碼可以在這裡下載:http://download.csdn.net/source/1079082
* 轉載請通知作者並註明出處,CSDN歡迎您! *
* 作者:盧培培(goodname008) *
* 郵箱:goodname008@163.com *
* 專欄:http://blog.csdn.net/goodname008 *
本文來自CSDN部落格,轉載請標明出處:http://blog.csdn.net/goodname008/archive/2006/01/02/568668.aspx
相關文章
- 圖解:HTTP 範圍請求,助力斷點續傳、多執行緒下載的核心原理圖解HTTP斷點執行緒
- Android多執行緒+單執行緒+斷點續傳+進度條顯示下載Android執行緒斷點
- Android中的多執行緒斷點續傳Android執行緒斷點
- 支援斷點續傳的大檔案傳輸協議斷點協議
- Java多執行緒檔案分片下載實現Java執行緒
- 多執行緒斷點下載原理執行緒斷點
- Java多執行緒下載器FileDownloader(支援斷點續傳、代理等功能)Java執行緒斷點
- 多執行緒下載檔案執行緒
- 使用curl斷點續傳下載檔案斷點
- 用Go語言實現多協程檔案上傳,斷點續傳,你如何實現?Go斷點
- Java實現檔案斷點續傳Java斷點
- 多執行緒下載nginx站點目錄下檔案執行緒Nginx
- Android下載檔案(一)下載進度&斷點續傳Android斷點
- 檔案下載之斷點續傳(客戶端與服務端的實現)斷點客戶端服務端
- requests如何友好地請求下載大檔案?requests實現分段下載、斷點續傳斷點
- Linux如何實現斷點續傳檔案功能?Linux斷點
- Android原生下載(下篇)多檔案下載+多執行緒下載Android執行緒
- JAVA實現大檔案分片上傳斷點續傳Java斷點
- Node.js實現大檔案斷點續傳Node.js斷點
- 擼了個多執行緒斷點續傳下載器,我從中學習到了這些知識執行緒斷點
- VUE-多檔案斷點續傳、秒傳、分片上傳Vue斷點
- 利用執行緒池給客戶端傳檔案執行緒客戶端
- .NET或.NET Core Web APi基於tus協議實現斷點續傳WebAPI協議斷點
- 【連載 02】多執行緒實現執行緒
- delphi多執行緒檔案複製怎麼實現執行緒
- Android斷點續傳下載器JarvisDownloaderAndroid斷點JAR
- Qt實現基於多執行緒的檔案傳輸(服務端,客戶端)QT執行緒服務端客戶端
- IDEA多執行緒下空指標斷點除錯Idea執行緒指標斷點除錯
- 多執行緒下載工具 NeatDownloadManager下載執行緒
- scp實現斷點續傳---rsync斷點
- 5招教你實現多執行緒場景下的執行緒安全!執行緒
- 使用多執行緒實現郵件傳送執行緒
- 通用的上傳下載(執行緒)執行緒
- 大檔案上傳、斷點續傳、秒傳、beego、vue斷點GoVue
- 1. 大檔案上傳如何斷點續傳斷點
- C# HTTP實現斷點續傳客戶端和服務端C#HTTP斷點客戶端服務端
- ftp多執行緒下載工具FTP執行緒
- Java多執行緒下載分析Java執行緒
- Feign實現檔案上傳下載