转自:http://hi.baidu.com/zhaquanmin/item/3259e60cbcacb712addc70b6
基于WININET下HTTP协议访问,不采用VC下的封装类CInternetSession,因为它的异步操作理论上可行,MSDN中说明实际代码还在完善。
WININET API下以在HTTP协议下下载一个页面的源代码信息为例,先来看看同步的流程:
1)InternetOpen;
2)InternetOpenUrl;
3)HttpQueryInfo;
4)InternetReadFile;
5)InternetCloseHandle。
在第2步和第4步,程序会一直等待,直到函数返回。如果要设置超时,可以使用InternetSetOption(不过好像没什么用)。在很多时候,这个函数是不合适的。比如用户主动要中断下载,却只能等待函数返回。还有,如果是大文件下载,无法想像一次读取上兆字节的数据,需使用断点续传,虽然也可以使用同步函数InternetSetFilePointer来定位网络文件读取位置,但很多服务器是不支持的。如果在手机上使用,还要考虑诸如移动网关的限制等等。
同步的好处是函数较少,流程清晰,调试也方便。
再采用异步方法,看看异步的总体流程:
1)InternetOpen,需指定是异步;
2)InternetSetStatusCallback,设置回调;
3)InternetOpenUrl,需指定回调参数;
4)WaitForSingObject或WaitForMultipleObjects,接收信号量;
5)HttpQueryInfo;
6)InternetReadFileEx,需指定回调参数;
7)WaitForSingObject或WaitForMultipleObjects,接收信号量;
8)InternetSetStatusCallback,卸载回调;
9)InternetCloseHandle。
可以看出,异步比同步要复杂了不少,重点在于回调函数。在回调中,系统会及时返回各种系统定义的HTTP消息,我们根据这些消息来设置某些信号量。在WaitForSingObject或WaitForMultipleObjects里,等待这些信号(当然也可以等待用户的取消动作)。当有正确的信号返回时,继续往下的操作。
异步方式下,InternetOpenUrl可以在header头里设置要读取的范围。比如读0到1024的数据,在header头里加入Range: bytes=0-1024\r\n。这种方式保证了断点续传。要注意的是,如果服务器支持断点续传,此时使用HttpQueryInfo得到的状态码是206,而不是200。
回调函数写法:
回调函数怎么写啊?看看微软提供的一个例子吧。这个例子是用POST的方式上传数据,比上述下载数据步骤更为麻烦,把InternetOpenUrl这个函数分成更多的函数来处理。有时间再挖挖这个例子。微软这个例子是在一个sendreqexasync.cpp的文件中,在微软的网站上应该可以得到。
源代码如下:
#include<windows.h>
#include<wininet.h>
#include<iostream.h>
DWORD dwNumKSent;
DWORD dwNumKToSend;
DWORD dwNumBytesComplete = 0;
char lpOutBuf[1024];
HANDLE hConnectedEvent, hRequestCompleteEvent;
HINTERNET hInstance, hConnect, hRequest;
char *lpszUrl, *lpszServer;
BOOL bAllDone = FALSE;
void __stdcall Callback(HINTERNET hInternet,
DWORD dwContext,
DWORD dwInternetStatus,
LPVOID lpStatusInfo,
DWORD dwStatusInfoLen);
void main(int argc, char *argv[])
{
if (argc != 4)
{
cout << "Usage: sendreqexasync <server> <url> <size in kilobytes>" << endl;
cout << " Example: sendreqexasync www.foo.com /postfolder/upload.exe 256" << endl;
return;
}
lpszServer = argv[1];
lpszUrl = argv[2];
dwNumKToSend = atoi(argv[3]);
FillMemory(lpOutBuf, 1024, 'A');
hConnectedEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
hRequestCompleteEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
hInstance = InternetOpen("sendreqexasync",
INTERNET_OPEN_TYPE_PRECONFIG,
NULL,
NULL,
INTERNET_FLAG_ASYNC);
if (hInstance == NULL)
{
cout << "InternetOpen failed, error " << GetLastError();
return;
}
if (InternetSetStatusCallback(hInstance,
(INTERNET_STATUS_CALLBACK)&Callback) == INTERNET_INVALID_STATUS_CALLBACK)
{
cout << "InternetSetStatusCallback failed, error " << GetLastError();
return;
}
hConnect = InternetConnect(hInstance,
lpszServer,
INTERNET_DEFAULT_HTTP_PORT,
NULL,
NULL,
INTERNET_SERVICE_HTTP,
0,
1);
if (hConnect == NULL)
{
if (GetLastError() != ERROR_IO_PENDING)
{
cout << "InternetConnect failed, error " << GetLastError();
return;
}
WaitForSingleObject(hConnectedEvent, INFINITE);
}
hRequest = HttpOpenRequest(hConnect,
"POST",
lpszUrl,
NULL,
NULL,
NULL,
INTERNET_FLAG_RELOAD | INTERNET_FLAG_NO_CACHE_WRITE,
2);
if (hRequest == NULL)
{
if (GetLastError() != ERROR_IO_PENDING)
{
cout << "HttpOpenRequest failed, error " << GetLastError();
return;
}
WaitForSingleObject(hRequestCompleteEvent, INFINITE);
}
INTERNET_BUFFERS IntBuff;
FillMemory(&IntBuff, sizeof(IntBuff), 0);
IntBuff.dwStructSize= sizeof(IntBuff);
IntBuff.dwBufferTotal = 1024*dwNumKToSend;
IntBuff.lpcszHeader = "Content-Type: text/text\r\n";
IntBuff.dwHeadersLength = lstrlen(IntBuff.lpcszHeader);
if (!HttpSendRequestEx(hRequest,
&IntBuff,
NULL,
0,
2))
{
if (GetLastError() != ERROR_IO_PENDING)
{
cout << "HttpSendRequestEx failed, error " << GetLastError();
return;
}
cout << "HttpSendRequestEx called successfully" << endl;
cout.flush();
WaitForSingleObject(hRequestCompleteEvent, INFINITE);
}
for (dwNumKSent = 0; dwNumKSent < dwNumKToSend; dwNumKSent++)
{
DWORD dwBytesWritten;
if(!InternetWriteFile(hRequest,
lpOutBuf,
1024,
&dwBytesWritten))
{
if (GetLastError() != ERROR_IO_PENDING)
{
cout << "InternetWriteFile failed, error " << GetLastError();
return;
}
else
{
cout << "InternetWriteFile completing asynchronously" << endl;
cout.flush();
WaitForSingleObject(hRequestCompleteEvent, INFINITE);
}
}
}
cout << "Calling HttpEndRequest" << endl;
cout.flush();
if (!HttpEndRequest(hRequest, NULL, HSR_INITIATE, 2))
{
if (GetLastError() == ERROR_IO_PENDING)
{
cout << "HttpEndRequest called" << endl;
cout.flush();
WaitForSingleObject(hRequestCompleteEvent, INFINITE);
}
else
{
cout << "HttpEndRequest failed, error " << GetLastError() << endl;
return;
}
}
cout << "------------------- Read the response -------------------" << endl;
char lpReadBuff[256];
do
{
INTERNET_BUFFERS InetBuff;
FillMemory(&InetBuff, sizeof(InetBuff), 0);
InetBuff.dwStructSize = sizeof(InetBuff);
InetBuff.lpvBuffer = lpReadBuff;
InetBuff.dwBufferLength = sizeof(lpReadBuff) - 1;
cout << "Calling InternetReadFileEx" << endl;
cout.flush();
if (!InternetReadFileEx(hRequest,
&InetBuff,
0, 2))
{
if (GetLastError() == ERROR_IO_PENDING)
{
cout << "Waiting for InternetReadFile to complete" << endl;
cout.flush();
WaitForSingleObject(hRequestCompleteEvent, INFINITE);
}
else
{
cout << "InternetReadFileEx failed, error " << GetLastError();
cout.flush();
return;
}
}
lpReadBuff[InetBuff.dwBufferLength] = 0;
cout << lpReadBuff;
cout.flush();
if (InetBuff.dwBufferLength == 0)
bAllDone = TRUE;
} while (bAllDone == FALSE);
cout << endl << endl << "------------------- Request Complete ----------------" << endl;
}
/这是回调函数,重点看下它的写法
void __stdcall Callback(HINTERNET hInternet,
DWORD dwContext,
DWORD dwInternetStatus,
LPVOID lpStatusInfo,
DWORD dwStatusInfoLen)
{
cout << "Callback dwInternetStatus: " << dwInternetStatus << " Context: " << dwContext << endl;
cout.flush();
switch(dwContext)
{
case 1: // Connection handle
if (dwInternetStatus == INTERNET_STATUS_HANDLE_CREATED)
{
INTERNET_ASYNC_RESULT *pRes = (INTERNET_ASYNC_RESULT *)lpStatusInfo;
hConnect = (HINTERNET)pRes->dwResult;
cout << "Connect handle created" << endl;
cout.flush();
SetEvent(hConnectedEvent);
}
break;
case 2: // Request handle
switch(dwInternetStatus)
{
case INTERNET_STATUS_HANDLE_CREATED:
{
INTERNET_ASYNC_RESULT *pRes = (INTERNET_ASYNC_RESULT *)lpStatusInfo;
hRequest = (HINTERNET)pRes->dwResult;
cout << "Request handle created" << endl;
cout.flush();
}
break;
case INTERNET_STATUS_REQUEST_SENT:
{
DWORD *lpBytesSent = (DWORD*)lpStatusInfo;
cout << "Bytes Sent: " << *lpBytesSent << endl;
dwNumBytesComplete += *lpBytesSent;
}
break;
case INTERNET_STATUS_REQUEST_COMPLETE:
{
INTERNET_ASYNC_RESULT *pAsyncRes = (INTERNET_ASYNC_RESULT *)lpStatusInfo;
cout << "Function call finished" << endl;
cout << "dwResult: " << pAsyncRes->dwResult << endl;
cout << "dwError: " << pAsyncRes->dwError << endl;
cout.flush();
SetEvent(hRequestCompleteEvent);
}
break;
case INTERNET_STATUS_RECEIVING_RESPONSE:
cout << "Receiving Response" << endl;
cout.flush();
break;
case INTERNET_STATUS_RESPONSE_RECEIVED:
{
DWORD *dwBytesReceived = (DWORD*)lpStatusInfo;
cout << "Received " << *dwBytesReceived << endl;
cout.flush();
}
}
}
}