C/C++ 通過HTTP實現文件上傳下載

WinInet(Windows Internet)是 Microsoft Windows 操作系統中的一個 API 集,用於提供對 Internet 相關功能的支持。它包括了一系列的函數,使得 Windows 應用程序能夠進行網絡通信、處理 HTTP 請求、FTP 操作等。WinInet 提供了一套完整的網絡通信工具,使得開發者能夠輕鬆地構建支持網絡功能的應用程序,涵蓋了從簡單的 HTTP 請求到複雜的文件傳輸等多種網絡操作。

分解URL地址

InternetCrackUrl 函數可實現對URL字符串進行解析,提取其中的協議、主機名、端口、路徑和其他信息,並將這些信息存儲在 URL_COMPONENTS 結構中,方便後續的網絡操作,該函數是Windows下默認提供的,函數與依賴結果如下所示;

函數原型

BOOL InternetCrackUrl(
  LPCTSTR      lpszUrl,
  DWORD        dwUrlLength,
  DWORD        dwFlags,
  LPURL_COMPONENTS lpUrlComponents
);

參數說明

  • lpszUrl:指定待解析的 URL 字符串。
  • dwUrlLength:指定 URL 字符串的長度。
  • dwFlags:指定解析 URL 的標誌,可以是以下值之一:
    • ICU_DECODE:對 URL 進行解碼。
    • ICU_ESCAPE:對 URL 進行轉義。
  • lpUrlComponents:一個指向 URL_COMPONENTS 結構的指針,用於存儲解析後的各個部分信息。

URL_COMPONENTS結構

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;

返回值

如果函數成功,返回 TRUE,並在 lpUrlComponents 結構中存儲解析後的信息;如果失敗,返回 FALSE。在失敗時,可以調用 GetLastError 函數獲取詳細的錯誤信息。

函數調用

#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 };

	// 初始化變量中的內容
	RtlZeroMemory(&uc, sizeof(uc));
	RtlZeroMemory(pszScheme, dwBufferSize);
	RtlZeroMemory(pszHostName, dwBufferSize);
	RtlZeroMemory(pszUserName, dwBufferSize);
	RtlZeroMemory(pszPassword, dwBufferSize);
	RtlZeroMemory(pszUrlPath, dwBufferSize);
	RtlZeroMemory(pszExtraInfo, dwBufferSize);

	// 將長度填充到結構中
	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";

	// 對應的變量
	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;
}

運行代碼輸出特定網址的每個部分,如下圖所示;

下載頁面內容

InternetOpen

用於初始化 WinINet 函數的使用。以下是該函數的原型:

HINTERNET InternetOpen(
  LPCWSTR lpszAgent,
  DWORD   dwAccessType,
  LPCWSTR lpszProxyName,
  LPCWSTR lpszProxyBypass,
  DWORD   dwFlags
);

參數說明:

  • lpszAgent: 指定應用程序的名稱,用於標識調用 InternetOpen 的應用程序。
  • dwAccessType: 指定訪問類型,可以是 INTERNET_OPEN_TYPE_DIRECTINTERNET_OPEN_TYPE_PROXYINTERNET_OPEN_TYPE_PRECONFIG 中的一個。
  • lpszProxyName: 如果 dwAccessTypeINTERNET_OPEN_TYPE_PROXY,則指定代理服務器的名稱。否則,可以設爲 NULL
  • lpszProxyBypass: 如果 dwAccessTypeINTERNET_OPEN_TYPE_PROXY,則指定繞過代理服務器的地址。否則,可以設爲 NULL
  • dwFlags: 一些標誌,可以用來指定額外的行爲,如 INTERNET_FLAG_ASYNC 用於異步操作。

返回值:

如果函數調用成功,將返回一個類型爲 HINTERNET 的句柄,用於後續的 WinINet 操作。如果函數調用失敗,返回 NULL。可以使用 GetLastError 函數獲取詳細的錯誤信息。

InternetConnect

用於建立到遠程服務器的連接。以下是該函數的原型:

HINTERNET InternetConnect(
  HINTERNET     hInternet,
  LPCWSTR       lpszServerName,
  INTERNET_PORT nServerPort,
  LPCWSTR       lpszUserName,
  LPCWSTR       lpszPassword,
  DWORD         dwService,
  DWORD         dwFlags,
  DWORD_PTR     dwContext
);

參數說明:

  • hInternet: 調用 InternetOpen 返回的句柄,表示連接的上下文。
  • lpszServerName: 要連接的服務器的名稱或 IP 地址。
  • nServerPort: 服務器的端口號。
  • lpszUserName: 連接服務器時要使用的用戶名,可以爲 NULL
  • lpszPassword: 連接服務器時要使用的密碼,可以爲 NULL
  • dwService: 指定服務類型,可以是 INTERNET_SERVICE_FTPINTERNET_SERVICE_HTTP 或其他服務類型。
  • dwFlags: 一些標誌,用於指定連接的屬性,如 INTERNET_FLAG_RELOADINTERNET_FLAG_SECURE 等。
  • dwContext: 用戶定義的應用程序上下文,將在回調函數中使用。

返回值:

如果函數調用成功,將返回一個類型爲 HINTERNET 的句柄,表示連接的上下文。如果函數調用失敗,返回 NULL。可以使用 GetLastError 函數獲取詳細的錯誤信息。

InternetConnect 用於建立連接後,可以使用返回的句柄執行相關的協議操作,如 FTP 或 HTTP 操作。使用完連接後,同樣需要使用 InternetCloseHandle 函數關閉相應的句柄,以釋放資源。

HttpOpenRequest

它是在使用 WinINet 庫進行 HTTP 操作時的一部分。以下是該函數的原型:

HINTERNET HttpOpenRequest(
  HINTERNET hConnect,
  LPCWSTR   lpszVerb,
  LPCWSTR   lpszObjectName,
  LPCWSTR   lpszVersion,
  LPCWSTR   lpszReferrer,
  LPCWSTR   *lplpszAcceptTypes,
  DWORD     dwFlags,
  DWORD_PTR dwContext
);

參數說明:

  • hConnect: 調用 InternetConnect 返回的連接句柄,表示請求的上下文。
  • lpszVerb: HTTP 請求方法,如 "GET"、"POST" 等。
  • lpszObjectName: 請求的對象名,通常是 URL 的路徑部分。
  • lpszVersion: HTTP 協議版本,通常是 "HTTP/1.1"。
  • lpszReferrer: 引用的來源,可以爲 NULL
  • lplpszAcceptTypes: 指定可接受的媒體類型,可以爲 NULL
  • dwFlags: 一些標誌,用於指定請求的屬性,如 INTERNET_FLAG_RELOADINTERNET_FLAG_SECURE 等。
  • dwContext: 用戶定義的應用程序上下文,將在回調函數中使用。

返回值:

如果函數調用成功,將返回一個類型爲 HINTERNET 的句柄,表示打開的 HTTP 請求。如果函數調用失敗,返回 NULL。可以使用 GetLastError 函數獲取詳細的錯誤信息。

一旦打開了 HTTP 請求,可以使用返回的句柄執行發送請求、接收響應等操作。使用完請求後,同樣需要使用 InternetCloseHandle 函數關閉相應的句柄,以釋放資源。

HttpSendRequest

用於發送 HTTP 請求的函數,通常在使用 WinINet 庫進行 HTTP 操作時調用。以下是該函數的原型:

BOOL HttpSendRequest(
  HINTERNET hRequest,
  LPCWSTR   lpszHeaders,
  DWORD     dwHeadersLength,
  LPVOID    lpOptional,
  DWORD     dwOptionalLength
);

參數說明:

  • hRequest: 調用 HttpOpenRequest 返回的 HTTP 請求句柄,表示要發送請求的上下文。
  • lpszHeaders: 包含請求頭信息的字符串,可以爲 NULL
  • dwHeadersLength: 請求頭的長度,如果 lpszHeadersNULL,則可以爲零。
  • lpOptional: 包含請求的可選數據的緩衝區,可以爲 NULL
  • dwOptionalLength: 可選數據的長度,如果 lpOptionalNULL,則可以爲零。

