C++程式設計筆記:使用WinHTTP實現HTTP訪問(解決接收UTF8資料亂碼問題)
阿新 • • 發佈:2019-01-08
實現HTTP訪問的流程包括以下幾步:
1, 首先我們開啟一個Session獲得一個HINTERNET session控制代碼;
2, 然後我們使用這個session控制代碼與伺服器連線得到一個HINTERNET connect控制代碼;
3, 然後我們使用這個connect控制代碼來開啟Http請求得到一個HINTERNET request控制代碼;
4, 這時我們就可以使用這個request控制代碼來發送資料與讀取從伺服器返回的資料;
5, 最後依次關閉request,connect,session控制代碼。
微軟提供了兩套http訪問的介面:WinHTTP和WinINet。WinHTTP比WinINet更加安全和健壯,可以認為WinHTTP是WinINet的升級版本。這兩套API包含了很多相似的函式與巨集定義,訪問的流程也是完全類似的(上述5步)。本文主要通過WinHTTP實現post請求方法,嚴格按照上述5個步驟給大家進行講解。
又由於我所接收到的資料是UTF8而不是ASCII碼,因此一開始接收到的資料存在亂碼。在下述程式碼中我會詳細解釋出現亂碼的原因以及如何解決。
好,小二,上程式碼!
#include "stdafx.h"
#include "jsonparser.h"
#include <string>
#include <windows.h>
#include <winhttp.h>
#pragma comment(lib, "winhttp.lib")
int _tmain(int argc, _TCHAR* argv[])
{
HINTERNET hSession = NULL;
HINTERNET hConnect = NULL;
HINTERNET hRequest = NULL;
//1. 初始化一個WinHTTP-session控制代碼,引數1為此控制代碼的名稱
hSession = WinHttpOpen(L"[email protected]_bao", NULL, NULL, NULL, NULL);
if (hSession == NULL) {
cout<<"Error:Open session failed: "<<GetLastError()<<endl;
return -1;
}
//2. 通過上述控制代碼連線到伺服器,需要指定伺服器IP和埠號。若連線成功,返回的hConnect控制代碼不為NULL
hConnect = WinHttpConnect(hSession, L"192.168.50.112" , (INTERNET_PORT)8080, 0);
if (hConnect == NULL) {
cout << "Error:Connect failed: " << GetLastError()<<endl;
return -1;
}
//3. 通過hConnect控制代碼建立一個hRequest控制代碼,用於傳送資料與讀取從伺服器返回的資料。
hRequest = WinHttpOpenRequest(hConnect, L"Post", L"getServiceInfo", NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, 0);
//其中引數2表示請求方式,此處為Post;引數3:給定Post的具體地址,如這裡的具體地址為http://192.168.50.112/getServiceInfo
if (hRequest == NULL) {
cout << "Error:OpenRequest failed: " << GetLastError() << endl;
return -1;
}
//4-1. 向伺服器傳送post資料
//(1) 指定傳送的資料內容
string data = "This is my data to be sent";
const void *ss = (const char *)data.c_str();
//(2) 傳送請求
BOOL bResults;
bResults = WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, const_cast<void*>(ss), data.length(), data.length(), 0);
if (!bResults){
cout << "Error:SendRequest failed: " << GetLastError() << endl;
return -1;
}
else{
//(3) 傳送請求成功則準備接受伺服器的response。注意:在使用 WinHttpQueryDataAvailable和WinHttpReadData前必須使用WinHttpReceiveResponse才能access伺服器返回的資料
bResults = WinHttpReceiveResponse(hRequest, NULL);
}
//4-2. 獲取伺服器返回資料的header資訊。這一步我用來獲取返回資料的資料型別。
LPVOID lpHeaderBuffer = NULL;
DWORD dwSize = 0;
if (bResults)
{
//(1) 獲取header的長度
WinHttpQueryHeaders(hRequest, WINHTTP_QUERY_RAW_HEADERS_CRLF,
WINHTTP_HEADER_NAME_BY_INDEX, NULL,
&dwSize, WINHTTP_NO_HEADER_INDEX);
//(2) 根據header的長度為buffer申請記憶體空間
if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
{
lpHeaderBuffer = new WCHAR[dwSize / sizeof(WCHAR)];
//(3) 使用WinHttpQueryHeaders獲取header資訊
bResults = WinHttpQueryHeaders(hRequest,
WINHTTP_QUERY_RAW_HEADERS_CRLF,
WINHTTP_HEADER_NAME_BY_INDEX,
lpHeaderBuffer, &dwSize,
WINHTTP_NO_HEADER_INDEX);
}
}
printf("Header contents: \n%S", lpHeaderBuffer);
//解析上述header資訊會發現伺服器返回資料的charset為uft-8。這意味著後面需要對獲取到的raw data進行寬字元轉換。一開始由於沒有意識到需要進行轉換所以得到的資料都是亂碼。
//出現亂碼的原因是:HTTP在傳輸過程中是二值的,它並沒有text或者是unicode的概念。HTTP使用7bit的ASCII碼作為HTTP headers,但是內容是任意的二值資料,需要根據header中指定的編碼方式來描述它(通常是Content-Type header).
//因此當你接收到原始的HTTP資料時,先將其儲存到char[] buffer中,然後利用WinHttpQueryHearders()獲取HTTP頭,得到內容的Content-Type,這樣你就知道資料到底是啥型別的了,是ASCII還是Unicode或者其他。
//一旦你知道了具體的編碼方式,你就可以通過MultiByteToWideChar()將其轉換成合適編碼的字元,存入wchar_t[]中。
//關於亂碼的解決方案請看4-4
//4-3. 獲取伺服器返回資料
LPSTR pszOutBuffer = NULL;
DWORD dwDownloaded = 0; //實際收取的字元數
wchar_t *pwText = NULL;
if (bResults)
{
do
{
//(1) 獲取返回資料的大小(以位元組為單位)
dwSize = 0;
if (!WinHttpQueryDataAvailable(hRequest, &dwSize)){
cout << "Error:WinHttpQueryDataAvailable failed:" << GetLastError() << endl;
break;
}
if (!dwSize) break; //資料大小為0
//(2) 根據返回資料的長度為buffer申請記憶體空間
pszOutBuffer = new char[dwSize + 1];
if (!pszOutBuffer){
cout<<"Out of memory."<<endl;
break;
}
ZeroMemory(pszOutBuffer, dwSize + 1); //將buffer置0
//(3) 通過WinHttpReadData讀取伺服器的返回資料
if (!WinHttpReadData(hRequest,pszOutBuffer, dwSize, &dwDownloaded)){
cout << "Error:WinHttpQueryDataAvailable failed:" << GetLastError() << endl;
}
if (!dwDownloaded)
break;
} while (dwSize > 0);
//4-4. 將返回資料轉換成UTF8
DWORD dwNum = MultiByteToWideChar(CP_ACP, 0, pszOutBuffer, -1, NULL, 0); //返回原始ASCII碼的字元數目
pwText = new wchar_t[dwNum]; //根據ASCII碼的字元數分配UTF8的空間
MultiByteToWideChar(CP_UTF8, 0, pszOutBuffer, -1, pwText, dwNum); //將ASCII碼轉換成UTF8
printf("Received contents: \n%S", pwText);
}
//5. 依次關閉request,connect,session控制代碼
if (hRequest) WinHttpCloseHandle(hRequest);
if (hConnect) WinHttpCloseHandle(hConnect);
if (hSession) WinHttpCloseHandle(hSession);
return 0;
}