1. 程式人生 > >基於Wininet非同步模式實現的HttpClient

基於Wininet非同步模式實現的HttpClient

      專案在使用Wininet API時一直採用的同步模式,通過開一個執行緒+等待超時的“假"非同步方式來實現非阻塞呼叫。但是最近突然發現,同步呼叫InternetOpenUrl在處於阻塞狀態時,在某些情況下,通過InternetCloseHandle無法強制InternetOpenUrl立即返回。導致程式退出時開啟的執行緒無法正常退出。排查了很久,未找到問題原因,懷疑這是同步方式本身的bug。所以不得已引入了非同步模式。現封裝了一個非同步讀取類CHttpClient,採用Winnet的非同步介面進行讀取,可以自定義介面超時時間,通過使用非同步模式後,很好的避免了上述問題的發生。
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();
};
客戶端呼叫示例程式碼:
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;
}
CHttpClient完整的程式碼如下:
/********************************************************************  
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