2015 年 11 月

第 30 卷,第 12 期

本文章是由機器翻譯。

ASP.NET - 將 ASP.NET 用做高效能的檔案下載程式

Doug Duerner

緩慢和錯誤的連線已長時間的大型檔案下載致命傷。您可以透過在簡報期間很長的航班、 或在非洲嘗試下載太陽供電水幫浦透過衛星連結的大型的安裝檔案的大上運作的簡略 WiFi 連接收集媒體機場 concourse 中。在任一個執行個體,讓大型檔案下載損毀的成本是相同: 時間遺失清空的產能和 imperiled 的作業是否成功。

它不一定要是如此。在本文中我們會示範如何建立一個公用程式來解決問題的繼續並繼續進行失敗可能因不佳容易卸除離線期間大型檔案傳輸的連線的下載。

背景

我們想要建立簡單的檔案下載公用程式可以輕鬆地加入至現有 IIS 網頁伺服器與極為簡單和容易使用用戶端程式 (或只做為用戶端使用網頁瀏覽器的選項)。

IIS Web 伺服器已證明具有高擴充性的企業級 Web 伺服器親友多年的瀏覽器的檔案。我們基本上被想要利用的同時,平行處理許多 HTTP Web 要求並套用至檔案下載 (複製) 的 IIS Web 伺服器的能力。

基本上,我們需要可以有時位於遠端區域與緩慢和通常故障的網路連結世界各地的使用者下載大型檔案的檔案下載公用程式。某些世界各地的遠端使用者的可能性仍在使用數據機連結或故障的附屬可能離線隨機時間或間歇性線上和離線之間切換,公用程式需要極有彈性的重試無法下載的檔案部分的能力。我們不希望使用者花長談透過慢速連結,下載大型檔案並在網路連結一個小波動是否需要重新開始下載整個程序。我們也必須確保這些巨大正在下載的檔案不在伺服器記憶體中緩衝和伺服器記憶體使用狀況已降到最低,讓記憶體使用量不會保留上升直到伺服器失敗時有許多使用者已在相同的時間下載檔案。

相反地,如果使用者已幸運有可靠的高速網路連結 — 與正在高階電腦配備了多個 Cpu 和網路卡的用戶端和伺服器機器 — 我們想要能夠使用多個執行緒和多個連接,將檔案下載使用者以平行方式使用所有硬體資源允許在同一時間的多個區塊的檔案下載而在同時使用最小伺服器記憶體。

簡單的說,我們建立了簡單、 多執行緒、 平行、 低記憶體使用量檔案下載公用程式可以將檔案分割成區塊 (chunk)、 下載個別的執行緒上的個別區塊並允許使用者再試一次下載失敗的區塊。

本文所附的範例專案包含檔案下載公用程式的程式碼,並提供基本的基底基礎結構可展開從現在開始,可讓您更複雜一點需要發生。

範例專案概觀

