實現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"csdn@elaine_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; }