在WinInet编程中,同步的使用方法如下: InternetOpen->InternetOpenUrl->HttpQueryInfo->InternetReadFile->InternetCloseHandle; 在InternetOpenUrl和InternetReadFile时会导致程序阻塞,知道操作完成,同步的好处就是比较简单,调试方便。 异步 的使用方法如下: 1)InternetOpen,需指定是异步; 2)InternetSetStatusCallback,设置回调; 3)InternetOpenUrl, 需指定回调参数; 4)WaitForSingObject或WaitForMultipleObjects,接收信号量; 5)HttpQueryInfo; 6)InternetReadFileEx, 需指定回调参数; 7)WaitForSingObject或WaitForMultipleObjects,接收信号量; 8)InternetSetStatusCallback, 卸载回调; 9)InternetCloseHandle。 异步比同步要复杂了不少,重点在于回调函数。在回调中,系统会及时返回各种系统 定义的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(); } } } }参考的异步类:
1 include < wininet.h > 2 #include < mmsystem.h > 3 4 class AsyncWinINet 5 { 6 public : 7 typedef void ( * notify_fp)( const StringMap & ); 8 9 class thread_info 10 { 11 public : 12 thread_info( const String & _url, // 请求下载的地址 (in) 13 const StringMap & _request_headrs, // 请求头request_headrs(in) 14 const notify_fp & _pfp, // 下载进度通知回调函 数指针 15 const StringMap & _pfp_param, 16 String & _response_headrs, // 返回头response_headrs(out) 17 const String & _saved_filename, // 下载内容保存文件名(in) 18 String & _response_content, // 返回内容(out) 19 size_t _read_content_size) // 控制保存在response_content中内容的长度(in)) : 20 : request_headrs(_request_headrs), pfp(_pfp), 21 pfp_param(_pfp_param), // pfp函数传回参数 22 response_headrs(_response_headrs), saved_filename(_saved_filename), 23 response_content(_response_content), read_content_size(_read_content_size) 24 { 25 this -> response_headrs.clear(); 26 this -> response_content.clear(); 27 this -> url = StringUtil::EncodeURIComponent(_url); 28 for ( int i = 0 ; i < 3 ; ++ i) 29 { 30 this -> hEvent[i] = CreateEvent(NULL,TRUE,FALSE,NULL); 31 } 32 } 33 34 HANDLE hThread; 35 DWORD dwThreadID; 36 HANDLE hCallbackThread; 37 DWORD dwCallbackThreadID; 38 HANDLE hEvent[ 3 ]; 39 LPVOID hInternet; 40 LPVOID hFile; 41 DWORD dwStatusCode; 42 DWORD dwContentLength; 43 44 String url; // 请求下载的地址(in) 45 const StringMap & request_headrs; // 请求头request_headrs(in) 46 const notify_fp & pfp; // 下载进度通知回调函数指针 47 const StringMap & pfp_param; // pfp函数传回参数 48 49 String & response_headrs; // 返回头response_headrs(out) 50 const String & saved_filename; // 下载内容保存文件名(in) 51 String & response_content; // 返回内容(out) 52 size_t read_content_size; // 控制保存在 response_content中内容的长度(in) 53 }; 54 55 /* ****************************************************************************** 56 * 函数:download 57 * 功能:下载,返回 WinINet_ERR_CODE值 58 * 说明:关于notify_fp 类型说明: 函数的参数为 StringMap类型,传回的变量名与变量值 59 * 2007-12 60 ****************************************************************************** */ 61 static DWORD download( const String & url, // 请求下载的地址(in) 62 const StringMap & request_headrs, // 请求头request_headrs(in) 63 const notify_fp & pfp, // 下载进度通知回调函数指针 64 const StringMap & pfp_param, // pfp函数传回参数 65 String & response_headrs, // 返回头response_headrs(out) 66 const String & saved_filename, // 下载内容保存文件名(in) 67 String & response_content, // 返回内容(out) 68 size_t read_content_size = 0 ); // 控制保存在response_content中内容的长度(in) 69 70 protected : 71 static BOOL WaitExitEvent(thread_info * p); 72 static DWORD WINAPI AsyncThread(LPVOID lpParameter); 73 static DWORD WINAPI AsyncCallbackThread(LPVOID lpParameter); 74 static VOID CALLBACK AsyncInternetCallback(HINTERNET hInternet, 75 DWORD dwContext, 76 DWORD dwInternetStatus, 77 LPVOID lpvStatusInformation, 78 DWORD dwStatusInformationLength); 79 80 }; 811 #include " AsyncWinINet.h " 2 3 #include " stdafx.h " 4 5 #pragma comment(lib, " Winmm.lib " ) 6 #pragma comment(lib, " Wininet.lib " ) 7 8 DWORD AsyncWinINet::download( const Fagex::String & url, const Fagex::StringMap & request_headrs, 9 const Fagex::AsyncWinINet::notify_fp & pfp, const Fagex::StringMap & pfp_param, Fagex::String & response_headrs, 10 const Fagex::String & saved_filename, Fagex::String & response_content, size_t read_content_size) 11 { 12 thread_info info(url, request_headrs, pfp, 13 pfp_param, response_headrs, saved_filename, 14 response_content, read_content_size); 15 16 info.hThread = CreateThread(NULL, 17 0 , 18 AsyncWinINet::AsyncThread, 19 & info, 20 NULL, 21 & info.dwThreadID); 22 23 WaitForSingleObject(info.hThread, INFINITE); // 等待子线程安全退出 24 CloseHandle(info.hThread); // 关闭线程句柄 25 26 return TRUE; 27 } 28 29 // --------------------------------------------------------------------- 30 DWORD WINAPI AsyncWinINet::AsyncThread(LPVOID lpParameter) 31 { 32 thread_info * p = (thread_info * )lpParameter; 33 34 // a. 使用标 记 INTERNET_FLAG_ASYNC 初始化 InternetOpen 35 String user_agent( " Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; TencentTraveler ; .NET CLR 2.0.50727) " ); 36 StringMap iheadrs(p -> request_headrs.begin(), p -> request_headrs.end()); 37 StringMap::iterator it = iheadrs.find( " User-Agent " ); 38 if (it == iheadrs.end()) iheadrs[ " User-Agent " ] = user_agent; 39 else user_agent = it -> second; 40 41 p -> hInternet = InternetOpen(user_agent.c_str(), 42 INTERNET_OPEN_TYPE_PRECONFIG, 43 NULL, 44 NULL, 45 INTERNET_FLAG_ASYNC); 46 47 // ResetEvent(p->hEvent[0]); 48 // p->hCallbackThread = CreateThread(NULL, 49 // 0, 50 // AsyncWinINet::AsyncCallbackThread, 51 // p, 52 // NULL, 53 // &p->dwCallbackThreadID); 54 // WaitForSingleObject(p->hEvent[0], INFINITE); // 等待回调函数设置成功事件 55 InternetSetStatusCallback(p -> hInternet, AsyncWinINet::AsyncInternetCallback); 56 57 String sheadrs; 58 for (it = iheadrs.begin(); it != iheadrs.end(); ++ it) 59 { 60 sheadrs += it -> first + " : " + it -> second; 61 if (it -> second.find(StringUtil::enter) == String::npos) { sheadrs += StringUtil::enter; } 62 } 63 sheadrs += StringUtil::enter; 64 65 DWORD start_time = timeGetTime(); 66 ResetEvent(p -> hEvent[ 0 ]); // 重置句柄被创建事件 67 p -> hFile = InternetOpenUrl(p -> hInternet, p -> url.c_str(), sheadrs.c_str(), sheadrs.length(), 68 INTERNET_FLAG_DONT_CACHE | INTERNET_FLAG_RELOAD, (DWORD)p); 69 70 FILE * fp = fopen(p -> saved_filename.c_str(), " w+ " ); 71 while ( true ) 72 { 73 if (NULL == p -> hFile) 74 { 75 DWORD dwError = ::GetLastError(); 76 if (ERROR_IO_PENDING == dwError || ERROR_SUCCESS == dwError) 77 { 78 if (WaitExitEvent(p)) { break ; } 79 } 80 else break ; 81 } 82 83 // 读取返回文件头 84 DWORD dwLength = 0 ; 85 LPVOID lpOutBuffer = NULL; 86 while ( true ) // 读取response_headrs数据 87 { 88 if ( ! HttpQueryInfo(p -> hFile, HTTP_QUERY_RAW_HEADERS_CRLF, 89 lpOutBuffer, & dwLength, NULL)) 90 { 91 DWORD err_code = GetLastError(); 92 if (err_code == ERROR_HTTP_HEADER_NOT_FOUND) break ; 93 else if (err_code == ERROR_INSUFFICIENT_BUFFER) 94 { 95 lpOutBuffer = new char [dwLength]; 96 continue ; 97 } 98 else break ; 99 } 100 break ; 101 } 102 if (lpOutBuffer != NULL) 103 { 104 p -> response_headrs.append(( char * )lpOutBuffer,dwLength); 105 delete [] lpOutBuffer; 106 } 107 108 // e. 使 用 HttpQueryInfo 分析头信息 HttpQueryInfo 使用非阻塞方式,所以不用等待 109 DWORD dwStatusSize = sizeof (p -> dwStatusCode); 110 if (FALSE == HttpQueryInfo(p -> hFile, // 获取返回状态码 111 HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, 112 & p -> dwStatusCode, & dwStatusSize, NULL)) { break ; } 113 114 // 判断状态码是不是 200 115 if (HTTP_STATUS_OK != p -> dwStatusCode) break ; 116 117 StringMap msgMap(p -> pfp_param.begin(), p -> pfp_param.end()); 118 msgMap[ " url " ] = p -> url; 119 120 // 获取返回的Content-Length 121 // DWORD dwLengthSize = sizeof(p->dwContentLength); 122 // if (FALSE == HttpQueryInfo(p->hFile, 123 // HTTP_QUERY_CONTENT_LENGTH | HTTP_QUERY_FLAG_NUMBER, 124 // &p->dwContentLength, &dwLengthSize, NULL)) { p->dwContentLength = 0; } 125 126 // f. 使用标 记 IRF_ASYNC 读数据 InternetReadFileEx 127 // 为了向主线程报告进度,我们设置每次读数据最多 1024 字节 128 129 char lpvBuffer[ 1024 ]; 130 p -> dwContentLength = 0 ; // Content-Length: 202749 131 while ( true ) 132 { 133 INTERNET_BUFFERS i_buf = { 0 }; 134 i_buf.dwStructSize = sizeof (INTERNET_BUFFERS); 135 i_buf.lpvBuffer = lpvBuffer; 136 i_buf.dwBufferLength = 1024 ; 137 138 // 重置读数据事件 139 ResetEvent(p -> hEvent[ 0 ]); 140 if (FALSE == InternetReadFileEx(p -> hFile, & i_buf, IRF_ASYNC, (DWORD)p)) 141 { 142 if (ERROR_IO_PENDING == ::GetLastError()) 143 { 144 if (WaitExitEvent(p)) break ; 145 } 146 else break ; 147 } 148 else 149 { 150 // 在网络传输速度快,步长较小的情况 下,InternetReadFileEx 经常会直接返回成功, 151 // 因此要判断是否发生了用户要求终止子线程事件。 152 if (WAIT_OBJECT_0 == WaitForSingleObject(p -> hEvent[ 2 ], 0 )) 153 { 154 ResetEvent(p -> hEvent[ 2 ]); 155 break ; 156 } 157 } 158 159 if (i_buf.dwBufferLength == 0 ) 160 { 161 DWORD time = timeGetTime() - start_time; 162 if (time != 0 ) 163 { 164 Real speed = (Real)p -> dwContentLength; 165 speed /= ((Real)time) / 1000.0f ; 166 speed /= 1024.0f ; 167 msgMap[ " speed " ] = StringUtil::toString((DWORD)speed); 168 } 169 if (p -> pfp) p -> pfp(msgMap); 170 break ; 171 } 172 if (fp) 173 { 174 fwrite(i_buf.lpvBuffer, sizeof ( char ), i_buf.dwBufferLength, fp); 175 } 176 if (p -> read_content_size > p -> response_content.size()) 177 { 178 p -> response_content.append(( char * )i_buf.lpvBuffer, i_buf.dwBufferLength); 179 } 180 p -> dwContentLength += i_buf.dwBufferLength; 181 } 182 break ; 183 } 184 185 if (fp) 186 { 187 fflush(fp); fclose(fp); fp = NULL; 188 } 189 190 if (p -> hFile) 191 { 192 InternetCloseHandle(p -> hFile); // 关闭 m_hFile 193 while ( ! WaitExitEvent(p)) // 等待句柄被关闭事件或者要求子线程退出事件 194 { 195 ResetEvent(p -> hEvent[ 0 ]); 196 } 197 } 198 199 // 设置子线程退出事件,通知回调线程退出 200 SetEvent(p -> hEvent[ 2 ]); 201 202 // 等待回调线程安全退出 203 // WaitForSingleObject(p->hCallbackThread, INFINITE); 204 // CloseHandle(p->hCallbackThread); 205 206 // 注销回调函数 207 InternetSetStatusCallback(p -> hInternet, NULL); 208 InternetCloseHandle(p -> hInternet); 209 210 return TRUE; 211 } 212 213 // ------------------------------------------------------------------------------------ 214 DWORD WINAPI AsyncWinINet::AsyncCallbackThread(LPVOID lpParameter) 215 { 216 thread_info * p = (thread_info * )lpParameter; 217 InternetSetStatusCallback(p -> hInternet, AsyncWinINet::AsyncInternetCallback); 218 219 // 通知子线程回调函数设置成功,子线程可以继续 工作 220 SetEvent(p -> hEvent[ 0 ]); 221 222 // 等待用户终止事件或者子线程结束事件 223 // 子线程结束前需要设置子线程结束事件,并等待回调线程结束 224 WaitForSingleObject(p -> hEvent[ 2 ], INFINITE); 225 226 return 0 ; 227 } 228 229 // ---------------------------------------------------------------------------- 230 VOID CALLBACK AsyncWinINet::AsyncInternetCallback(HINTERNET hInternet, 231 DWORD dwContext, 232 DWORD dwInternetStatus, 233 LPVOID lpvStatusInformation, 234 DWORD dwStatusInformationLength) 235 { 236 thread_info * p = (thread_info * )dwContext; 237 238 // 在我们的应用中,我们只关心下面三个状态 239 switch (dwInternetStatus) 240 { 241 // 句柄被创建 242 case INTERNET_STATUS_HANDLE_CREATED: 243 p -> hFile = (HINTERNET)(((LPINTERNET_ASYNC_RESULT) 244 (lpvStatusInformation)) -> dwResult); 245 break ; 246 247 // 句柄被关闭 248 case INTERNET_STATUS_HANDLE_CLOSING: 249 SetEvent(p -> hEvent[ 1 ]); 250 break ; 251 252 // 一个请求完成,比如一次句柄创建的请求,或者一次读数据的请求 253 case INTERNET_STATUS_REQUEST_COMPLETE: 254 if (ERROR_SUCCESS == ((LPINTERNET_ASYNC_RESULT) 255 (lpvStatusInformation)) -> dwError) 256 { 257 // 设置句柄被创建事件或者读数据成功 完成事件 258 SetEvent(p -> hEvent[ 0 ]); 259 } 260 else 261 { 262 // 如果发生错误,则设置子线程退出事件 这里也是一个陷阱,经常会忽视处理这个错误, 263 SetEvent(p -> hEvent[ 2 ]); 264 } 265 break ; 266 267 case INTERNET_STATUS_CONNECTION_CLOSED: 268 SetEvent(p -> hEvent[ 2 ]); 269 break ; 270 271 } 272 } 273 274 // -------------------------------------------------------------------- 275 BOOL AsyncWinINet::WaitExitEvent(thread_info * p) 276 { 277 DWORD dwRet = WaitForMultipleObjects( 3 , p -> hEvent, FALSE, INFINITE); 278 279 switch (dwRet) 280 { 281 case WAIT_OBJECT_0: // 句柄被创建事件或者 读数据请求成功完成事件 282 case WAIT_OBJECT_0 + 1 : // 句柄被关闭事件 283 case WAIT_OBJECT_0 + 2 : // 用户要求终止子线程事件或者发生错误事件 284 break ; 285 } 286 return WAIT_OBJECT_0 != dwRet; 287 } 288