返回值:

如果函數調用成功,返回非零值;如果函數調用失敗,返回零。可以使用 GetLastError 函數獲取詳細的錯誤信息。

HttpSendRequest 用於實際發送 HTTP 請求。在調用此函數之後,可以使用其他 WinINet 函數來讀取服務器的響應。同樣,使用完請求後,需要使用 InternetCloseHandle 函數關閉相應的句柄,以釋放資源。

HttpQueryInfo

用於檢索有關 HTTP 請求或響應的信息的函數,通常在使用 WinINet 庫進行 HTTP 操作時調用。以下是該函數的原型:

BOOL HttpQueryInfo(
  HINTERNET hRequest,
  DWORD     dwInfoLevel,
  LPVOID    lpBuffer,
  LPDWORD   lpdwBufferLength,
  LPDWORD   lpdwIndex
);

參數說明:

  • hRequest: 調用 HttpOpenRequest 返回的 HTTP 請求句柄,表示要查詢信息的上下文。
  • dwInfoLevel: 指定要檢索的信息類型,可以是預定義的常量,如 HTTP_QUERY_STATUS_CODEHTTP_QUERY_CONTENT_TYPE 等。
  • lpBuffer: 用於接收檢索到的信息的緩衝區。
  • lpdwBufferLength: 指向一個變量,表示 lpBuffer 緩衝區的大小。在調用函數前,應該將該變量設置爲 lpBuffer 緩衝區的大小。在調用函數後,該變量將包含實際寫入緩衝區的字節數。
  • lpdwIndex: 如果請求返回多個值,可以使用此參數指定要檢索的值的索引。對於單值的信息,可以將其設置爲 NULL

返回值:

如果函數調用成功,返回非零值;如果函數調用失敗,返回零。可以使用 GetLastError 函數獲取詳細的錯誤信息。

HttpQueryInfo 用於獲取與 HTTP 請求或響應相關的信息,如狀態碼、內容類型等。注意,在調用此函數之前,通常需要先調用 HttpSendRequest 發送請求。同樣,使用完請求後,需要使用 InternetCloseHandle 函數關閉相應的句柄,以釋放資源。

InternetReadFile

用於從指定的句柄讀取數據的函數,通常在使用 WinINet 庫進行網絡操作時調用。以下是該函數的原型:

BOOL InternetReadFile(
  HINTERNET hFile,
  LPVOID    lpBuffer,
  DWORD     dwNumberOfBytesToRead,
  LPDWORD   lpdwNumberOfBytesRead
);

參數說明:

  • hFile: 調用 HttpOpenRequestFtpOpenFile 返回的句柄,表示要讀取數據的上下文。
  • lpBuffer: 用於接收讀取到的數據的緩衝區。
  • dwNumberOfBytesToRead: 指定要讀取的字節數。
  • lpdwNumberOfBytesRead: 指向一個變量,表示 lpBuffer 緩衝區中實際讀取的字節數。在調用函數前,應該將該變量設置爲 lpBuffer 緩衝區的大小。在調用函數後,該變量將包含實際讀取的字節數。

返回值:

如果函數調用成功,返回非零值;如果函數調用失敗,返回零。可以使用 GetLastError 函數獲取詳細的錯誤信息。

InternetReadFile 用於從網絡資源中讀取數據,如從 HTTP 請求的響應中讀取內容。在調用此函數之前,通常需要先調用其他相關的函數,如 HttpOpenRequestHttpSendRequestHttpQueryInfo。同樣,使用完資源後,需要使用 InternetCloseHandle 函數關閉相應的句柄,以釋放資源。

下載頁面的完整代碼是這樣的,如下所示;

