Winform文件下載之WinINet

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

  • 系列文章

Winform文件下載之WebClientweb

 

  • 接口介紹

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

 

資源的初始化和釋放

InternetOpen緩存

這是須要調用的第一個方法,它會初始化內部數據結構,爲後面的調用作準備。服務器

InternetCloseHandle數據結構

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

 

創建到服務器的鏈接

InternetOpenUrl函數

這是一個通用的函數,應用程序能夠用它來請求數據(只要是WinINet支持的協議就能夠)。尤爲是當咱們僅僅想要經過一個URL獲取數據,而不關心通訊協議相關的內容時,這個接口就特別合適。該方法會解析參數中的URL字符串,而後創建到服務器的鏈接,並準備下載由RUL標識的數據。this

 

檢查響應信息

HttpQueryInfourl

檢索與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

相關文章
相關標籤/搜索