• <ruby id="5koa6"></ruby>
    <ruby id="5koa6"><option id="5koa6"><thead id="5koa6"></thead></option></ruby>

    <progress id="5koa6"></progress>

  • <strong id="5koa6"></strong>
  • WinInet API 的異步方式使用

    發表于:2007-07-01來源:作者:點擊數: 標簽:
    作者郵箱: 異步方式并不是什么高深莫測的事物,WinInet API 更是大家耳熟能詳。 如果你仔細看過 MSDN 和 internet 上關于 WinInet API 的文章,你會發現盡管在很多篇章中提到了異步方式的使用,但是大部分說明都只說可以使用,而沒有說如何使用。盡管如此,

    作者郵箱:

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

    如果你仔細看過 MSDN 和 internet 上關于 WinInet API 的文章,你會發現盡管在很多篇章中提到了異步方式的使用,但是大部分說明都只說可以使用,而沒有說如何使用。盡管如此,還是有一些文章可以給我們很多的提示,我會在后面列出。

    由于網絡數據傳輸經常會消耗一定的時間,因此我們總是把這些可能消耗時間的操作放到一個單獨的子線程,以免影響主線程正常的進行??墒钱斪泳€程發生長時間阻塞的時候,主線程由于某種原因需要退出,我們通常希望子線程能在主線程退出前正常退出。這時主線程就不得不 wait 子線程,這樣就導致主線程也被阻塞了。當然,主線程可以不 wait 子線程而自行退出,還可以使用 TerminateThread 強行終止子線程,但是這樣的后果通常是不可預料的,內存泄漏或許是最輕的一種危害了。

    使用異步方式是解決這類問題的正確手段,下面我們根據一個實例來分析一下 WinInet API 異步方式的使用方法和注意事項。

    我們的例子完成這樣的功能:給定一個 URL (如:),使用 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


    參考文獻:
     1.
     2. MSDN: <Technical Articles\Web Development\Authoring and Programming\Advanced FTP, or Teaching Fido To Phetch>
     3. MSDN: <Platform SDK Documentation\Web Development\Internet Development SDK\Win32 Internet Functions\Common Functions>
     4. MSDN: <Platform SDK Documentation\Web Development\Internet Development SDK\Win32 Internet Functions\Tutorials\Calling Win32 Internet Functions Asynchronously>

     


    原文轉自:http://www.kjueaiud.com

    老湿亚洲永久精品ww47香蕉图片_日韩欧美中文字幕北美法律_国产AV永久无码天堂影院_久久婷婷综合色丁香五月

  • <ruby id="5koa6"></ruby>
    <ruby id="5koa6"><option id="5koa6"><thead id="5koa6"></thead></option></ruby>

    <progress id="5koa6"></progress>

  • <strong id="5koa6"></strong>