C/C++通過HTTP實現(xiàn)文件上傳與下載的示例詳解
引言
WinInet(Windows Internet)是 Microsoft Windows 操作系統(tǒng)中的一個 API 集,用于提供對 Internet 相關(guān)功能的支持。它包括了一系列的函數(shù),使得 Windows 應(yīng)用程序能夠進(jìn)行網(wǎng)絡(luò)通信、處理 HTTP 請求、FTP 操作等。WinInet 提供了一套完整的網(wǎng)絡(luò)通信工具,使得開發(fā)者能夠輕松地構(gòu)建支持網(wǎng)絡(luò)功能的應(yīng)用程序,涵蓋了從簡單的 HTTP 請求到復(fù)雜的文件傳輸?shù)榷喾N網(wǎng)絡(luò)操作。
分解URL地址
InternetCrackUrl
函數(shù)可實現(xiàn)對URL字符串進(jìn)行解析,提取其中的協(xié)議、主機名、端口、路徑和其他信息,并將這些信息存儲在 URL_COMPONENTS
結(jié)構(gòu)中,方便后續(xù)的網(wǎng)絡(luò)操作,該函數(shù)是Windows下默認(rèn)提供的,函數(shù)與依賴結(jié)果如下所示;
函數(shù)原型
BOOL InternetCrackUrl( LPCTSTR lpszUrl, DWORD dwUrlLength, DWORD dwFlags, LPURL_COMPONENTS lpUrlComponents );
參數(shù)說明
lpszUrl
:指定待解析的 URL 字符串。dwUrlLength
:指定 URL 字符串的長度。dwFlags
:指定解析 URL 的標(biāo)志,可以是以下值之一:ICU_DECODE
:對 URL 進(jìn)行解碼。ICU_ESCAPE
:對 URL 進(jìn)行轉(zhuǎn)義。
lpUrlComponents
:一個指向URL_COMPONENTS
結(jié)構(gòu)的指針,用于存儲解析后的各個部分信息。
URL_COMPONENTS結(jié)構(gòu)
typedef struct { DWORD dwStructSize; LPTSTR lpszScheme; DWORD dwSchemeLength; INTERNET_SCHEME nScheme; LPTSTR lpszHostName; DWORD dwHostNameLength; INTERNET_PORT nPort; LPTSTR lpszUserName; DWORD dwUserNameLength; LPTSTR lpszPassword; DWORD dwPasswordLength; LPTSTR lpszUrlPath; DWORD dwUrlPathLength; LPTSTR lpszExtraInfo; DWORD dwExtraInfoLength; } URL_COMPONENTS, *LPURL_COMPONENTS;
返回值
如果函數(shù)成功,返回 TRUE
,并在 lpUrlComponents
結(jié)構(gòu)中存儲解析后的信息;如果失敗,返回 FALSE
。在失敗時,可以調(diào)用 GetLastError
函數(shù)獲取詳細(xì)的錯誤信息。
函數(shù)調(diào)用
#include <iostream> #include <Windows.h> #include <WinInet.h> #pragma comment(lib, "WinInet.lib") using namespace std; BOOL UrlCrack(char* pszUrl, char* pszScheme, char* pszHostName, char* pszUserName, char* pszPassword, char* pszUrlPath, char* pszExtraInfo, DWORD dwBufferSize) { BOOL bRet = FALSE; URL_COMPONENTS uc = { 0 }; // 初始化變量中的內(nèi)容 RtlZeroMemory(&uc, sizeof(uc)); RtlZeroMemory(pszScheme, dwBufferSize); RtlZeroMemory(pszHostName, dwBufferSize); RtlZeroMemory(pszUserName, dwBufferSize); RtlZeroMemory(pszPassword, dwBufferSize); RtlZeroMemory(pszUrlPath, dwBufferSize); RtlZeroMemory(pszExtraInfo, dwBufferSize); // 將長度填充到結(jié)構(gòu)中 uc.dwStructSize = sizeof(uc); uc.dwSchemeLength = dwBufferSize - 1; uc.dwHostNameLength = dwBufferSize - 1; uc.dwUserNameLength = dwBufferSize - 1; uc.dwPasswordLength = dwBufferSize - 1; uc.dwUrlPathLength = dwBufferSize - 1; uc.dwExtraInfoLength = dwBufferSize - 1; uc.lpszScheme = pszScheme; uc.lpszHostName = pszHostName; uc.lpszUserName = pszUserName; uc.lpszPassword = pszPassword; uc.lpszUrlPath = pszUrlPath; uc.lpszExtraInfo = pszExtraInfo; // 分解URL地址 bRet = InternetCrackUrl(pszUrl, 0, 0, &uc); if (FALSE == bRet) { return bRet; } return bRet; } int main(int argc, char* argv[]) { char szHttpDownloadUrl[] = "http://www.lyshark.com/index.php&username=lyshark&password=123"; // 對應(yīng)的變量 char szScheme[MAX_PATH] = { 0 }; char szHostName[MAX_PATH] = { 0 }; char szUserName[MAX_PATH] = { 0 }; char szPassword[MAX_PATH] = { 0 }; char szUrlPath[MAX_PATH] = { 0 }; char szExtraInfo[MAX_PATH] = { 0 }; // 初始化用0填充 RtlZeroMemory(szScheme, MAX_PATH); RtlZeroMemory(szHostName, MAX_PATH); RtlZeroMemory(szUserName, MAX_PATH); RtlZeroMemory(szPassword, MAX_PATH); RtlZeroMemory(szUrlPath, MAX_PATH); RtlZeroMemory(szExtraInfo, MAX_PATH); // 分解URL if (FALSE == UrlCrack(szHttpDownloadUrl, szScheme, szHostName, szUserName, szPassword, szUrlPath, szExtraInfo, MAX_PATH)) { return FALSE; } std::cout << szScheme << std::endl; std::cout << szHostName << std::endl; std::cout << szUserName << std::endl; std::cout << szPassword << std::endl; std::cout << szUrlPath << std::endl; std::cout << szExtraInfo << std::endl; system("pause"); return 0; }
運行代碼輸出特定網(wǎng)址的每個部分,如下圖所示;
下載頁面內(nèi)容
InternetOpen
用于初始化 WinINet 函數(shù)的使用。以下是該函數(shù)的原型:
HINTERNET InternetOpen( LPCWSTR lpszAgent, DWORD dwAccessType, LPCWSTR lpszProxyName, LPCWSTR lpszProxyBypass, DWORD dwFlags );
參數(shù)說明:
lpszAgent
: 指定應(yīng)用程序的名稱,用于標(biāo)識調(diào)用InternetOpen
的應(yīng)用程序。dwAccessType
: 指定訪問類型,可以是INTERNET_OPEN_TYPE_DIRECT
、INTERNET_OPEN_TYPE_PROXY
或INTERNET_OPEN_TYPE_PRECONFIG
中的一個。lpszProxyName
: 如果dwAccessType
是INTERNET_OPEN_TYPE_PROXY
,則指定代理服務(wù)器的名稱。否則,可以設(shè)為NULL
。lpszProxyBypass
: 如果dwAccessType
是INTERNET_OPEN_TYPE_PROXY
,則指定繞過代理服務(wù)器的地址。否則,可以設(shè)為NULL
。dwFlags
: 一些標(biāo)志,可以用來指定額外的行為,如INTERNET_FLAG_ASYNC
用于異步操作。
返回值:
如果函數(shù)調(diào)用成功,將返回一個類型為 HINTERNET
的句柄,用于后續(xù)的 WinINet 操作。如果函數(shù)調(diào)用失敗,返回 NULL
??梢允褂?nbsp;GetLastError
函數(shù)獲取詳細(xì)的錯誤信息。
InternetConnect
用于建立到遠(yuǎn)程服務(wù)器的連接。以下是該函數(shù)的原型:
HINTERNET InternetConnect( HINTERNET hInternet, LPCWSTR lpszServerName, INTERNET_PORT nServerPort, LPCWSTR lpszUserName, LPCWSTR lpszPassword, DWORD dwService, DWORD dwFlags, DWORD_PTR dwContext );
參數(shù)說明:
hInternet
: 調(diào)用InternetOpen
返回的句柄,表示連接的上下文。lpszServerName
: 要連接的服務(wù)器的名稱或 IP 地址。nServerPort
: 服務(wù)器的端口號。lpszUserName
: 連接服務(wù)器時要使用的用戶名,可以為NULL
。lpszPassword
: 連接服務(wù)器時要使用的密碼,可以為NULL
。dwService
: 指定服務(wù)類型,可以是INTERNET_SERVICE_FTP
、INTERNET_SERVICE_HTTP
或其他服務(wù)類型。dwFlags
: 一些標(biāo)志,用于指定連接的屬性,如INTERNET_FLAG_RELOAD
、INTERNET_FLAG_SECURE
等。dwContext
: 用戶定義的應(yīng)用程序上下文,將在回調(diào)函數(shù)中使用。
返回值:
如果函數(shù)調(diào)用成功,將返回一個類型為 HINTERNET
的句柄,表示連接的上下文。如果函數(shù)調(diào)用失敗,返回 NULL
??梢允褂?nbsp;GetLastError
函數(shù)獲取詳細(xì)的錯誤信息。
InternetConnect
用于建立連接后,可以使用返回的句柄執(zhí)行相關(guān)的協(xié)議操作,如 FTP 或 HTTP 操作。使用完連接后,同樣需要使用 InternetCloseHandle
函數(shù)關(guān)閉相應(yīng)的句柄,以釋放資源。
HttpOpenRequest
它是在使用 WinINet 庫進(jìn)行 HTTP 操作時的一部分。以下是該函數(shù)的原型:
HINTERNET HttpOpenRequest( HINTERNET hConnect, LPCWSTR lpszVerb, LPCWSTR lpszObjectName, LPCWSTR lpszVersion, LPCWSTR lpszReferrer, LPCWSTR *lplpszAcceptTypes, DWORD dwFlags, DWORD_PTR dwContext );
參數(shù)說明:
hConnect
: 調(diào)用InternetConnect
返回的連接句柄,表示請求的上下文。lpszVerb
: HTTP 請求方法,如 “GET”、“POST” 等。lpszObjectName
: 請求的對象名,通常是 URL 的路徑部分。lpszVersion
: HTTP 協(xié)議版本,通常是 “HTTP/1.1”。lpszReferrer
: 引用的來源,可以為NULL
。lplpszAcceptTypes
: 指定可接受的媒體類型,可以為NULL
。dwFlags
: 一些標(biāo)志,用于指定請求的屬性,如INTERNET_FLAG_RELOAD
、INTERNET_FLAG_SECURE
等。dwContext
: 用戶定義的應(yīng)用程序上下文,將在回調(diào)函數(shù)中使用。
返回值:
如果函數(shù)調(diào)用成功,將返回一個類型為 HINTERNET
的句柄,表示打開的 HTTP 請求。如果函數(shù)調(diào)用失敗,返回 NULL
??梢允褂?nbsp;GetLastError
函數(shù)獲取詳細(xì)的錯誤信息。
一旦打開了 HTTP 請求,可以使用返回的句柄執(zhí)行發(fā)送請求、接收響應(yīng)等操作。使用完請求后,同樣需要使用 InternetCloseHandle
函數(shù)關(guān)閉相應(yīng)的句柄,以釋放資源。
HttpSendRequest
用于發(fā)送 HTTP 請求的函數(shù),通常在使用 WinINet 庫進(jìn)行 HTTP 操作時調(diào)用。以下是該函數(shù)的原型:
BOOL HttpSendRequest( HINTERNET hRequest, LPCWSTR lpszHeaders, DWORD dwHeadersLength, LPVOID lpOptional, DWORD dwOptionalLength );
參數(shù)說明:
hRequest
: 調(diào)用HttpOpenRequest
返回的 HTTP 請求句柄,表示要發(fā)送請求的上下文。lpszHeaders
: 包含請求頭信息的字符串,可以為NULL
。dwHeadersLength
: 請求頭的長度,如果lpszHeaders
是NULL
,則可以為零。lpOptional
: 包含請求的可選數(shù)據(jù)的緩沖區(qū),可以為NULL
。dwOptionalLength
: 可選數(shù)據(jù)的長度,如果lpOptional
是NULL
,則可以為零。
返回值:
如果函數(shù)調(diào)用成功,返回非零值;如果函數(shù)調(diào)用失敗,返回零??梢允褂?nbsp;GetLastError
函數(shù)獲取詳細(xì)的錯誤信息。
HttpSendRequest
用于實際發(fā)送 HTTP 請求。在調(diào)用此函數(shù)之后,可以使用其他 WinINet 函數(shù)來讀取服務(wù)器的響應(yīng)。同樣,使用完請求后,需要使用 InternetCloseHandle
函數(shù)關(guān)閉相應(yīng)的句柄,以釋放資源。
HttpQueryInfo
用于檢索有關(guān) HTTP 請求或響應(yīng)的信息的函數(shù),通常在使用 WinINet 庫進(jìn)行 HTTP 操作時調(diào)用。以下是該函數(shù)的原型:
BOOL HttpQueryInfo( HINTERNET hRequest, DWORD dwInfoLevel, LPVOID lpBuffer, LPDWORD lpdwBufferLength, LPDWORD lpdwIndex );
參數(shù)說明:
hRequest
: 調(diào)用HttpOpenRequest
返回的 HTTP 請求句柄,表示要查詢信息的上下文。dwInfoLevel
: 指定要檢索的信息類型,可以是預(yù)定義的常量,如HTTP_QUERY_STATUS_CODE
、HTTP_QUERY_CONTENT_TYPE
等。lpBuffer
: 用于接收檢索到的信息的緩沖區(qū)。lpdwBufferLength
: 指向一個變量,表示lpBuffer
緩沖區(qū)的大小。在調(diào)用函數(shù)前,應(yīng)該將該變量設(shè)置為lpBuffer
緩沖區(qū)的大小。在調(diào)用函數(shù)后,該變量將包含實際寫入緩沖區(qū)的字節(jié)數(shù)。lpdwIndex
: 如果請求返回多個值,可以使用此參數(shù)指定要檢索的值的索引。對于單值的信息,可以將其設(shè)置為NULL
。
返回值:
如果函數(shù)調(diào)用成功,返回非零值;如果函數(shù)調(diào)用失敗,返回零??梢允褂?nbsp;GetLastError
函數(shù)獲取詳細(xì)的錯誤信息。
HttpQueryInfo
用于獲取與 HTTP 請求或響應(yīng)相關(guān)的信息,如狀態(tài)碼、內(nèi)容類型等。注意,在調(diào)用此函數(shù)之前,通常需要先調(diào)用 HttpSendRequest
發(fā)送請求。同樣,使用完請求后,需要使用 InternetCloseHandle
函數(shù)關(guān)閉相應(yīng)的句柄,以釋放資源。
InternetReadFile
用于從指定的句柄讀取數(shù)據(jù)的函數(shù),通常在使用 WinINet 庫進(jìn)行網(wǎng)絡(luò)操作時調(diào)用。以下是該函數(shù)的原型:
BOOL InternetReadFile( HINTERNET hFile, LPVOID lpBuffer, DWORD dwNumberOfBytesToRead, LPDWORD lpdwNumberOfBytesRead );
參數(shù)說明:
hFile
: 調(diào)用HttpOpenRequest
或FtpOpenFile
返回的句柄,表示要讀取數(shù)據(jù)的上下文。lpBuffer
: 用于接收讀取到的數(shù)據(jù)的緩沖區(qū)。dwNumberOfBytesToRead
: 指定要讀取的字節(jié)數(shù)。lpdwNumberOfBytesRead
: 指向一個變量,表示lpBuffer
緩沖區(qū)中實際讀取的字節(jié)數(shù)。在調(diào)用函數(shù)前,應(yīng)該將該變量設(shè)置為lpBuffer
緩沖區(qū)的大小。在調(diào)用函數(shù)后,該變量將包含實際讀取的字節(jié)數(shù)。
返回值:
如果函數(shù)調(diào)用成功,返回非零值;如果函數(shù)調(diào)用失敗,返回零??梢允褂?nbsp;GetLastError
函數(shù)獲取詳細(xì)的錯誤信息。
InternetReadFile
用于從網(wǎng)絡(luò)資源中讀取數(shù)據(jù),如從 HTTP 請求的響應(yīng)中讀取內(nèi)容。在調(diào)用此函數(shù)之前,通常需要先調(diào)用其他相關(guān)的函數(shù),如 HttpOpenRequest
、HttpSendRequest
和 HttpQueryInfo
。同樣,使用完資源后,需要使用 InternetCloseHandle
函數(shù)關(guān)閉相應(yīng)的句柄,以釋放資源。
下載頁面的完整代碼是這樣的,如下所示;
#include <iostream> #include <Windows.h> #include <WinInet.h> #pragma comment(lib, "WinInet.lib") using namespace std; BOOL UrlCrack(char* pszUrl, char* pszScheme, char* pszHostName, char* pszUserName, char* pszPassword, char* pszUrlPath, char* pszExtraInfo, DWORD dwBufferSize) { BOOL bRet = FALSE; URL_COMPONENTS uc = { 0 }; // 初始化變量中的內(nèi)容 RtlZeroMemory(&uc, sizeof(uc)); RtlZeroMemory(pszScheme, dwBufferSize); RtlZeroMemory(pszHostName, dwBufferSize); RtlZeroMemory(pszUserName, dwBufferSize); RtlZeroMemory(pszPassword, dwBufferSize); RtlZeroMemory(pszUrlPath, dwBufferSize); RtlZeroMemory(pszExtraInfo, dwBufferSize); // 將長度填充到結(jié)構(gòu)中 uc.dwStructSize = sizeof(uc); uc.dwSchemeLength = dwBufferSize - 1; uc.dwHostNameLength = dwBufferSize - 1; uc.dwUserNameLength = dwBufferSize - 1; uc.dwPasswordLength = dwBufferSize - 1; uc.dwUrlPathLength = dwBufferSize - 1; uc.dwExtraInfoLength = dwBufferSize - 1; uc.lpszScheme = pszScheme; uc.lpszHostName = pszHostName; uc.lpszUserName = pszUserName; uc.lpszPassword = pszPassword; uc.lpszUrlPath = pszUrlPath; uc.lpszExtraInfo = pszExtraInfo; // 分解URL地址 bRet = InternetCrackUrl(pszUrl, 0, 0, &uc); if (FALSE == bRet) { return bRet; } return bRet; } // 從響應(yīng)信息頭信息中獲取數(shù)據(jù)內(nèi)容長度大小 BOOL GetContentLength(char* pResponseHeader, DWORD* pdwContentLength) { int i = 0; char szContentLength[MAX_PATH] = { 0 }; DWORD dwContentLength = 0; char szSubStr[] = "Content-Length: "; RtlZeroMemory(szContentLength, MAX_PATH); // 在傳入字符串中查找子串 char* p = strstr(pResponseHeader, szSubStr); if (NULL == p) { return FALSE; } p = p + lstrlen(szSubStr); // 如果找到了就提取出里面的純數(shù)字 while (('0' <= *p) && ('9' >= *p)) { szContentLength[i] = *p; p++; i++; } // 字符串轉(zhuǎn)數(shù)字 dwContentLength = atoi(szContentLength); *pdwContentLength = dwContentLength; return TRUE; } // 數(shù)據(jù)下載 BOOL HttpDownload(char* pszDownloadUrl, BYTE** ppDownloadData, DWORD* pdwDownloadDataSize) { // 定義HTTP子變量 char szScheme[MAX_PATH] = { 0 }; char szHostName[MAX_PATH] = { 0 }; char szUserName[MAX_PATH] = { 0 }; char szPassword[MAX_PATH] = { 0 }; char szUrlPath[MAX_PATH] = { 0 }; char szExtraInfo[MAX_PATH] = { 0 }; // 填充為空 RtlZeroMemory(szScheme, MAX_PATH); RtlZeroMemory(szHostName, MAX_PATH); RtlZeroMemory(szUserName, MAX_PATH); RtlZeroMemory(szPassword, MAX_PATH); RtlZeroMemory(szUrlPath, MAX_PATH); RtlZeroMemory(szExtraInfo, MAX_PATH); // 拆解URL地址 if (FALSE == UrlCrack(pszDownloadUrl, szScheme, szHostName, szUserName, szPassword, szUrlPath, szExtraInfo, MAX_PATH)) { return FALSE; } // 數(shù)據(jù)下載 HINTERNET hInternet = NULL; HINTERNET hConnect = NULL; HINTERNET hRequest = NULL; DWORD dwOpenRequestFlags = 0; BOOL bRet = FALSE; unsigned char* pResponseHeaderIInfo = NULL; DWORD dwResponseHeaderIInfoSize = 2048; BYTE* pBuf = NULL; DWORD dwBufSize = 64 * 1024; BYTE* pDownloadData = NULL; DWORD dwDownloadDataSize = 0; DWORD dwRet = 0; DWORD dwOffset = 0; do { // 建立會話 hInternet = InternetOpen("WinInetGet/0.1", INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0); if (NULL == hInternet) { break; } // 建立連接 hConnect = InternetConnect(hInternet, szHostName, INTERNET_DEFAULT_HTTP_PORT, szUserName, szPassword, INTERNET_SERVICE_HTTP, 0, 0); if (NULL == hConnect) { break; } // 打開并發(fā)送HTTP請求 dwOpenRequestFlags = INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP | INTERNET_FLAG_KEEP_CONNECTION | INTERNET_FLAG_NO_AUTH | INTERNET_FLAG_NO_COOKIES | INTERNET_FLAG_NO_UI | INTERNET_FLAG_RELOAD; if (0 < lstrlen(szExtraInfo)) { lstrcat(szUrlPath, szExtraInfo); } // 以GET模式打開請求 hRequest = HttpOpenRequest(hConnect, "GET", szUrlPath, NULL, NULL, NULL, dwOpenRequestFlags, 0); if (NULL == hRequest) { break; } // 發(fā)送請求 bRet = HttpSendRequest(hRequest, NULL, 0, NULL, 0); if (FALSE == bRet) { break; } // 接收響應(yīng)的報文信息頭(Get Response Header) pResponseHeaderIInfo = new unsigned char[dwResponseHeaderIInfoSize]; if (NULL == pResponseHeaderIInfo) { break; } RtlZeroMemory(pResponseHeaderIInfo, dwResponseHeaderIInfoSize); // 查詢HTTP請求頭 bRet = HttpQueryInfo(hRequest, HTTP_QUERY_RAW_HEADERS_CRLF, pResponseHeaderIInfo, &dwResponseHeaderIInfoSize, NULL); if (FALSE == bRet) { break; } // 從字符串中 "Content-Length: " 網(wǎng)頁獲取數(shù)據(jù)長度 bRet = GetContentLength((char*)pResponseHeaderIInfo, &dwDownloadDataSize); // 輸出完整響應(yīng)頭 std::cout << pResponseHeaderIInfo << std::endl; if (FALSE == bRet) { break; } // 接收報文主體內(nèi)容(Get Response Body) pBuf = new BYTE[dwBufSize]; if (NULL == pBuf) { break; } pDownloadData = new BYTE[dwDownloadDataSize]; if (NULL == pDownloadData) { break; } RtlZeroMemory(pDownloadData, dwDownloadDataSize); do { RtlZeroMemory(pBuf, dwBufSize); // 循環(huán)讀入數(shù)據(jù)并保存在變量中 bRet = InternetReadFile(hRequest, pBuf, dwBufSize, &dwRet); if (FALSE == bRet) { break; } RtlCopyMemory((pDownloadData + dwOffset), pBuf, dwRet); dwOffset = dwOffset + dwRet; } while (dwDownloadDataSize > dwOffset); // 返回數(shù)據(jù) *ppDownloadData = pDownloadData; *pdwDownloadDataSize = dwDownloadDataSize; } while (FALSE); // 關(guān)閉并釋放資源 if (NULL != pBuf) { delete[]pBuf; pBuf = NULL; } if (NULL != pResponseHeaderIInfo) { delete[]pResponseHeaderIInfo; pResponseHeaderIInfo = NULL; } if (NULL != hRequest) { InternetCloseHandle(hRequest); hRequest = NULL; } if (NULL != hConnect) { InternetCloseHandle(hConnect); hConnect = NULL; } if (NULL != hInternet) { InternetCloseHandle(hInternet); hInternet = NULL; } return bRet; } // 創(chuàng)建并保存文件 BOOL SaveToFile(char* pszFileName, BYTE* pData, DWORD dwDataSize) { // 創(chuàng)建空文件 HANDLE hFile = CreateFile(pszFileName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_ARCHIVE, NULL); if (INVALID_HANDLE_VALUE == hFile) { return FALSE; } DWORD dwRet = 0; // 寫出數(shù)據(jù)到文件 WriteFile(hFile, pData, dwDataSize, &dwRet, NULL); // 關(guān)閉句柄保存文件 CloseHandle(hFile); return TRUE; }
使用時調(diào)用HttpDownload
實現(xiàn)數(shù)據(jù)下載,下載后的文件會保存在pHttpDownloadData
中,此時直接調(diào)用SaveToFile
將其保存在文件中即可;
int main(int argc, char* argv[]) { // 設(shè)置需要下載的地址 char szHttpDownloadUrl[] = "http://www.lyshark.com/index.html"; BYTE* pHttpDownloadData = NULL; DWORD dwHttpDownloadDataSize = 0; // HTTP下載 if (TRUE == HttpDownload(szHttpDownloadUrl, &pHttpDownloadData, &dwHttpDownloadDataSize)) { std::cout << "已保存文件,長度: " << dwHttpDownloadDataSize << " 字節(jié)"<< std::endl; } // 將下載數(shù)據(jù)保存成文件 SaveToFile((char *)"d://index.html", pHttpDownloadData, dwHttpDownloadDataSize); // 釋放內(nèi)存 delete[]pHttpDownloadData; pHttpDownloadData = NULL; system("pause"); return 0; }
運行后則可一輸出響應(yīng)頭Content-Length:
完整參數(shù)以及輸出的字節(jié)數(shù),如下圖所示;
上傳文件內(nèi)容
服務(wù)端,首先需要實現(xiàn)一個簡單的上傳接收功能,這里使用flask框架實現(xiàn),通過執(zhí)行pip install flask命令安裝這個庫,安裝成功以后手動保存為main.py文件,上傳文件是只需要向http://127.0.0.1/upload?file=推送數(shù)據(jù)即可,代碼如下;
from flask import Flask, request app = Flask(__name__) @app.route('/upload', methods=['POST']) def upload_file(): file = request.args.get('file') if not file: return "上傳錯誤,文件名未指定" try: with open(file, 'wb') as uploaded_file: uploaded_file.write(request.get_data()) if os.path.exists(file): return "上傳成功" else: return "上傳失敗" except Exception as e: return f"上傳失敗: {str(e)}" if __name__ == '__main__': app.run(debug=True, host='localhost', port=80)
服務(wù)端以管理員身份運行main.py
文件,此時會啟用一個Web服務(wù)器用于接收客戶端的上傳請求,如下圖所示;
接著是客戶端的實現(xiàn),首先介紹如下幾個關(guān)鍵API函數(shù);
HttpSendRequestEx
用于發(fā)送帶有附加選項的 HTTP 請求。相對于 HttpSendRequest
,它提供了更多的靈活性,允許在請求中包含額外的信息,例如頭部和數(shù)據(jù)。
以下是 HttpSendRequestEx
的原型:
BOOL HttpSendRequestEx( HINTERNET hRequest, LPINTERNET_BUFFERS lpBuffersIn, LPINTERNET_BUFFERS lpBuffersOut, DWORD dwFlags, DWORD_PTR dwContext );
參數(shù)說明:
hRequest
:由HttpOpenRequest
返回的句柄,表示 HTTP 請求。lpBuffersIn
:指向INTERNET_BUFFERS
結(jié)構(gòu)的指針,其中包含要作為請求的一部分發(fā)送的數(shù)據(jù)。lpBuffersOut
:指向INTERNET_BUFFERS
結(jié)構(gòu)的指針,用于接收響應(yīng)中接收到的數(shù)據(jù)。dwFlags
:附加標(biāo)志,控制函數(shù)的行為。這可以包括選項,如INTERNET_FLAG_RELOAD
、INTERNET_FLAG_SECURE
等。dwContext
:傳遞給回調(diào)函數(shù)的用戶定義的上下文值。
INTERNET_BUFFERS
是一個結(jié)構(gòu),允許您在 HTTP 請求和響應(yīng)中指定用于發(fā)送和接收數(shù)據(jù)的緩沖區(qū)。
使用 HttpSendRequestEx
需要謹(jǐn)慎處理內(nèi)存,并根據(jù)您的需求設(shè)置 INTERNET_BUFFERS
結(jié)構(gòu)的具體方式。
InternetWriteFile
用于將數(shù)據(jù)寫入到由 InternetOpenUrl
、InternetOpen
、HttpOpenRequest
或 FtpOpenFile
等函數(shù)打開的 URL、連接或文件。以下是該函數(shù)的原型:
BOOL InternetWriteFile( HINTERNET hFile, LPCVOID lpBuffer, DWORD dwNumberOfBytesToWrite, LPDWORD lpdwNumberOfBytesWritten );
參數(shù)說明:
hFile
: 調(diào)用InternetOpenUrl
、InternetOpen
、HttpOpenRequest
或FtpOpenFile
等函數(shù)返回的句柄,表示要寫入的文件、URL 或連接。lpBuffer
: 指向包含要寫入的數(shù)據(jù)的緩沖區(qū)的指針。dwNumberOfBytesToWrite
: 要寫入的字節(jié)數(shù)。lpdwNumberOfBytesWritten
: 指向一個變量,表示實際寫入的字節(jié)數(shù)。在調(diào)用函數(shù)前,應(yīng)該將該變量設(shè)置為緩沖區(qū)的大小。在調(diào)用函數(shù)后,該變量將包含實際寫入的字節(jié)數(shù)。
返回值:
如果函數(shù)調(diào)用成功,返回非零值;如果函數(shù)調(diào)用失敗,返回零。可以使用 GetLastError
函數(shù)獲取詳細(xì)的錯誤信息。
InternetWriteFile
主要用于將數(shù)據(jù)寫入網(wǎng)絡(luò)資源,如通過 HTTP 或 FTP 協(xié)議上傳文件。在調(diào)用此函數(shù)之前,通常需要先調(diào)用其他相關(guān)的函數(shù),如 InternetOpenUrl
、InternetOpen
、HttpOpenRequest
等。同樣,使用完資源后,需要使用 InternetCloseHandle
函數(shù)關(guān)閉相應(yīng)的句柄,以釋放資源。
HttpEndRequest
它通常與 HttpSendRequest
或 HttpSendRequestEx
配合使用,用于完成 HTTP 請求的發(fā)送,并準(zhǔn)備接收服務(wù)器的響應(yīng)。
以下是 HttpEndRequest
函數(shù)的原型:
BOOL HttpEndRequest( HINTERNET hRequest, LPINTERNET_BUFFERS lpBuffersOut, DWORD dwFlags, DWORD_PTR dwContext );
參數(shù)說明:
hRequest
: 調(diào)用HttpOpenRequest
、HttpOpenRequestEx
、HttpSendRequest
或HttpSendRequestEx
等函數(shù)返回的 HTTP 請求句柄。lpBuffersOut
: 指向一個INTERNET_BUFFERS
結(jié)構(gòu)的指針,該結(jié)構(gòu)用于傳遞關(guān)于響應(yīng)數(shù)據(jù)的信息。可以為NULL
。dwFlags
: 一些標(biāo)志,用于指定結(jié)束請求的選項。通常為 0。dwContext
: 用戶定義的應(yīng)用程序上下文,將在回調(diào)函數(shù)中使用。
返回值:
如果函數(shù)調(diào)用成功,返回非零值;如果函數(shù)調(diào)用失敗,返回零??梢允褂?nbsp;GetLastError
函數(shù)獲取詳細(xì)的錯誤信息。
HttpEndRequest
的主要作用是完成 HTTP 請求的發(fā)送,并在請求完成后準(zhǔn)備接收服務(wù)器的響應(yīng)。在調(diào)用此函數(shù)之后,通常會使用 InternetReadFile
函數(shù)等來讀取服務(wù)器的響應(yīng)數(shù)據(jù)。
上傳文件的完整代碼是這樣的,如下所示;
#include <iostream> #include <Windows.h> #include <WinInet.h> #pragma comment(lib, "WinInet.lib") using namespace std; // 切割路徑 BOOL UrlCrack(char* pszUrl, char* pszScheme, char* pszHostName, char* pszUserName, char* pszPassword, char* pszUrlPath, char* pszExtraInfo, DWORD dwBufferSize) { BOOL bRet = FALSE; URL_COMPONENTS uc = { 0 }; // 初始化變量中的內(nèi)容 RtlZeroMemory(&uc, sizeof(uc)); RtlZeroMemory(pszScheme, dwBufferSize); RtlZeroMemory(pszHostName, dwBufferSize); RtlZeroMemory(pszUserName, dwBufferSize); RtlZeroMemory(pszPassword, dwBufferSize); RtlZeroMemory(pszUrlPath, dwBufferSize); RtlZeroMemory(pszExtraInfo, dwBufferSize); // 將長度填充到結(jié)構(gòu)中 uc.dwStructSize = sizeof(uc); uc.dwSchemeLength = dwBufferSize - 1; uc.dwHostNameLength = dwBufferSize - 1; uc.dwUserNameLength = dwBufferSize - 1; uc.dwPasswordLength = dwBufferSize - 1; uc.dwUrlPathLength = dwBufferSize - 1; uc.dwExtraInfoLength = dwBufferSize - 1; uc.lpszScheme = pszScheme; uc.lpszHostName = pszHostName; uc.lpszUserName = pszUserName; uc.lpszPassword = pszPassword; uc.lpszUrlPath = pszUrlPath; uc.lpszExtraInfo = pszExtraInfo; // 分解URL地址 bRet = InternetCrackUrl(pszUrl, 0, 0, &uc); if (FALSE == bRet) { return bRet; } return bRet; } // 從響應(yīng)信息頭信息中獲取數(shù)據(jù)內(nèi)容長度大小 BOOL GetContentLength(char* pResponseHeader, DWORD* pdwContentLength) { int i = 0; char szContentLength[MAX_PATH] = { 0 }; DWORD dwContentLength = 0; char szSubStr[] = "Content-Length: "; RtlZeroMemory(szContentLength, MAX_PATH); // 在傳入字符串中查找子串 char* p = strstr(pResponseHeader, szSubStr); if (NULL == p) { return FALSE; } p = p + lstrlen(szSubStr); // 如果找到了就提取出里面的純數(shù)字 while (('0' <= *p) && ('9' >= *p)) { szContentLength[i] = *p; p++; i++; } // 字符串轉(zhuǎn)數(shù)字 dwContentLength = atoi(szContentLength); *pdwContentLength = dwContentLength; return TRUE; } // 數(shù)據(jù)上傳 BOOL HttpUpload(char* pszUploadUrl, BYTE* pUploadData, DWORD dwUploadDataSize) { // 初始化變量中的內(nèi)容 char szScheme[MAX_PATH] = { 0 }; char szHostName[MAX_PATH] = { 0 }; char szUserName[MAX_PATH] = { 0 }; char szPassword[MAX_PATH] = { 0 }; char szUrlPath[MAX_PATH] = { 0 }; char szExtraInfo[MAX_PATH] = { 0 }; // 將長度填充到結(jié)構(gòu)中 RtlZeroMemory(szScheme, MAX_PATH); RtlZeroMemory(szHostName, MAX_PATH); RtlZeroMemory(szUserName, MAX_PATH); RtlZeroMemory(szPassword, MAX_PATH); RtlZeroMemory(szUrlPath, MAX_PATH); RtlZeroMemory(szExtraInfo, MAX_PATH); // 分解URL地址 if (FALSE == UrlCrack(pszUploadUrl, szScheme, szHostName, szUserName, szPassword, szUrlPath, szExtraInfo, MAX_PATH)) { return FALSE; } // 數(shù)據(jù)上傳 HINTERNET hInternet = NULL; HINTERNET hConnect = NULL; HINTERNET hRequest = NULL; DWORD dwOpenRequestFlags = 0; BOOL bRet = FALSE; DWORD dwRet = 0; unsigned char* pResponseHeaderIInfo = NULL; DWORD dwResponseHeaderIInfoSize = 2048; BYTE* pBuf = NULL; DWORD dwBufSize = 64 * 1024; BYTE* pResponseBodyData = NULL; DWORD dwResponseBodyDataSize = 0; DWORD dwOffset = 0; DWORD dwPostDataSize = dwUploadDataSize; INTERNET_BUFFERS internetBuffers = { 0 }; do { // 建立會話 hInternet = InternetOpen("WinInetPost/0.1", INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0); if (NULL == hInternet) { break; } // 建立連接 hConnect = InternetConnect(hInternet, szHostName, INTERNET_DEFAULT_HTTP_PORT, szUserName, szPassword, INTERNET_SERVICE_HTTP, 0, 0); if (NULL == hConnect) { break; } // 打開并發(fā)送HTTP請求 dwOpenRequestFlags = INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP | INTERNET_FLAG_KEEP_CONNECTION | INTERNET_FLAG_NO_AUTH | INTERNET_FLAG_NO_COOKIES | INTERNET_FLAG_NO_UI | INTERNET_FLAG_RELOAD; if (0 < lstrlen(szExtraInfo)) { lstrcat(szUrlPath, szExtraInfo); } // 使用POST請求 hRequest = HttpOpenRequest(hConnect, "POST", szUrlPath, NULL, NULL, NULL, dwOpenRequestFlags, 0); if (NULL == hRequest) { break; } // 告訴服務(wù)器傳輸數(shù)據(jù)的總大小 RtlZeroMemory(&internetBuffers, sizeof(internetBuffers)); internetBuffers.dwStructSize = sizeof(internetBuffers); internetBuffers.dwBufferTotal = dwPostDataSize; bRet = HttpSendRequestEx(hRequest, &internetBuffers, NULL, 0, 0); if (FALSE == bRet) { break; } // 發(fā)送數(shù)據(jù) bRet = InternetWriteFile(hRequest, pUploadData, dwUploadDataSize, &dwRet); if (FALSE == bRet) { break; } // 發(fā)送完畢 bRet = HttpEndRequest(hRequest, NULL, 0, 0); if (FALSE == bRet) { break; } // 接收響應(yīng)報文 pResponseHeaderIInfo = new unsigned char[dwResponseHeaderIInfoSize]; if (NULL == pResponseHeaderIInfo) { break; } RtlZeroMemory(pResponseHeaderIInfo, dwResponseHeaderIInfoSize); bRet = HttpQueryInfo(hRequest, HTTP_QUERY_RAW_HEADERS_CRLF, pResponseHeaderIInfo, &dwResponseHeaderIInfoSize, NULL); if (FALSE == bRet) { break; } // 獲取數(shù)據(jù)長度 bRet = GetContentLength((char*)pResponseHeaderIInfo, &dwResponseBodyDataSize); if (FALSE == bRet) { break; } // 輸出響應(yīng)頭 std::cout << pResponseHeaderIInfo << std::endl; // 接收報文主體內(nèi)容(Get Response Body) pBuf = new BYTE[dwBufSize]; if (NULL == pBuf) { break; } pResponseBodyData = new BYTE[dwResponseBodyDataSize]; if (NULL == pResponseBodyData) { break; } RtlZeroMemory(pResponseBodyData, dwResponseBodyDataSize); do { // 循環(huán)讀取數(shù)據(jù)并填充到緩沖區(qū)內(nèi) RtlZeroMemory(pBuf, dwBufSize); bRet = InternetReadFile(hRequest, pBuf, dwBufSize, &dwRet); if (FALSE == bRet) { break; } RtlCopyMemory((pResponseBodyData + dwOffset), pBuf, dwRet); dwOffset = dwOffset + dwRet; } while (dwResponseBodyDataSize > dwOffset); } while (FALSE); // 關(guān)閉釋放 if (NULL != pResponseBodyData) { delete[]pResponseBodyData; pResponseBodyData = NULL; } if (NULL != pBuf) { delete[]pBuf; pBuf = NULL; } if (NULL != pResponseHeaderIInfo) { delete[]pResponseHeaderIInfo; pResponseHeaderIInfo = NULL; } if (NULL != hRequest) { InternetCloseHandle(hRequest); hRequest = NULL; } if (NULL != hConnect) { InternetCloseHandle(hConnect); hConnect = NULL; } if (NULL != hInternet) { InternetCloseHandle(hInternet); hInternet = NULL; } return bRet; }
上傳代碼通過指定szHttpUploadUrl
將d://lyshark.exe
文件提交到遠(yuǎn)程主機,代碼如下所示;
int main(int argc, char* argv[]) { // 設(shè)置上傳接口地址 char szHttpUploadUrl[] = "http://127.0.0.1/upload?file=lyshark.exe"; // 被上傳文件絕對路徑 char szHttpUploadFileName[] = "d://lyshark.exe"; BYTE* pHttpUploadData = NULL; DWORD dwHttpUploadDataSize = 0; DWORD dwRet = 0; // 打開文件 HANDLE hFile = CreateFile(szHttpUploadFileName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_ARCHIVE, NULL); // 獲取文件大小 dwHttpUploadDataSize = GetFileSize(hFile, NULL); // 讀取文件數(shù)據(jù) pHttpUploadData = new BYTE[dwHttpUploadDataSize]; ReadFile(hFile, pHttpUploadData, dwHttpUploadDataSize, &dwRet, NULL); dwHttpUploadDataSize = dwRet; // 上傳數(shù)據(jù) if (FALSE == HttpUpload(szHttpUploadUrl, pHttpUploadData, dwHttpUploadDataSize)) { return 0; } // 釋放內(nèi)存 delete[]pHttpUploadData; pHttpUploadData = NULL; CloseHandle(hFile); system("pause"); return 0; }
運行后提交文件,則可以看到輸出信息,如下圖所示;
以上就是C/C++通過HTTP實現(xiàn)文件上傳與下載的示例詳解的詳細(xì)內(nèi)容,更多關(guān)于C/C++ HTTP實現(xiàn)文件上傳下載的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
vscode ssh遠(yuǎn)程連接服務(wù)器一直卡在下載 vscode server問題解決
在使用vscode使用ssh遠(yuǎn)程連接服務(wù)器時,一直卡在下載"vscode 服務(wù)器"階段,本文主要介紹了vscode ssh遠(yuǎn)程連接服務(wù)器一直卡在下載 vscode server問題解決,感興趣的可以了解一下2025-01-01基于對話框程序中讓對話框捕獲WM_KEYDOWN消息的實現(xiàn)方法
下面我們將通過程序給大家演示基于對話框的應(yīng)用程序?qū)M_KEYDOWN消息的捕獲。需要的朋友可以參考下2013-05-05Qt實現(xiàn)邊加載數(shù)據(jù)邊顯示頁面的示例代碼
無論是MFC框架還是QT框架,實現(xiàn)加載數(shù)據(jù)的等待效果都是很麻煩的,不像WEB端輕輕松松一句代碼就搞定了。本文將通過Qt實現(xiàn)邊加載數(shù)據(jù)邊顯示頁面的功能,需要的可以參考一下2022-01-01