在C#中,除了webclient咱們還可使用一組WindowsAPI來完成下載任務。這就是Windows Internet,簡稱 WinINet。本文經過一個demo來介紹WinINet的基本用法和一些實用技巧。 html
相比WebClient的用法,Win32API在使用時可能會煩瑣一些。因此先把用到的API簡單介紹一下。windows
這是須要調用的第一個方法,它會初始化內部數據結構,爲後面的調用作準備。服務器
這個方法用來關閉使用中打開的Internet句柄,釋放資源。ide
這是一個通用的函數,應用程序能夠用它來請求數據(只要是WinINet支持的協議就能夠)。尤爲是當咱們僅僅想要經過一個URL獲取數據,而不關心通訊協議相關的內容時,這個接口就特別合適。該方法會解析參數中的URL字符串,而後創建到服務器的鏈接,並準備下載由RUL標識的數據。this
檢索與HTTP請求相關的報頭信息。主要是查看請求是否成功。
從 InternetOpenUrl打開的句柄中讀取數據。
這裏咱們只介紹下載過程當中的關鍵環節,完整的過程請參考本文的demo。
當請求與服務器創建鏈接時,咱們重點考慮三個問題:請求的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);
接下來咱們開始檢查前面發送的請求返回的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進行了簡單的封裝。建立了一個繼承自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壓縮的數據,這就須要咱們先檢查數據的格式。若是是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。不過在筆者看來,更重要的是咱們能夠選用不一樣的方式去處理下載問題。