基於Wininet非同步模式實現的HttpClient
阿新 • • 發佈:2018-12-30
專案在使用Wininet API時一直採用的同步模式,通過開一個執行緒+等待超時的“假"非同步方式來實現非阻塞呼叫。但是最近突然發現,同步呼叫InternetOpenUrl在處於阻塞狀態時,在某些情況下,通過InternetCloseHandle無法強制InternetOpenUrl立即返回。導致程式退出時開啟的執行緒無法正常退出。排查了很久,未找到問題原因,懷疑這是同步方式本身的bug。所以不得已引入了非同步模式。現封裝了一個非同步讀取類CHttpClient,採用Winnet的非同步介面進行讀取,可以自定義介面超時時間,通過使用非同步模式後,很好的避免了上述問題的發生。
CHttpClient.cpp
參考文章:http://www.codeproject.com/Articles/822/Using-WinInet-HTTP-functions-in-Full-Asynchronous
客戶端呼叫示例程式碼:class CHttpClient { public: CHttpClient(void); ~CHttpClient(void); public: BOOL Open(LPCTSTR lpUrl,int timeout = INFINITE); int Read(unsigned char* buffer,int size,int timeout = INFINITE); void Close(); };
CHttpClient完整的程式碼如下:int main(int argc, char *argv[]) { CHttpClient httpClient; BOOL bRet = httpClient.Open("http://192.168.169.200/stream.php?cam=c0a86b8500",5000); if(!bRet) { return 0; } unsigned char buffer[4096] = {0}; while(1) { int len = httpClient.Read(buffer,sizeof(buffer)); if(len <= 0) { break; } } httpClient.Close(); return 1; }
/******************************************************************** filename: HttpClient.h created: 2016-04-08 author: firehood purpose: A Asynchronous Http Client By Using WinInet HTTP functions *********************************************************************/ #pragma once #include <windows.h> #include <wininet.h> class CHttpClient { public: CHttpClient(void); ~CHttpClient(void); public: BOOL Open(LPCTSTR lpUrl,int timeout = INFINITE); int Read(unsigned char* buffer,int size,int timeout = INFINITE); void Close(); private: static void CALLBACK HttpStatusCallback( HINTERNET hInternet, DWORD dwContext, DWORD dwInternetStatus, LPVOID lpStatusInfo, DWORD dwStatusInfoLen); private: HINTERNET m_hInternet; HINTERNET m_hSession; HANDLE m_hRequestOpenedEvent; HANDLE m_hRequestCompleteEvent; DWORD m_dwCompleteResult; };
CHttpClient.cpp
/********************************************************************
filename: HttpClient.cpp
created: 2016-04-08
author: firehood
purpose: A Asynchronous Http Client By Using WinInet HTTP functions
*********************************************************************/
#include "HttpClient.h"
#include <tchar.h>
#pragma comment(lib,"wininet.lib")
CHttpClient::CHttpClient(void)
: m_hInternet(NULL)
, m_hSession(NULL)
, m_dwCompleteResult(0)
{
m_hRequestOpenedEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
m_hRequestCompleteEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
}
CHttpClient::~CHttpClient(void)
{
Close();
if(m_hRequestOpenedEvent)
{
CloseHandle(m_hRequestOpenedEvent);
m_hRequestOpenedEvent = NULL;
}
if(m_hRequestCompleteEvent)
{
CloseHandle(m_hRequestCompleteEvent);
m_hRequestCompleteEvent = NULL;
}
}
BOOL CHttpClient::Open(LPCTSTR lpUrl,int timeout)
{
if(lpUrl == NULL)
{
return FALSE;
}
if(m_hInternet)
{
Close();
}
BOOL bRet = FALSE;
do
{
m_hInternet = InternetOpen(NULL,INTERNET_OPEN_TYPE_PRECONFIG,NULL,NULL,INTERNET_FLAG_ASYNC);
if (m_hInternet == NULL)
{
break;
}
// Setup callback function
if (InternetSetStatusCallback(m_hInternet,HttpStatusCallback) == INTERNET_INVALID_STATUS_CALLBACK)
{
break;
}
m_hSession = InternetOpenUrl(m_hInternet,lpUrl,NULL,NULL,INTERNET_FLAG_NO_CACHE_WRITE,(DWORD_PTR)this);
if(NULL == m_hSession)
{
if (GetLastError() != ERROR_IO_PENDING)
{
break;
}
// Wait until we get the connection handle
if(WaitForSingleObject(m_hRequestOpenedEvent,timeout) == WAIT_TIMEOUT)
{
break;
}
}
if(WaitForSingleObject(m_hRequestCompleteEvent,timeout) == WAIT_TIMEOUT)
{
break;
}
if(m_dwCompleteResult == 0)
{
break;
}
DWORD dwStatusCode;
TCHAR responseText[256] = {0};
DWORD responseTextSize = sizeof(responseText);
if(!HttpQueryInfo(m_hSession,HTTP_QUERY_STATUS_CODE,&responseText,&responseTextSize,NULL))
{
break;
}
dwStatusCode = _ttoi(responseText);
if(dwStatusCode != HTTP_STATUS_OK )
{
break;
}
bRet = TRUE;
}while(0);
if(!bRet)
{
if(m_hSession)
{
InternetCloseHandle(m_hSession);
m_hSession = NULL;
}
if(m_hInternet)
{
InternetSetStatusCallback(m_hInternet, NULL);
InternetCloseHandle(m_hInternet);
m_hInternet = NULL;
}
}
return bRet;
}
int CHttpClient::Read(unsigned char* buffer,int size,int timeout)
{
if(buffer == NULL || size <= 0)
{
return -1;
}
INTERNET_BUFFERS InetBuff;
memset(&InetBuff, 0, sizeof(InetBuff));
InetBuff.dwStructSize = sizeof(InetBuff);
InetBuff.lpvBuffer = buffer;
InetBuff.dwBufferLength = size;
if(!InternetReadFileEx(m_hSession,&InetBuff,0,(DWORD_PTR)this))
{
if (GetLastError() == ERROR_IO_PENDING)
{
if(WaitForSingleObject(m_hRequestCompleteEvent,timeout) == WAIT_TIMEOUT)
{
return -1;
}
}
else
{
return -1;
}
}
return InetBuff.dwBufferLength;
}
void CHttpClient::Close()
{
SetEvent(m_hRequestOpenedEvent);
SetEvent(m_hRequestCompleteEvent);
if(m_hSession)
{
InternetCloseHandle(m_hSession);
m_hSession = NULL;
}
if(m_hInternet)
{
InternetSetStatusCallback(m_hInternet, NULL);
InternetCloseHandle(m_hInternet);
m_hInternet = NULL;
}
ResetEvent(m_hRequestOpenedEvent);
ResetEvent(m_hRequestCompleteEvent);
}
void CALLBACK CHttpClient::HttpStatusCallback(
HINTERNET hInternet,
DWORD dwContext,
DWORD dwInternetStatus,
LPVOID lpStatusInfo,
DWORD dwStatusInfoLen)
{
CHttpClient *p = (CHttpClient*)dwContext;
switch(dwInternetStatus)
{
case INTERNET_STATUS_HANDLE_CREATED:
{
INTERNET_ASYNC_RESULT *pRes = (INTERNET_ASYNC_RESULT *)lpStatusInfo;
p->m_hSession = (HINTERNET)pRes->dwResult;
SetEvent(p->m_hRequestOpenedEvent);
}
break;
case INTERNET_STATUS_REQUEST_COMPLETE:
{
INTERNET_ASYNC_RESULT *pRes = (INTERNET_ASYNC_RESULT *)lpStatusInfo;
p->m_dwCompleteResult = pRes->dwResult;
SetEvent(p->m_hRequestCompleteEvent);
}
break;
case INTERNET_STATUS_HANDLE_CLOSING:
break;
case INTERNET_STATUS_RESPONSE_RECEIVED:
break;
default:
break;
}
}
參考文章:http://www.codeproject.com/Articles/822/Using-WinInet-HTTP-functions-in-Full-Asynchronous