#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 };

	// 初始化變量中的內容
	RtlZeroMemory(&uc, sizeof(uc));
	RtlZeroMemory(pszScheme, dwBufferSize);
	RtlZeroMemory(pszHostName, dwBufferSize);
	RtlZeroMemory(pszUserName, dwBufferSize);
	RtlZeroMemory(pszPassword, dwBufferSize);
	RtlZeroMemory(pszUrlPath, dwBufferSize);
	RtlZeroMemory(pszExtraInfo, dwBufferSize);

	// 將長度填充到結構中
	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;
}

// 從響應信息頭信息中獲取數據內容長度大小
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);
	
	// 如果找到了就提取出裏面的純數字
	while (('0' <= *p) && ('9' >= *p))
	{
		szContentLength[i] = *p;
		p++;
		i++;
	}

	// 字符串轉數字
	dwContentLength = atoi(szContentLength);
	*pdwContentLength = dwContentLength;
	return TRUE;
}

// 數據下載
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;
	}

	// 數據下載
	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;
		}

		// 打開併發送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;
		}

		// 發送請求
		bRet = HttpSendRequest(hRequest, NULL, 0, NULL, 0);
		if (FALSE == bRet)
		{
			break;
		}
		// 接收響應的報文信息頭(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: " 網頁獲取數據長度
		bRet = GetContentLength((char*)pResponseHeaderIInfo, &dwDownloadDataSize);

		// 輸出完整響應頭
		std::cout << pResponseHeaderIInfo << std::endl;
		if (FALSE == bRet)
		{
			break;
		}
		// 接收報文主體內容(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);

			// 循環讀入數據並保存在變量中
			bRet = InternetReadFile(hRequest, pBuf, dwBufSize, &dwRet);
			if (FALSE == bRet)
			{
				break;
			}

			RtlCopyMemory((pDownloadData + dwOffset), pBuf, dwRet);
			dwOffset = dwOffset + dwRet;

		} while (dwDownloadDataSize > dwOffset);

		// 返回數據
		*ppDownloadData = pDownloadData;
		*pdwDownloadDataSize = dwDownloadDataSize;

	} while (FALSE);

	// 關閉並釋放資源
	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;
}

// 創建並保存文件
BOOL SaveToFile(char* pszFileName, BYTE* pData, DWORD dwDataSize)
{
	// 創建空文件
	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;

	// 寫出數據到文件
	WriteFile(hFile, pData, dwDataSize, &dwRet, NULL);

	// 關閉句柄保存文件
	CloseHandle(hFile);

	return TRUE;
}

使用時調用HttpDownload實現數據下載,下載後的文件會保存在pHttpDownloadData中,此時直接調用SaveToFile將其保存在文件中即可;

int main(int argc, char* argv[])
{
	// 設置需要下載的地址
	char szHttpDownloadUrl[] = "http://www.lyshark.com/index.html";
	BYTE* pHttpDownloadData = NULL;
	DWORD dwHttpDownloadDataSize = 0;

	// HTTP下載 
	if (TRUE == HttpDownload(szHttpDownloadUrl, &pHttpDownloadData, &dwHttpDownloadDataSize))
	{
		std::cout << "已保存文件,長度: " << dwHttpDownloadDataSize << " 字節"<< std::endl;
	}

	// 將下載數據保存成文件
	SaveToFile((char *)"d://index.html", pHttpDownloadData, dwHttpDownloadDataSize);

	// 釋放內存
	delete[]pHttpDownloadData;
	pHttpDownloadData = NULL;

	system("pause");
	return 0;
}

運行後則可一輸出響應頭Content-Length:完整參數以及輸出的字節數,如下圖所示;

上傳文件內容

服務端,首先需要實現一個簡單的上傳接收功能,這裏使用flask框架實現,通過執行pip install flask命令安裝這個庫,安裝成功以後手動保存爲main.py文件,上傳文件是隻需要向http://127.0.0.1/upload?file=推送數據即可,代碼如下;

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)

服務端以管理員身份運行main.py文件,此時會啓用一個Web服務器用於接收客戶端的上傳請求,如下圖所示;

接着是客戶端的實現,首先介紹如下幾個關鍵API函數;

HttpSendRequestEx

用於發送帶有附加選項的 HTTP 請求。相對於 HttpSendRequest,它提供了更多的靈活性,允許在請求中包含額外的信息,例如頭部和數據。

