您的位置:首页 > 编程语言

WInINet编程中如何使用异步

2016-01-25 10:15 204 查看
在WinInet编程中,同步的使用方法如下:
InternetOpen->InternetOpenUrl->HttpQueryInfo->InternetReadFile->InternetCloseHandle;在InternetOpenUrl和InternetReadFile时会导致程序阻塞,知道操作完成,同步的好处就是比较简单,调试方便。
异步的使用方法如下:
1)InternetOpen,需指定是异步;
2)InternetSetStatusCallback,设置回调;
3)InternetOpenUrl,需指定回调参数;
4)WaitForSingObject或WaitForMultipleObjects,接收信号量;
5)HttpQueryInfo;
6)InternetReadFileEx,需指定回调参数(CE或mobile下是InternetReadFileExA);
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 };
81
#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 }


本文为转载,如有侵权,请联系我:534624117@qq.com,原文地址:http://www.cppblog.com/zhangyq/archive/2009/05/17/83191.aspx
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  WinINet