同步和異步的概念在此就不囉唆了。
以下載一個文件爲例,我們來看看同步的做法:
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的文件中,在微軟的網站上應該可以得到。
省去其他,我們單單看看回調的寫法:
......
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();
}
}
}
}