以下是 HttpSendRequestEx 的原型:

BOOL HttpSendRequestEx(
  HINTERNET               hRequest,
  LPINTERNET_BUFFERS      lpBuffersIn,
  LPINTERNET_BUFFERS      lpBuffersOut,
  DWORD                   dwFlags,
  DWORD_PTR               dwContext
);

參數說明:

  • hRequest:由 HttpOpenRequest 返回的句柄,表示 HTTP 請求。
  • lpBuffersIn:指向 INTERNET_BUFFERS 結構的指針,其中包含要作爲請求的一部分發送的數據。
  • lpBuffersOut:指向 INTERNET_BUFFERS 結構的指針,用於接收響應中接收到的數據。
  • dwFlags:附加標誌,控制函數的行爲。這可以包括選項,如 INTERNET_FLAG_RELOADINTERNET_FLAG_SECURE 等。
  • dwContext:傳遞給回調函數的用戶定義的上下文值。

INTERNET_BUFFERS 是一個結構,允許您在 HTTP 請求和響應中指定用於發送和接收數據的緩衝區。

使用 HttpSendRequestEx 需要謹慎處理內存,並根據您的需求設置 INTERNET_BUFFERS 結構的具體方式。

InternetWriteFile

用於將數據寫入到由 InternetOpenUrlInternetOpenHttpOpenRequestFtpOpenFile 等函數打開的 URL、連接或文件。以下是該函數的原型:

BOOL InternetWriteFile(
  HINTERNET hFile,
  LPCVOID   lpBuffer,
  DWORD     dwNumberOfBytesToWrite,
  LPDWORD   lpdwNumberOfBytesWritten
);

參數說明:

  • hFile: 調用 InternetOpenUrlInternetOpenHttpOpenRequestFtpOpenFile 等函數返回的句柄,表示要寫入的文件、URL 或連接。
  • lpBuffer: 指向包含要寫入的數據的緩衝區的指針。
  • dwNumberOfBytesToWrite: 要寫入的字節數。
  • lpdwNumberOfBytesWritten: 指向一個變量,表示實際寫入的字節數。在調用函數前,應該將該變量設置爲緩衝區的大小。在調用函數後,該變量將包含實際寫入的字節數。

返回值:

如果函數調用成功,返回非零值;如果函數調用失敗,返回零。可以使用 GetLastError 函數獲取詳細的錯誤信息。

InternetWriteFile 主要用於將數據寫入網絡資源,如通過 HTTP 或 FTP 協議上傳文件。在調用此函數之前,通常需要先調用其他相關的函數,如 InternetOpenUrlInternetOpenHttpOpenRequest 等。同樣,使用完資源後,需要使用 InternetCloseHandle 函數關閉相應的句柄,以釋放資源。

HttpEndRequest

它通常與 HttpSendRequestHttpSendRequestEx 配合使用,用於完成 HTTP 請求的發送,並準備接收服務器的響應。

以下是 HttpEndRequest 函數的原型:

BOOL HttpEndRequest(
  HINTERNET hRequest,
  LPINTERNET_BUFFERS lpBuffersOut,
  DWORD dwFlags,
  DWORD_PTR dwContext
);

參數說明:

  • hRequest: 調用 HttpOpenRequestHttpOpenRequestExHttpSendRequestHttpSendRequestEx 等函數返回的 HTTP 請求句柄。
  • lpBuffersOut: 指向一個 INTERNET_BUFFERS 結構的指針,該結構用於傳遞關於響應數據的信息。可以爲 NULL
  • dwFlags: 一些標誌,用於指定結束請求的選項。通常爲 0。
  • dwContext: 用戶定義的應用程序上下文,將在回調函數中使用。

返回值:

如果函數調用成功,返回非零值;如果函數調用失敗,返回零。可以使用 GetLastError 函數獲取詳細的錯誤信息。

HttpEndRequest 的主要作用是完成 HTTP 請求的發送,並在請求完成後準備接收服務器的響應。在調用此函數之後,通常會使用 InternetReadFile 函數等來讀取服務器的響應數據。

