项目在使用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
#include
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
#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