异步比同步要复杂了不少,重点在于回调函数。在回调中,系统会及时返回各种系统定义的HTTP消息,我们根据这些消息来设置某些信号量。在WaitForSingObject或WaitForMultipleObjects里,等待这些信号(当然也可以等待用户的取消动作)。当有正确的信号返回时,继续往下的操作。下面一个例子代码:上面的理论同样适用于wince或windows mobile平台
#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(); } } } }
参考的异步类:
include <wininet.h> #include <mmsystem.h> class AsyncWinINet { public: typedef void (*notify_fp)(const StringMap&); class thread_info { public: thread_info(const String& _url, //请求下载的地址(in) const StringMap& _request_headrs, //请求头request_headrs(in) const notify_fp& _pfp, //下载进度通知回调函数指针 const StringMap& _pfp_param, String& _response_headrs, //返回头response_headrs(out) const String& _saved_filename, //下载内容保存文件名(in) String& _response_content, //返回内容(out) size_t _read_content_size) //控制保存在response_content中内容的长度(in)) : : request_headrs(_request_headrs), pfp(_pfp), pfp_param(_pfp_param), //pfp函数传回参数 response_headrs(_response_headrs), saved_filename(_saved_filename), response_content(_response_content), read_content_size(_read_content_size) { this->response_headrs.clear(); this->response_content.clear(); this->url = StringUtil::EncodeURIComponent(_url); for(int i = 0; i < 3; ++i) { this->hEvent[i] = CreateEvent(NULL,TRUE,FALSE,NULL); } } HANDLE hThread; DWORD dwThreadID; HANDLE hCallbackThread; DWORD dwCallbackThreadID; HANDLE hEvent[3]; LPVOID hInternet; LPVOID hFile; DWORD dwStatusCode; DWORD dwContentLength; String url; //请求下载的地址(in) const StringMap& request_headrs; //请求头request_headrs(in) const notify_fp& pfp; //下载进度通知回调函数指针 const StringMap& pfp_param; //pfp函数传回参数 String& response_headrs; //返回头response_headrs(out) const String& saved_filename; //下载内容保存文件名(in) String& response_content; //返回内容(out) size_t read_content_size; //控制保存在response_content中内容的长度(in) }; /******************************************************************************* * 函数:download * 功能:下载,返回WinINet_ERR_CODE值 * 说明:关于notify_fp 类型说明: 函数的参数为StringMap类型,传回的变量名与变量值 * 2007-12 *******************************************************************************/ static DWORD download(const String& url, //请求下载的地址(in) const StringMap& request_headrs, //请求头request_headrs(in) const notify_fp& pfp, //下载进度通知回调函数指针 const StringMap& pfp_param, //pfp函数传回参数 String& response_headrs, //返回头response_headrs(out) const String& saved_filename, //下载内容保存文件名(in) String& response_content, //返回内容(out) size_t read_content_size = 0); //控制保存在response_content中内容的长度(in) protected: static BOOL WaitExitEvent(thread_info *p); static DWORD WINAPI AsyncThread(LPVOID lpParameter); static DWORD WINAPI AsyncCallbackThread(LPVOID lpParameter); static VOID CALLBACK AsyncInternetCallback(HINTERNET hInternet, DWORD dwContext, DWORD dwInternetStatus, LPVOID lpvStatusInformation, DWORD dwStatusInformationLength); };
#include "AsyncWinINet.h" #include "stdafx.h" #pragma comment(lib, "Winmm.lib") #pragma comment(lib, "Wininet.lib") DWORD AsyncWinINet::download(const Fagex::String &url, const Fagex::StringMap &request_headrs, const Fagex::AsyncWinINet::notify_fp &pfp, const Fagex::StringMap &pfp_param, Fagex::String &response_headrs, const Fagex::String &saved_filename, Fagex::String &response_content, size_t read_content_size) { thread_info info(url, request_headrs, pfp, pfp_param, response_headrs, saved_filename, response_content, read_content_size); info.hThread = CreateThread(NULL, 0, AsyncWinINet::AsyncThread, &info, NULL, &info.dwThreadID); WaitForSingleObject(info.hThread, INFINITE); //等待子线程安全退出 CloseHandle(info.hThread);//关闭线程句柄 return TRUE; } //--------------------------------------------------------------------- DWORD WINAPI AsyncWinINet::AsyncThread(LPVOID lpParameter) { thread_info* p = (thread_info*)lpParameter; //a. 使用标记 INTERNET_FLAG_ASYNC 初始化 InternetOpen String user_agent("Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; TencentTraveler ; .NET CLR 2.0.50727)"); StringMap iheadrs(p->request_headrs.begin(), p->request_headrs.end()); StringMap::iterator it = iheadrs.find("User-Agent"); if(it == iheadrs.end()) iheadrs["User-Agent"] = user_agent; else user_agent = it->second; p->hInternet = InternetOpen(user_agent.c_str(), INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, INTERNET_FLAG_ASYNC); //ResetEvent(p->hEvent[0]); //p->hCallbackThread = CreateThread(NULL, // 0, // AsyncWinINet::AsyncCallbackThread, // p, // NULL, // &p->dwCallbackThreadID); //WaitForSingleObject(p->hEvent[0], INFINITE);//等待回调函数设置成功事件 InternetSetStatusCallback(p->hInternet, AsyncWinINet::AsyncInternetCallback); String sheadrs; for(it = iheadrs.begin(); it != iheadrs.end(); ++it) { sheadrs += it->first + ":" + it->second; if(it->second.find(StringUtil::enter) == String::npos) { sheadrs += StringUtil::enter; } } sheadrs += StringUtil::enter; DWORD start_time = timeGetTime(); ResetEvent(p->hEvent[0]); //重置句柄被创建事件 p->hFile = InternetOpenUrl(p->hInternet, p->url.c_str(), sheadrs.c_str(), sheadrs.length(), INTERNET_FLAG_DONT_CACHE | INTERNET_FLAG_RELOAD, (DWORD)p); FILE *fp = fopen(p->saved_filename.c_str(), "w+"); while(true) { if (NULL == p->hFile) { DWORD dwError = ::GetLastError(); if (ERROR_IO_PENDING == dwError || ERROR_SUCCESS == dwError) { if (WaitExitEvent(p)) { break; } } else break; } //读取返回文件头 DWORD dwLength = 0; LPVOID lpOutBuffer = NULL; while(true) //读取response_headrs数据 { if(!HttpQueryInfo(p->hFile, HTTP_QUERY_RAW_HEADERS_CRLF, lpOutBuffer, &dwLength, NULL)) { DWORD err_code = GetLastError(); if (err_code == ERROR_HTTP_HEADER_NOT_FOUND) break; else if(err_code == ERROR_INSUFFICIENT_BUFFER) { lpOutBuffer = new char[dwLength]; continue; } else break; } break; } if(lpOutBuffer != NULL) { p->response_headrs.append((char*)lpOutBuffer,dwLength); delete [] lpOutBuffer; } //e. 使用 HttpQueryInfo 分析头信息 HttpQueryInfo 使用非阻塞方式,所以不用等待 DWORD dwStatusSize = sizeof(p->dwStatusCode); if (FALSE == HttpQueryInfo(p->hFile, //获取返回状态码 HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, &p->dwStatusCode, &dwStatusSize, NULL)) { break; } //判断状态码是不是 200 if (HTTP_STATUS_OK != p->dwStatusCode) break; StringMap msgMap(p->pfp_param.begin(), p->pfp_param.end()); msgMap["url"] = p->url; //获取返回的Content-Length //DWORD dwLengthSize = sizeof(p->dwContentLength); //if (FALSE == HttpQueryInfo(p->hFile, //HTTP_QUERY_CONTENT_LENGTH | HTTP_QUERY_FLAG_NUMBER, //&p->dwContentLength, &dwLengthSize, NULL)) { p->dwContentLength = 0; } //f. 使用标记 IRF_ASYNC 读数据 InternetReadFileEx //为了向主线程报告进度,我们设置每次读数据最多 1024 字节 char lpvBuffer[1024]; p->dwContentLength = 0; //Content-Length: 202749 while(true) { INTERNET_BUFFERS i_buf = {0}; i_buf.dwStructSize = sizeof(INTERNET_BUFFERS); i_buf.lpvBuffer = lpvBuffer; i_buf.dwBufferLength = 1024; //重置读数据事件 ResetEvent(p->hEvent[0]); if (FALSE == InternetReadFileEx(p->hFile, &i_buf, IRF_ASYNC, (DWORD)p)) { if (ERROR_IO_PENDING == ::GetLastError()) { if (WaitExitEvent(p)) break; } else break; } else { //在网络传输速度快,步长较小的情况下,InternetReadFileEx 经常会直接返回成功, //因此要判断是否发生了用户要求终止子线程事件。 if (WAIT_OBJECT_0 == WaitForSingleObject(p->hEvent[2], 0)) { ResetEvent(p->hEvent[2]); break; } } if(i_buf.dwBufferLength == 0) { DWORD time = timeGetTime() - start_time; if(time != 0) { Real speed = (Real)p->dwContentLength; speed /= ((Real)time)/1000.0f; speed /= 1024.0f; msgMap["speed"] = StringUtil::toString((DWORD)speed); } if(p->pfp) p->pfp(msgMap); break; } if(fp) { fwrite(i_buf.lpvBuffer, sizeof(char), i_buf.dwBufferLength, fp); } if(p->read_content_size > p->response_content.size()) { p->response_content.append((char*)i_buf.lpvBuffer, i_buf.dwBufferLength); } p->dwContentLength += i_buf.dwBufferLength; } break; } if(fp) { fflush(fp); fclose(fp); fp = NULL; } if(p->hFile) { InternetCloseHandle(p->hFile);//关闭 m_hFile while (!WaitExitEvent(p)) //等待句柄被关闭事件或者要求子线程退出事件 { ResetEvent(p->hEvent[0]); } } //设置子线程退出事件,通知回调线程退出 SetEvent(p->hEvent[2]); //等待回调线程安全退出 //WaitForSingleObject(p->hCallbackThread, INFINITE); //CloseHandle(p->hCallbackThread); //注销回调函数 InternetSetStatusCallback(p->hInternet, NULL); InternetCloseHandle(p->hInternet); return TRUE; } //------------------------------------------------------------------------------------ DWORD WINAPI AsyncWinINet::AsyncCallbackThread(LPVOID lpParameter) { thread_info *p = (thread_info*)lpParameter; InternetSetStatusCallback(p->hInternet, AsyncWinINet::AsyncInternetCallback); //通知子线程回调函数设置成功,子线程可以继续工作 SetEvent(p->hEvent[0]); //等待用户终止事件或者子线程结束事件 //子线程结束前需要设置子线程结束事件,并等待回调线程结束 WaitForSingleObject(p->hEvent[2], INFINITE); return 0; } //---------------------------------------------------------------------------- VOID CALLBACK AsyncWinINet::AsyncInternetCallback(HINTERNET hInternet, DWORD dwContext, DWORD dwInternetStatus, LPVOID lpvStatusInformation, DWORD dwStatusInformationLength) { thread_info* p = (thread_info*)dwContext; //在我们的应用中,我们只关心下面三个状态 switch(dwInternetStatus) { //句柄被创建 case INTERNET_STATUS_HANDLE_CREATED: p->hFile = (HINTERNET)(((LPINTERNET_ASYNC_RESULT) (lpvStatusInformation))->dwResult); break; //句柄被关闭 case INTERNET_STATUS_HANDLE_CLOSING: SetEvent(p->hEvent[1]); break; //一个请求完成,比如一次句柄创建的请求,或者一次读数据的请求 case INTERNET_STATUS_REQUEST_COMPLETE: if (ERROR_SUCCESS == ((LPINTERNET_ASYNC_RESULT) (lpvStatusInformation))->dwError) { //设置句柄被创建事件或者读数据成功完成事件 SetEvent(p->hEvent[0]); } else { //如果发生错误,则设置子线程退出事件 这里也是一个陷阱,经常会忽视处理这个错误, SetEvent(p->hEvent[2]); } break; case INTERNET_STATUS_CONNECTION_CLOSED: SetEvent(p->hEvent[2]); break; } } //-------------------------------------------------------------------- BOOL AsyncWinINet::WaitExitEvent(thread_info *p) { DWORD dwRet = WaitForMultipleObjects(3, p->hEvent, FALSE, INFINITE); switch (dwRet) { case WAIT_OBJECT_0://句柄被创建事件或者读数据请求成功完成事件 case WAIT_OBJECT_0+1://句柄被关闭事件 case WAIT_OBJECT_0+2://用户要求终止子线程事件或者发生错误事件 break; } return WAIT_OBJECT_0 != dwRet; }