上傳文件的完整代碼是這樣的,如下所示;

#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 };

	// 初始化變量中的內容
	RtlZeroMemory(&uc, sizeof(uc));
	RtlZeroMemory(pszScheme, dwBufferSize);
	RtlZeroMemory(pszHostName, dwBufferSize);
	RtlZeroMemory(pszUserName, dwBufferSize);
	RtlZeroMemory(pszPassword, dwBufferSize);
	RtlZeroMemory(pszUrlPath, dwBufferSize);
	RtlZeroMemory(pszExtraInfo, dwBufferSize);

	// 將長度填充到結構中
	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;
}

// 從響應信息頭信息中獲取數據內容長度大小
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);

	// 如果找到了就提取出裏面的純數字
	while (('0' <= *p) && ('9' >= *p))
	{
		szContentLength[i] = *p;
		p++;
		i++;
	}

	// 字符串轉數字
	dwContentLength = atoi(szContentLength);
	*pdwContentLength = dwContentLength;
	return TRUE;
}

// 數據上傳
BOOL HttpUpload(char* pszUploadUrl, BYTE* pUploadData, DWORD dwUploadDataSize)
{
	// 初始化變量中的內容
	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(pszUploadUrl, szScheme, szHostName, szUserName, szPassword, szUrlPath, szExtraInfo, MAX_PATH))
	{
		return FALSE;
	}

	// 數據上傳
	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;
		}

		// 打開併發送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;
		}

		// 告訴服務器傳輸數據的總大小
		RtlZeroMemory(&internetBuffers, sizeof(internetBuffers));
		internetBuffers.dwStructSize = sizeof(internetBuffers);
		internetBuffers.dwBufferTotal = dwPostDataSize;

		bRet = HttpSendRequestEx(hRequest, &internetBuffers, NULL, 0, 0);
		if (FALSE == bRet)
		{
			break;
		}

		// 發送數據
		bRet = InternetWriteFile(hRequest, pUploadData, dwUploadDataSize, &dwRet);
		if (FALSE == bRet)
		{
			break;
		}

		// 發送完畢
		bRet = HttpEndRequest(hRequest, NULL, 0, 0);
		if (FALSE == bRet)
		{
			break;
		}

		// 接收響應報文
		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;
		}

		// 獲取數據長度
		bRet = GetContentLength((char*)pResponseHeaderIInfo, &dwResponseBodyDataSize);
		if (FALSE == bRet)
		{
			break;
		}

		// 輸出響應頭
		std::cout << pResponseHeaderIInfo << std::endl;

		// 接收報文主體內容(Get Response Body)
		pBuf = new BYTE[dwBufSize];
		if (NULL == pBuf)
		{
			break;
		}
		pResponseBodyData = new BYTE[dwResponseBodyDataSize];
		if (NULL == pResponseBodyData)
		{
			break;
		}
		RtlZeroMemory(pResponseBodyData, dwResponseBodyDataSize);

		do
		{
			// 循環讀取數據並填充到緩衝區內
			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);

	// 關閉釋放
	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;
}

上傳代碼通過指定szHttpUploadUrld://lyshark.exe文件提交到遠程主機,代碼如下所示;

int main(int argc, char* argv[])
{
	// 設置上傳接口地址
	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);
	
	// 讀取文件數據
	pHttpUploadData = new BYTE[dwHttpUploadDataSize];
	ReadFile(hFile, pHttpUploadData, dwHttpUploadDataSize, &dwRet, NULL);
	dwHttpUploadDataSize = dwRet;
	
	// 上傳數據
	if (FALSE == HttpUpload(szHttpUploadUrl, pHttpUploadData, dwHttpUploadDataSize))
	{
		return 0;
	}

	// 釋放內存
	delete[]pHttpUploadData;
	pHttpUploadData = NULL;
	CloseHandle(hFile);

	system("pause");
	return 0;
}

運行後提交文件,則可以看到輸出信息,如下圖所示;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章