DownloadHandler.dll 在本質上,將現有的 IIS Web 伺服器轉換成可讓您下載平行區塊 (chunk) 檔案中所示使用獨立可執行用戶端 (FileDownloader.exe) 中的簡單 URL 多執行緒的檔案下載 [圖 1。請注意,參數 (chunksize = 5242880) 為選擇性,而且如果未包含,將會下載一個區塊中的整個檔案的預設值。[圖 2[圖 3 示範如何讓您重複重試的失敗的部分的檔案,直到成功為止,而不用完全重新啟動整個下載從一開始就像大部分其他檔案下載軟體。

處理流程 DownloadHandler.dll 高階設計概觀
圖 1 高階設計概觀的處理流程 DownloadHandler.dll (做為用戶端使用 FileDownloader.exe)

下載用戶端的獨立可執行檔
圖 2 獨立可執行檔下載用戶端 (與失敗的區塊)

下載用戶端的獨立可執行檔
圖 3 獨立可執行檔 (在之後重試) 下載用戶端

[圖 1 DownloadHandler.dll 和 FileDownloader.exe、 顯示伺服器電腦的硬碟機上的檔案區塊通過 DownloadHandler.dll 及 FileDownloader.exe 到在用戶端電腦的硬碟上檔案的處理流程的設計的高階概觀並說明該程序中的 HTTP 通訊協定標頭。

[圖 1, ,FileDownloader.exe 啟始呼叫使用簡單的 URL,其中包含您想要下載 URL 查詢字串參數 (file=file.txt) 和內部的檔案名稱的伺服器下載檔案使用的 HTTP 方法 (前端),而初始伺服器會送回只有其回應標頭,其中包含檔案大小總計。用戶端接著使用 Parallel.ForEach 建構來逐一查看,分割成多個區塊 (位元範圍) 的參數中的區塊大小為基礎的檔案大小總計 (chunksize = 5242880)。針對每個個別反覆項目 Parallel.ForEach 建構會傳遞相關聯的位元組範圍中的個別執行緒上執行的處理方法。在處理方法中,用戶端發出 HttpWebRequest 呼叫伺服器使用相同的 URL 並在內部將附加包含提供給該處理方法的位元組範圍的 HTTP 要求標頭 (亦即範圍: 位元組 = 0-範圍 5242880: 位元組 = 5242880 10485760 以此類推)。

伺服器上 IHttpAsyncHandler 介面 (System.Web.IHttpAsyncHandler) 的實作會處理個別的執行緒執行 HttpResponse.TransmitFile 方法以寫入網路資料流直接從伺服器電腦的檔案要求的位元組範圍上的每個要求 — 沒有明確的緩衝 — 因此在伺服器上的記憶體影響是幾乎不存在。伺服器送回其回應的 HTTP 狀態碼 206 (PartialContent) 並在內部將附加識別要傳回的位元組範圍的 HTTP 回應標頭 (亦即內容範圍: 0-5242880/26214400,內容範圍的位元組: 位元組 5242880-10485760/26214400 等等)。每個執行緒收到用戶端電腦上的 HTTP 回應,它會寫入至已識別出的 HTTP 回應標頭 (內容範圍) 中的用戶端電腦的硬碟機上檔案的對應部分回應中傳回的位元組。它會使用非同步重疊的檔案 I/O (以確保 Windows I/O 管理員不會序列化 I/O 要求在分派 I/O 要求封包到核心模式驅動程式之前完成檔案寫入作業)。如果所有的多個使用者模式執行緒執行檔案寫入您沒有開啟以供非同步 I/O 重疊的檔案,要求將會序列化和核心模式驅動程式只會收到一個要求一次。如需有關非同步 I/O 重疊的詳細資訊,請參閱 「 取得您的驅動程式來處理多個一個 I/O 要求在時間 」 (bit.ly/1NIaqxP) 和 「 支援非同步 I/O"(bit.ly/1NIaKMW) 硬體開發人員中心網站上。

若要實作種非同步功能可以在我們 IHttpAsyncHandler,我們以手動方式張貼重疊的 I/O 結構 I/O 完成連接埠並 CLR 執行緒集區執行完成委派中完成連接埠執行緒上的重疊結構所提供。這些是大部分的內建的非同步方法所使用的相同完成連接埠執行緒。一般而言,最好是使用新的內建的非同步方法對於最我 I/O 繫結工作,但我們在此情況下使用 HttpResponse.TransmitFile 函數由於它未處理的功能來傳輸超大檔案,而不明確地在伺服器記憶體中緩衝處理它們。這是令人驚艷!

Parallel.ForEach 主要是供受限於 CPU 的工作並不會真正用於由於封鎖伺服器實作。我們卸載工作來完成連接埠執行緒從 CLR 執行緒集區而不是 CLR 執行緒集區的一般背景工作執行緒以防止資源相同的執行緒供 IIS 用來服務連入要求。此外,更有效率的方式可以在其中完成連接埠處理程序運作稍微限制在伺服器上的執行緒消耗。沒有具有更詳細的說明中的 CLR 執行緒集區中完成連接埠執行緒和背景工作執行緒之間的差異會反白顯示的 IOThread 類別頂端的 [註解] 區段的範例專案程式碼中所列的圖表。因為調整為數百萬名使用者不是此公用程式的主要目標,我們可以負擔得起兼顧 HttpResponse.TransmitFile 函式執行以達到省下的相關聯的記憶體在伺服器上傳送大量的檔案時所需的其他伺服器執行緒。基本上,我們正在交易因使用額外的執行緒 (而不是內建的非同步方法的任何執行緒) 伺服器上使用 HttpResponse.TransmitFile 函式會消耗杯最小伺服器記憶體的延展性的遺失。雖然本文的範圍之外,您可以選擇性地內建的非同步方法一起使用未緩衝處理的檔案 I/O 以達成類似的記憶體節約與任何其他的執行緒從什麼我們了解,一切都必須是對齊的磁區但很難正確地實作。除此之外,,它會出現 Microsoft 刻意移除了 NoBuffering 項目從 FileOptions 列舉實際上以免未緩衝處理的檔案 I/O 需要手動甚至讓駭客。我們很難眠的未正確實作它的相關風險而且決定採用 HttpResponse.TransmitFile,經過完整測試的風險較低的選項。

FileDownloader.exe 可以啟動多個執行緒,並如所示,每個發行對應到不同 (位元組範圍) 的一部份所下載之檔案的檔案大小總計為基礎的個別 HttpWebRequest 呼叫分成"區塊指定的位元組", [圖 2

若要下載其 HttpWebRequest 呼叫中指定的檔案 (位元組範圍) 的部分失敗的任何執行緒可以重試藉由只是呼叫 (適用於只有該失敗的位元組範圍) 重複直到最後成功中, 所示相同 HttpWebRequest [圖 3。您將不會遺失部分已經下載,這在慢速連線的情況下意味著幾個小時的下載時間儲存的檔案。您幾乎可以排除故障的連線持續正在離線的負面影響。與設計的多個執行緒以平行方式同時下載檔案的不同部分 — 直接寫入網路資料流沒有明確的緩衝 — 到非同步重疊檔案 I/O 與硬碟機、 最大化下載完成的時間白白連接時實際上有連線期間的數量。此工具將會繼續完成其餘部分的網路連結重新上線,而不會遺失任何工作每次。我們要將它視為多個 「 可重試 」 檔案下載程式沒有 「 繼續 」 檔案的下載程式。

差異可以是假設的範例所示。您即將下載大型檔案會採取長談。當您離開工作並讓它執行時啟動能夠繼續將檔案下載。當您抵達早上時,您會看到檔案下載失敗在百分之 10,並且準備好繼續。但是當它恢復它仍然需要執行夜間工作一次以完成剩餘的 90%。

相較之下,當您離開工作並讓它執行所有的晚上開始我們可以重試的檔案下載。當您抵達早上時,您會看到檔案下載在百分之 10 的一個區塊的失敗但繼續下載的檔案區塊的其餘部分。現在,您只需要再試一次只是該一個區塊和完成時。在發生短暫的波動的網路連結,從該失敗的區塊後還是把它並透過網路連結來源回到線上時晚上 rest 完成其餘的 90%。

網頁瀏覽器內建預設下載用戶端也可用以使用例如 https://localhost/DownloadPortal/Download?file=test.txt&chunksize=5242880 URL 下載用戶端。

請注意,參數 (chunksize = 5242880) 也選擇性時使用網頁瀏覽器做為下載用戶端。如果未包含,伺服器會下載整個檔案中使用相同的 HttpResponse.TransmitFile 一個區塊。如果包含,它將會執行每個區塊的個別 HttpResponse.TransmitFile 呼叫。

[圖 4 使用網頁瀏覽器不支援下載用戶端的部分內容時的 DownloadHandler.dll 設計的高階概觀。這個範例說明處理流程的伺服器電腦的硬碟機上的檔案區塊通過 DownloadHandler.dll 和網頁瀏覽器到 Web 瀏覽器電腦的硬碟機上的檔案。

處理流程 DownloadHandler.dll 高階設計概觀
圖 4 高層級的設計概觀的處理流程 DownloadHandler.dll (使用網頁瀏覽器不支援部分的內容做為用戶端)

很酷的功能的 IIS Web 伺服器上的 IHttpAsyncHandler 介面的實作方法是藉由在其 HTTP 回應中傳送接受範圍 HTTP 標頭"位元組服務 」 的支援 (接受範圍: 位元組為單位),告知用戶端它將提供檔 (部分內容的範圍) 的部分。如果預設的下載用戶端在 Web 瀏覽器支援部分的內容,它可以傳送伺服器範圍的 HTTP 標頭的 HTTP 要求中 (範圍: 位元組 = 5242880 10485760),以及當伺服器傳送至用戶端部分的內容後,會送回其 HTTP 回應內的內容範圍 HTTP 標頭 (內容範圍: 5242880-10485760/26214400 位元組)。因此,根據功能所使用的網頁瀏覽器和預設值下載該瀏覽器內建的用戶端,您可能會收到一些我們獨立可執行的用戶端同樣的權益。無論如何,大部分的網頁瀏覽器可讓您建置您自己自訂的下載用戶端可直接插入到瀏覽器中,取代內建的預設值。

範例專案組態

範例專案的只是複製到 DownloadHandler.dll 和 IOThreads.dll 虛擬目錄下的 \bin 目錄並將項目放在 web.config 的區段處理常式和模組就像這樣:

<handlers>
  <add name="Download" verb="*" path="Download"
    type="DownloaderHandlers.DownloadHandler" />
</handlers>
<modules>
  <add name="CustomBasicAuthenticationModule" preCondition="managedHandler"
    type="DownloaderHandlers.CustomBasicAuthenticationModule" />
</modules>

如果 IIS 伺服器上沒有任何虛擬目錄,建立一個連結到 \bin 目錄、 讓應用程式並確定它使用 Microsoft.NET Framework 4 應用程式集區。

自訂基本驗證模組會使用相同、 簡單易用、 AspNetSqlMembershipProvider 現今許多 ASP.NET 網站上使用儲存的使用者名稱和密碼才能下載 aspnetdb 資料庫內部的 SQL Server 上的檔案。AspNetSqlMembershipProvider 的好用的優點之一是使用者也不需要有 Windows 網域帳戶。若要設定使用者帳戶和 SSL 憑證會列在 CustomBasicAuthenticationModule 類別頂端的 [註解] 區段中的範例專案程式碼在 IIS 伺服器上所需有關如何安裝 AspNetSqlMembershipProvider 和設定的詳細的指示。用於調整 IIS 伺服器的其他進階的組態選項通常已經設定由 IT 部門管理伺服器和已超出範圍的這篇文章,但如果不是如此,它們都可以使用在 TechNet Library bit.ly/1JRJjNS

您所完成。就這麼簡單。

令人讚嘆的因素

設計吸引人的最重要因素較為快速,但是會更有彈性並不能容許持續將線上白白、 不穩定的網路連結所造成的網路中斷和離線會發生錯誤。一般而言,下載一個檔案,一個區塊中的上一個連線將會產生的最大輸送量。

這項規則,例如鏡像的伺服器環境中不同的部分,取得從不同的鏡像伺服器的每個檔案片段中所示下載檔案時有些唯一例外 [圖 5。但是一般而言,下載多個執行緒上的檔案實際上低於下載一個執行緒上的檔案因為網路通常是瓶頸。不過,能夠以重複直到成功為止,而不用重新啟動整個下載程序中,重試完全失敗的部分的檔案下載 molds 我們想要將視為一種 odd 容錯功能。

假設未來的增強功能以模擬非常基本的鏡像基礎結構
圖 5 假設未來的增強功能以模擬非常基本的鏡像基礎結構

此外,如果有人修改為未來的增強功能以模擬非常基本的鏡像伺服器基礎結構的設計中所示 [圖 5, ,它可能圖形什麼可以視為一種 odd 備援性。

基本上,設計可讓您確實不可靠網路下載的檔案。在您的網路連結的簡短波動並不表示您不必從頭開始。相反地,您可以只是重試失敗的檔案片段。最好還設計 (也就得更有彈性) 是儲存至硬碟機上的檔案下載的目前進度狀態下載作業正進行,因此您無法跨平台用戶端應用程式和用戶端電腦重新啟動基本上是重試失敗的下載。但這會是一項工作將留給讀者。

另一個吸引人的因素,請媲美上述增長,在於要直接將檔案的位元組寫入網路資料流的伺服器上的 HttpResponse.TransmitFile 使用 — 沒有明確的緩衝 — 以 minimalize 對伺服器記憶體的衝擊。它是令人驚訝如何微不足道的影響是在伺服器記憶體即使下載極大的檔案。

有三個其他的因素會更顯著,但吸引人仍。

首先,因為設計包含用戶端前端和後端伺服器,您有伺服器端設定的完整控制權。這可讓您自由及調整可以通常會大幅降低下載程序並不在您控制的其他人所擁有的伺服器上的檔案的組態設定的電源。例如,您可以調整每一個用戶端 IP 位址的值大於兩個連線的一般限制加諸的限制連接限制。您也可以調整每個用戶端連接至更高的價值的節流限制。

第二,在我們的前端用戶端 (FileDownloader.exe) 和我們的後端伺服器 (DownloadHandler.dll) 內的範例專案程式碼可做為簡單、 清除程式碼範例示範如何使用 HTTP 要求和回應標頭的 HTTP 通訊協定中的部分內容的位元組範圍所需的區塊。很容易以查看哪些 HTTP 要求標頭的用戶端必須傳送以要求的位元組範圍和伺服器必須傳送哪些 HTTP 回應標頭以傳回的位元組範圍為部分的內容。它應該相當容易修改的程式碼來實作較高層級的功能在這個簡單的基本功能,或實作一些更進階的功能提供更複雜的軟體套件。此外,可以使用它做為簡單的起始範本輕鬆加入支援一些其他更多進階 HTTP 標頭,例如內容類型: multipart/位元組範圍 content-md5: md5 digest,If-match: 實體標記等等。

第三,因為設計使用 IIS Web 伺服器,您會自動受益於某些伺服器所提供的內建功能。例如,通訊可以自動加密 (使用 HTTPS 與 SSL 憑證) 和壓縮 (使用 gzip 壓縮)。不過,它可能不是建議如果這樣做會導致您的伺服器 Cpu 太多負荷極大的檔案執行 gzip 壓縮。但是萬一您的伺服器 Cpu 可以分攤的額外負載,傳送較小的壓縮的資料的效率有時會使整個系統的整體輸送量中最大的不同。

未來的增強功能

範例專案程式碼只會提供檔案下載程式運作所需的最小的核心功能。我們的目標是要保留設計簡單且容易理解因此無法做為依據的新增增強功能和其他功能的基底相當毫不費力地使用。它只是起始點和基底的範本。它甚至可以開始用於實際執行環境之前是許多其他的增強功能是必要的。加入更高階的抽象層可提供此其他、 更進階的功能則留待練習讀取器。不過,我們將構思更重要的增強功能的數項。

範例專案程式碼目前並不包含檔案 md5 雜湊。在真實世界中,請務必採用某種檔案總和檢查碼策略以確保檔案下載至用戶端符合伺服器上的檔案且尚未被竄改或以任何方式改變。HTTP 標頭讓使用者輕易與標頭 (內容 MD5: md5 digest)。事實上,我們的第一個原型的其中一個包含在檔案上要求檔案每次執行 MD5 雜湊的總和檢查碼,以及將摘要放入標頭 (內容 MD5: md5 digest) 檔案留伺服器之前。用戶端然後會在它接收到的檔案會確認產生的摘要符合標頭中的摘要上執行相同的 MD5 雜湊總和檢查碼 (content-md5: md5 digest) 伺服器所傳回。如果沒有符合有竄改或損毀的檔案。雖然這會確保檔案的目標不變更、 大型的檔案伺服器上造成激烈的 CPU 壓力和花太多時間來執行。

事實上,它可能需要某種形式的快取層級的未處理的檔案 (在背景中) 上一次的檔案存留期的 MD5 雜湊總和檢查碼並將產生的摘要儲存在檔案名稱做為索引鍵的字典。簡單的字典查閱則只需要在伺服器上取得檔案的摘要和摘要可以加入至標頭檔案伺服器、 flash、 離開伺服器 Cpu 的影響降到最低。

範例專案程式碼也不會目前限制用戶端從使用巨大的執行緒數目和將檔案分割成區塊中極大的數目。它基本上是允許"do 它需要 「 用戶端以確保它可以下載檔案。在真實世界中那里可能必須是某種讓一部用戶端將無法劫持伺服器並變空所有其他的用戶端可能會在用戶端限制的基礎結構。

[圖 5 說明藉由修改設計以做為 URL 查詢字串參數,而不是目前的設計"chunksize"參數提供的 「 節點名稱/位元組範圍 」 配對清單模擬非常基本的鏡像基礎結構的假設未來項增強功能。無法從不同的伺服器取得檔案的每個區塊只逐一"節點名稱/位元組範圍 」 配對,啟動每一對,而不是在內部重複分割成區塊 (chunk)"chunksize"參數為基礎的檔案大小總計和啟動每個區塊 HttpWebRequest HttpWebRequest 輕鬆修改目前的設計。

您可以建構 URL HttpWebRequest 的只將伺服器名稱取代"節點名稱/位元組範圍 」 配對清單的相關聯的節點名稱相關聯的位元組範圍加入範圍 HTTP 標頭 (亦即範圍: 位元組 = 0 5242880),然後完全從 URL 移除"節點名稱/位元組範圍 」 清單。某種形式的中繼資料檔無法識別哪一部伺服器上的檔案片段的位置、 並提出要求的機器無法再組合一個檔案從檔案的分散到不同的伺服器的項目。

如果檔案鏡像 10 部伺服器上,無法修改設計以獲得伺服器 1 的鏡像複本片段 2 的伺服器 2 的鏡像複本片段 3 從伺服器 3 鏡像複製檔案的來源檔的檔案片段 1 等等。同樣地,十分重要之後擷取檔案的所有項目和重組完整的檔案在用戶端,以確定沒有區塊已損毀在任何鏡像伺服器上和您實際上並未收到整個檔案進行檔案 md5 雜湊。您可能甚至更花俏一點並將它上一層樓讓地理位置分散的國家/地區的伺服器會判斷哪些伺服器是在最小的處理負載下的程式碼建立一些複雜的智慧然後使用這些伺服器傳回的檔案區塊的要求提供服務。

總結

若要建立更快、 更具彈性的檔案下載程式但若要建立一個相當的彈性,發生短暫的網路中斷不是設計的我們目標。

我們花了很棒的投入時間以確定設計非常簡單且清楚地示範了如何使用 HTTP 通訊協定標頭"位元組伺服"位元組範圍和部分內容。

在我們的研究,我們實際上會發現很難找到如何執行簡單的 HTTP 位元組服務以及如何適當使用 HTTP 通訊協定中的位元組範圍標頭的良好、 清除範例。大部分的範例是不必要地複雜或使用過多的其他標頭來實作更進階的功能在 HTTP 通訊協定,因此很難了解,更別提嘗試增強或擴展上從現在開始。

我們想要提供您簡單的實心基底包含只需要至少就會相當容易體驗並以累加方式經過一段時間中加入更多進階的功能 — 或甚至是移到目前為止,實作新增一些更進階的功能的 HTTP 通訊協定的整個較高層級抽象層。

我們只被想要提供直接的範例來了解從和往上建置。敬祝您使用愉快!


Doug Duerner是 15 年以上設計和實作與 Microsoft 技術的大型系統的資深軟體工程師。他曾針對數個 Fortune 500 大銀行機構和商業軟體公司的設計和建置大規模的分散式的網路管理系統由美國國防部的防禦資訊系統機構 (停用) 用於其 「 通用資訊方格 」 和部門的狀態。他是萬能的本質著重於所有層面但盡情享受最複雜且富於挑戰的技術障礙,尤其是每個人都說 「 無法完成。 」 可以到達 Duerner coding.innovation@gmail.com

Yeon 變更 Wang是 15 年以上設計和實作與 Microsoft 技術的大型系統的資深軟體工程師。他也曾為金融機構 Fortune 500 和商業軟體公司的設計和建置大規模的分散式的網路管理系統由美國國防部的防禦資訊系統機構 (停用) 用於其 「 通用資訊方格 」 和部門的狀態。他也設計和實作大規模的驅動程式認證系統的其中一個全球最大的晶片製造商。Wang 電腦科學中有主要的程度。他吃晚餐不上用場複雜的問題和 mailto yeon_wang@yahoo.com

感謝以下的微軟技術專家對本文的審閱: Stephen cleary 在探討與 James McCaffrey