WinInet编程中如何使用异步

在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,
                              
02
))
        {
            
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  };
81
 

 

  1  #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 *= (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
 

 

异步方式并不是什么高深莫测的事物,WinInet API 更是大家耳熟能详。

如果你仔细看过 MSDN 和 internet 上关于 WinInet API 的文章,你会发现尽管在很多篇章中提到了异步方式的使用,但是大部分说明都只说可以使用,而没有说如何使用。尽管如此,还是有一些文章可以给我们很多的提示,我会在后面列出。

由于网络数据传输经常会消耗一定的时间,因此我们总是把这些可能消耗时间的操作放到一个单独的子线程,以免影响主线程正常的进行。可是当子线程发生长时间阻塞的时候,主线程由于某种原因需要退出,我们通常希望子线程能在主线程退出前正常退出。这时主线程就不得不 wait 子线程,这样就导致主线程也被阻塞了。当然,主线程可以不 wait 子线程而自行退出,还可以使用 TerminateThread 强行终止子线程,但是这样的后果通常是不可预料的,内存泄漏或许是最轻的一种危害了。

使用异步方式是解决这类问题的正确手段,下面我们根据一个实例来分析一下 WinInet API 异步方式的使用方法和注意事项。

我们的例子完成这样的功能:给定一个 URL (如:http://www.sina.com.cn/),使用 HTTP 协议下载该网页或文件。我们一共创建了三个线程:主线程负责创建下载子线程,并等待子线程返回消息;子线程则使用异步方式的 WinInet API 完成下载任务,并在各个阶段返回消息给主线程;子线程还会创建一个回调函数线程,其作用我们稍后解释。

实例代码中涉及到一些线程,消息,事件,错误处理的 API,由于不是我讨论的内容,就不仔细说明了。

1. 主线程工作流程
a. 创建下载子线程
   m_hMainThread = ::CreateThread(NULL,
    0,
    AsyncMainThread,
    this,
    NULL,
    &m_dwMainThreadID);

b. 等待子线程返回消息
   MSG msg;
   while (1)
   {
    ::GetMessage(&msg, m_hWnd, 0, 0);

    if (msg.message == WM_ASYNCGETHTTPFILE)
    { //子线程发回消息
     switch(LOWORD(msg.wParam))
     {
     case AGHF_FAIL:
      {
      MessageBox(_T("下载行动失败结束!"));
      return;
      }
     case AGHF_SUCCESS:
      MessageBox(_T("下载行动成功结束!"));
      return;
     case AGHF_PROCESS:
      //下载进度通知
      break;
     case AGHF_LENGTH:
      //获取下载文件尺寸通知
      break;
     }
    }

    DispatchMessage(&msg);
   }

2. 下载子线程工作流程
a. 使用标记 INTERNET_FLAG_ASYNC 初始化 InternetOpen
   m_hInternet = ::InternetOpen(m_szAgent,
    INTERNET_OPEN_TYPE_PRECONFIG,
    NULL,
    NULL,
    INTERNET_FLAG_ASYNC);
   起步并不费劲,也不难理解,MSDN 上说这样设置之后,以后所有的 API 调用都是异步的了。
   警惕......
   看起来好像很简单,但是会有无数的陷阱等着我们掉进去。

b. 设置状态回调函数 InternetSetStatusCallback
   ::InternetSetStatusCallback(m_hInternet, AsyncInternetCallback);
   第一个陷阱就在这里等着你呢,文献[2]中提到使用一个单独的线程来进行这项设置,并解释说如果不这样会有潜在的影响,而在其他文档中却没有这样使用的例子。尽管看起来多余,并且增加了一些复杂度,我们还是先把这种方法写出来再讨论。子线程需要创建一个回调函数线程:
   //重置回调函数设置成功事件
   ::ResetEvent(m_hEvent[0]);
   m_hCallbackThread = ::CreateThread(NULL,
    0,
    AsyncCallbackThread,
    this,
    NULL,
    &m_dwCallbackThreadID);
   //等待回调函数设置成功事件
   ::WaitForSingleObject(m_hEvent[0], INFINITE);
   回调函数线程的实现如下:
   DWORD WINAPI CAsyncGetHttpFile::AsyncCallbackThread(LPVOID lpParameter)
   {
    CAsyncGetHttpFile * pObj = (CAsyncGetHttpFile*)lpParameter;
  
    ::InternetSetStatusCallback(pObj->m_hInternet, AsyncInternetCallback);
  
    //通知子线程回调函数设置成功,子线程可以继续工作
    ::SetEvent(pObj->m_hEvent[0]);
   
    //等待用户终止事件或者子线程结束事件
    //子线程结束前需要设置子线程结束事件,并等待回调线程结束
    ::WaitForSingleObject(pObj->m_hEvent[2], INFINITE);
    return 0;
   }
   确实复杂了很多吧,虽然我试验的结果发现两种设置方法都能正确工作,但是确实发现了这两种设置方法产生的一些不同效果,遗憾的是我没有弄清具体的原因。我推荐大家使用后一种方法。

c. 打断一下子线程的流程,由于回调函数和上一部分的关系如此密切,我们来看看它的实现
   void CALLBACK CAsyncGetHttpFile::AsyncInternetCallback(
    HINTERNET hInternet,
    DWORD dwContext,
    DWORD dwInternetStatus,
    LPVOID lpvStatusInformation,
    DWORD dwStatusInformationLength)
   {
    CAsyncGetHttpFile * pObj = (CAsyncGetHttpFile*)dwContext;
    //在我们的应用中,我们只关心下面三个状态
    switch(dwInternetStatus)
    {
    //句柄被创建
    case INTERNET_STATUS_HANDLE_CREATED:
     pObj->m_hFile = (HINTERNET)(((LPINTERNET_ASYNC_RESULT)
      (lpvStatusInformation))->dwResult);
     break;
    //句柄被关闭
    case INTERNET_STATUS_HANDLE_CLOSING:
     ::SetEvent(pObj->m_hEvent[1]);
     break;
    //一个请求完成,比如一次句柄创建的请求,或者一次读数据的请求
    case INTERNET_STATUS_REQUEST_COMPLETE:
     if (ERROR_SUCCESS == ((LPINTERNET_ASYNC_RESULT)
      (lpvStatusInformation))->dwError)
     { //设置句柄被创建事件或者读数据成功完成事件
      ::SetEvent(pObj->m_hEvent[0]);
     }
     else
     { //如果发生错误,则设置子线程退出事件
      //这里也是一个陷阱,经常会忽视处理这个错误,
      ::SetEvent(pObj->m_hEvent[2]);
     }
     break;
    }
   }

d. 继续子线程的流程,使用 InternetOpenUrl 完成连接并获取下载文件头信息
   //重置句柄被创建事件
   ::ResetEvent(m_hEvent[0]);
   m_hFile = ::InternetOpenUrl(m_hInternet,
    m_szUrl,
    NULL,
    NULL,
    INTERNET_FLAG_DONT_CACHE | INTERNET_FLAG_RELOAD,
    (DWORD)this);
   if (NULL == m_hFile)
   {
    if (ERROR_IO_PENDING == ::GetLastError())
    {
     if (WaitExitEvent())
     {
      return FALSE;
     }
    }
    else
    {
     return FALSE;
    }
   }
   等我们把 WaitExitEvent 函数的实现列出在来再解释发生的一切:
   BOOL CAsyncGetHttpFile::WaitExitEvent()
   {
    DWORD dwRet = ::WaitForMultipleObjects(3, m_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;
   }
   在这里我们终于看到异步方式的巨大优势了,InternetOpenUrl 函数要完成域名解析,服务器连接,发送请求,接收返回头信息等任务,异步方式中 InternetOpenUrl 并不等待成功创建了 m_hFile 才返回,我们看到 m_hFile 是可以在回调函数中赋值的。如果 InternetOpenUrl 的返回值为 NULL 并且 GetLastError 返回 ERROR_IO_PENDING,我们使用 WaitForMultipleObjects 来等待请求的成功完成,这样主线程就有机会在这个等待过程中终止子线程的操作。我真是迫不及待的想把主线程如何强行终止子线程的代码列出来了:
   //设置要求子线程结束事件
   ::SetEvent(m_hEvent[2]);
   //等待子线程安全退出
   ::WaitForSingleObject(m_hMainThread, INFINITE);
   //关闭线程句柄
   ::CloseHandle(m_hMainThread);
   哈哈,不需要使用 TerminateThread 终止线程,一切都是安全的,可预料的。
   我们再考虑一种情况,这种情况好得超乎你的想象,InternetOpenUrl 返回了一个非空的 m_hFile 怎么办?呵呵,这说明 InternetOpenUrl 已经成功创建了一个 m_hFile,并且没有发生任何阻塞,都不用等待任何事件,直接继续下一步吧。
   最后需要说明得是,InternetOpenUrl 的最后一个参数会被作为回调函数的第二个参数使用。并且哪怕在回调函数中不需要这个参数,这个值你也不能设置为 0,否则 InternetOpenUrl 将不会按照异步的方式工作。
   到这里,我们已经将 WinInet API 的异步方式使用的关键部分都展示了,你应该可以使用 WinInet API 的异步方式写出你自己的应用了。不过还是让我们继续完成这个实例的其他部分。

e. 使用 HttpQueryInfo 分析头信息
   DWORD dwStatusSize = sizeof(m_dwStatusCode);
   if (FALSE == ::HttpQueryInfo(m_hFile,
    HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER,
    &m_dwStatusCode,
    &dwStatusSize,
    NULL))   //获取返回状态码
   {
    return FALSE;
   }
   //判断状态码是不是 200
   if (HTTP_STATUS_OK != m_dwStatusCode)
   {
    return FALSE;
   }
   DWORD dwLengthSize = sizeof(m_dwContentLength);
   if (FALSE == ::HttpQueryInfo(m_hFile,
    HTTP_QUERY_CONTENT_LENGTH | HTTP_QUERY_FLAG_NUMBER,
    &m_dwContentLength,
    &dwLengthSize,
    NULL))   //获取返回的Content-Length
   {
    return FALSE;
   }
   ...//通知主线程获取文件大小成功
   需要说明的是 HttpQueryInfo 并不进行网络操作,因此它不需要进行异步操作的处理。

f. 使用标记 IRF_ASYNC 读数据 InternetReadFileEx
   //为了向主线程报告进度,我们设置每次读数据最多 1024 字节
   for (DWORD i=0; i<m_dwContentLength; )
   {
    INTERNET_BUFFERS i_buf = {0};
    i_buf.dwStructSize = sizeof(INTERNET_BUFFERS);
    i_buf.lpvBuffer = new TCHAR[1024];
    i_buf.dwBufferLength = 1024;

    //重置读数据事件
    ::ResetEvent(m_hEvent[0]);
    if (FALSE == ::InternetReadFileEx(m_hFile,
     &i_buf,
     IRF_ASYNC,
     (DWORD)this))
    {
     if (ERROR_IO_PENDING == ::GetLastError())
     {
      if (WaitExitEvent())
      {
       delete[] i_buf.lpvBuffer;
       return FALSE;
      }
     }
     else
     {
      delete[] i_buf.lpvBuffer;
      return FALSE;
     }
    }
    else
    {
     //在网络传输速度快,步长较小的情况下,
     //InternetReadFileEx 经常会直接返回成功,
     //因此要判断是否发生了用户要求终止子线程事件。
     if (WAIT_OBJECT_0 == ::WaitForSingleObject(m_hEvent[2], 0))
     {
      ::ResetEvent(m_hEvent[2]);
      delete[] i_buf.lpvBuffer;
      return FALSE;
     }
    }
    i += i_buf.dwBufferLength;
    ...//保存数据
    ...//通知主线程下载进度
    delete[] i_buf.lpvBuffer;
   }
   这里 InternetReadFileEx 的异步处理方式同 InternetOpenUrl 的处理方式类似,我没有使用 InternetReadFile 因为它没有异步的工作方式。

g. 最后清理战场,一切都该结束了
   //关闭 m_hFile
   ::InternetCloseHandle(m_hFile);
   //等待句柄被关闭事件或者要求子线程退出事件
   while (!WaitExitEvent())
   {
    ::ResetEvent(m_hEvent[0]);
   }
   //设置子线程退出事件,通知回调线程退出
   ::SetEvent(m_hEvent[2]);
   //等待回调线程安全退出
   ::WaitForSingleObject(m_hCallbackThread, INFINITE);
   ::CloseHandle(m_hCallbackThread);
   //注销回调函数
   ::InternetSetStatusCallback(m_hInternet, NULL);
   ::InternetCloseHandle(m_hInternet);
   ...//通知主线程子线程成功或者失败退出


实例中,我们建立一个完整的 HTTP 下载程序,并且可以在主线程中对下载过程进行完全的监控。我们使用了 WinInet API 中的这些函数:
InternetOpen
InternetSetStatusCallback
InternetOpenUrl
HttpQueryInfo
InternetReadFileEx
InternetCloseHandle
其中 InternetOpenUrl 和 InternetReadFileEx 函数是按照异步方式工作的,文献[4]中列出了可以按照异步方式工作的 API:
FtpCreateDirectory
FtpDeleteFile
FtpFindFirstFile
FtpGetCurrentDirectory
FtpGetFile
FtpOpenFile
FtpPutFile
FtpRemoveDirectory
FtpRenameFile
FtpSetCurrentDirectory
GopherFindFirstFile
GopherOpenFile
HttpEndRequest
HttpOpenRequest
HttpSendRequestEx
InternetConnect
InternetOpenUrl
InternetReadFileEx

你可能感兴趣的:(thread,编程,object,null,query,internet)