Winform文件下載之WinINet


在C#中,除了webclient我們還可以使用一組WindowsAPI來完成下載任務。這就是Windows Internet,簡稱 WinINet。本文通過一個demo來介紹WinINet的基本用法和一些實用技巧。 

  • 系列文章

Winform文件下載之WebClient

 

  • 接口介紹

相比WebClient的用法,Win32API在使用時可能會煩瑣一些。所以先把用到的API簡單介紹一下。

 

資源的初始化和釋放

InternetOpen

這是需要調用的第一個方法,它會初始化內部數據結構,為后面的調用做准備。

InternetCloseHandle

這個方法用來關閉使用中打開的Internet句柄,釋放資源。

 

建立到服務器的連接

InternetOpenUrl

這是一個通用的函數,應用程序可以用它來請求數據(只要是WinINet支持的協議就可以)。尤其是當我們僅僅想要通過一個URL獲取數據,而不關心通信協議相關的內容時,這個接口就特別合適。該方法會解析參數中的URL字符串,然后建立到服務器的連接,並准備下載由RUL標識的數據。

 

檢查響應信息

HttpQueryInfo

檢索與HTTP請求相關的報頭信息。主要是查看請求是否成功。

 

讀取響應內容

InternetReadFile

從 InternetOpenUrl打開的句柄中讀取數據。

 

  • 下載過程

這里我們只介紹下載過程中的關鍵環節,完整的過程請參考本文的demo。

 

InternetOpenUrl

當請求與服務器建立連接時,我們重點考慮三個問題:請求的url,是否使用 RELOAD 標識, 客戶端是否支持gzip壓縮。

 

請求的url不用多說,這里直接請求一個http url.

 

我們不希望拿到客戶端緩存中的數據,所以希望每次請求都能夠從服務器重新下載。此時需要為InternetOpenUrl方法傳入INTERNET_FLAG_RELOAD 標識。

 

當前絕大多數的web服務器都是支持gzip壓縮的,我們的客戶端當然也要能夠解壓縮服務器傳回來的gzip格式的數據。所以我們要在請求中告訴服務器,客戶端是能夠處理gzip數據的。只有這樣,服務器才會主動的返回gzip格式的數據。

代碼如下:

string referer = "Referer: xxxxxx\nAccept-Encoding: gzip";

// INTERNET_FLAG_RELOAD -> 0x80000000

// 跳過緩存,強制從原始的服務器下載數據

hInetFile = NativeMethods.InternetOpenUrl(this._hInet, uri.AbsoluteUri, referer, referer.Length, 0x80000000, IntPtr.Zero);

  

 

HttpQueryInfo

接下來我們開始檢查前面發送的請求返回的header中的信息。主要是:請求的資源是否存在,返回的數據有多長,返回的文件的原始名稱是什么,返回的數據是以什么格式被壓縮的。

 

我們先要通過檢查返回的狀態碼來確定請求是否成功,也就是返回的是不是200。

byte[] content = new byte[BufferSize];

int count = BufferSize;

int temp = 0;

NativeMethods.HttpQueryInfo(hInetFile, 19, content, out count, out temp)

statuscode = Encoding.Unicode.GetString(content, 0, count);

  

正確返回時,statuscode應該是 “200”。

不要對HttpQueryInfo的第二個參數感到奇怪,為了獲得請求的返回狀態我們就得傳入19。你可以參考Query Onfo Flags 。

用類似的方法可以得到返回數據的長度,原始的文件名稱,返回數據的格式。

 

InternetReadFile

前面一切順利的話就可以讀取數據了。這個方法本身沒什么可說的,但出於簡化操作的目的,筆者對InternetReadFile進行了簡單的封裝。創建了一個繼承自Stream的類MyInternetReadStream。在重寫的 Read方法中調用InternetReadFile,並且添加了一個回調方法用來計算下載進度等信息。下面是代碼概要,完整代碼請參考demo。

public override int Read(byte[] buffer, int offset, int count)

{

    int dwNumberOfBytesToRead = Math.Min(BufferSize, count);

    int length = 0;

    NativeMethods.InternetReadFile(this._hInetFile, this._localBuffer,
dwNumberOfBytesToRead, out length) Array.Copy(this._localBuffer, 0, buffer, offset, length); this._bytesReadCallback(length, this._contentLength); return length; }

  

 

Gzip stream

前面我們提到,服務器可能返回的是經過gzip壓縮的數據,這就需要我們先檢查數據的格式。如果是gzip格式的數據就需要把它解壓縮。其實這在C#中是很簡單的,我們只要把剛才創建的MyInternetReadStream的實例傳給GZipStream的構造函數,創建一個新的GZipStream實例就可以了。

private Stream GetInternetStream(IntPtr hInetFile)

{

    //檢查數據是不是gzip格式

    string contentEncoding = MyWinInet.GetContentEncoding(hInetFile);

    if (contentEncoding.IndexOf("gzip", StringComparison.OrdinalIgnoreCase) != -1)

    {

        return new GZipStream(this.ForGZipReadStream(hInetFile), CompressionMode.Decompress, false);

    }

    …

}

private Stream ForGZipReadStream(IntPtr hInetFile)

{
return new MyWinInet.MyInternetReadStream(hInetFile, 
new MyWinInet.MyInternetReadStream.BytesReadCallback(this.BytesReadCallback)); }

  

 

至於計算下載進度,實時的下載速度的實現和Winform文件下載之WebClient 中的實現基本相同,請參考上文,或者直接看本文的demo。

 

總結:相比WebClient,使用WinINet接口要煩瑣不少。當然也有一定的優勢,比如前文中提到的代理問題,WinINet的默認設置就能處理好Credentials。不過在筆者看來,更重要的是我們可以選用不同的方式去處理下載問題。

 

  • Demo 下載:

WinInetDemo


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM