C#實(shí)現(xiàn)http斷點(diǎn)續(xù)傳上傳和下載的實(shí)現(xiàn)示例
當(dāng)前位置:點(diǎn)晴教程→知識(shí)管理交流
→『 技術(shù)文檔交流 』
斷點(diǎn)續(xù)傳是一種可以在文件傳輸過(guò)程中出現(xiàn)斷電、網(wǎng)絡(luò)故障等情況時(shí),能夠保證傳輸內(nèi)容不會(huì)全部丟失,而是可以從已傳輸?shù)奈恢美^續(xù)傳輸?shù)臋C(jī)制。在文件傳輸較大、較復(fù)雜的情況下,使用斷點(diǎn)續(xù)傳可以提高傳輸質(zhì)量、穩(wěn)定性和效率。 在C#中,可以使用HTTP協(xié)議的Range頭部域來(lái)實(shí)現(xiàn)斷點(diǎn)續(xù)傳。使用HTTP Range頭部域,可以控制取哪個(gè)字節(jié)范圍內(nèi)的字節(jié)。具體實(shí)現(xiàn)方法,在HTTP請(qǐng)求頭中填寫(xiě)Range頭部信息,指明下載區(qū)間: Range: bytes=[start]-[end] start和end的值為0和文件大小減1,表示下載全部數(shù)據(jù);若要實(shí)現(xiàn)斷點(diǎn)續(xù)傳,則start的值為當(dāng)前已下載的數(shù)據(jù)大小,end的值不變。 在本篇文章中,我們將詳細(xì)介紹如何使用C#實(shí)現(xiàn)HTTP協(xié)議的斷點(diǎn)續(xù)傳功能,并提供了完整的代碼示例。 實(shí)現(xiàn)步驟 C#實(shí)現(xiàn)斷點(diǎn)續(xù)傳功能的步驟,簡(jiǎn)要描述如下: 1.定義HTTP請(qǐng)求,并填寫(xiě)Range頭部信息,指明下載區(qū)間信息。 2.執(zhí)行HTTP請(qǐng)求,接收服務(wù)端返回的字節(jié)流,并將流寫(xiě)入本地文件。 3.檢查最終下載文件的大小,與服務(wù)端的文件大小是否一致,若不一致則下載失敗。 4.上傳文件時(shí),同樣需制定Range信息,然后發(fā)送PUT請(qǐng)求進(jìn)行上傳。 代碼實(shí)現(xiàn) 我們將使用HttpClient來(lái)執(zhí)行請(qǐng)求,使用FileStream來(lái)讀寫(xiě)文件。下面是代碼實(shí)現(xiàn)的詳細(xì)過(guò)程。 1.下載文件 下載文件時(shí),首先需要判斷本地是否已經(jīng)存在相同的文件,如果存在,則需要計(jì)算出當(dāng)前已下載數(shù)據(jù)的大?。雌鹗嘉恢胹tartPosition),否則從頭開(kāi)始下載。 下載時(shí),需要在HTTP請(qǐng)求頭中填寫(xiě)Range頭部信息,指明下載區(qū)間。同時(shí),需要注意控制下載緩沖區(qū)大小,以避免內(nèi)存不足的情況。 最后,需要檢查下載完成后文件的大小是否與服務(wù)端的文件大小一致,若不一致,則下載失敗。 代碼示例: 1. private static async Task DownloadFileAsync(Uri uri, string filename, CancellationToken cancellationToken = default) 2. { 3. long startPosition; 4. var fileInfo = new FileInfo(filename); 5. 6. if (fileInfo.Exists) 7. { 8. startPosition = fileInfo.Length; 9. if (startPosition == uri.GetFileSize()) 10. { 11. Console.WriteLine($"The file '{filename}' has already been downloaded."); 12. return; 13. } 14. } 15. else 16. { 17. startPosition = 0; 18. } 19. 20. using var fs = new FileStream(filename, startPosition == 0 ? FileMode.Create : FileMode.Append); 21. 22. var rangeHeader = new RangeHeaderValue(startPosition, null); 23. 24. var request = new HttpRequestMessage(HttpMethod.Get, uri); 25. request.Headers.Range = rangeHeader; 26. 27. using var response = await HttpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken); 28. var contentLength = response.Content.Headers.ContentLength; 29. if (!contentLength.HasValue) 30. { 31. throw new InvalidOperationException("The server did not provide the content length."); 32. } 33. 34. var totalSize = contentLength.Value + startPosition; 35. 36. using var stream = await response.Content.ReadAsStreamAsync(cancellationToken); 37. await stream.CopyToAsync(fs); 38. 39. if (fs.Length != totalSize) 40. { 41. fileInfo.Refresh(); 42. if (fileInfo.Length < totalSize) 43. { 44. throw new InvalidOperationException($"The file '{filename}' was not downloaded correctly."); 45. } 46. } 47. 48. Console.WriteLine($"The file '{filename}' has been downloaded."); 49.} 2.上傳文件 上傳文件與下載文件相似,同樣需要在HTTP請(qǐng)求頭中填寫(xiě)Range頭部信息,以限制上傳的范圍。同時(shí),需要指定Content-Type,以明確上傳數(shù)據(jù)的類(lèi)型。 上傳文件需要注意的一點(diǎn)是,如果文件較大,則需要分多次上傳??梢詫⑽募指畛啥鄠€(gè)大小相同的片段,逐個(gè)上傳,確保操作的穩(wěn)定性和效率。 上傳完成后,會(huì)收到服務(wù)端的響應(yīng)。如果響應(yīng)碼為2xx,則表示上傳成功;否則,表示上傳失敗。 代碼示例: 1. public static async Task UploadFileAsync(Uri uri, string filename, int bufferSize = 4096, CancellationToken cancellationToken = default) 2. { 3. long startPosition; 4. var fileInfo = new FileInfo(filename); 5. 6. if (fileInfo.Exists) 7. { 8. startPosition = fileInfo.Length; 9. } 10. else 11. { 12. throw new FileNotFoundException("The file was not found.", filename); 13. } 14. 15. using var fs = new FileStream(filename, FileMode.Open); 16. 17. var rangeHeader = new RangeHeaderValue(startPosition, fs.Length - 1); 18. 19. var request = new HttpRequestMessage(HttpMethod.Put, uri); 20. request.Headers.Range = rangeHeader; 21. request.Headers.TryAddWithoutValidation("Content-Type", "application/octet-stream"); 22. 23. var content = new StreamContent(fs, bufferSize); 24. request.Content = content; 25. 26. using var response = await HttpClient.SendAsync(request, cancellationToken); 27. 28. if (!response.IsSuccessStatusCode) 29. { 30. var responseMessage = await response.Content.ReadAsStringAsync(); 31. throw new InvalidOperationException($"Failed to upload file: {response.StatusCode} {responseMessage}"); 32. } 33. 34. Console.WriteLine($"The file '{filename}' has been uploaded."); 35.} 完整代碼 上述代碼僅為示例,仍然需要加入部分邊界檢查、異常處理等邏輯,以保證代碼的健壯性。下面是完整的實(shí)現(xiàn)代碼,包含了斷點(diǎn)續(xù)傳功能的完整實(shí)現(xiàn)。 1. using System; 2. using System.IO; 3. using System.Net.Http; 4. using System.Net.Http.Headers; 5. using System.Threading; 6. using System.Threading.Tasks; 7. 8. namespace ConsoleApp 9. { 10. internal static class Program 11. { 12. private static readonly HttpClient HttpClient = new HttpClient(); 13. 14. private static async Task Main(string[] args) 15. { 16. var uri = new Uri("https://download.visualstudio.microsoft.com/download/pr/26246709-5c10-4383-ad1a-f22f3e8e5e15/23e2d41d2e57b81fc0f9c72068994e70/vc_redist.x64.exe"); 17. 18. var filename = Path.Combine(Path.GetTempPath(), "vc_redist.x64.exe"); 19. 20. Console.WriteLine("Start downloading the file..."); 21. 22. try 23. { 24. await DownloadFileAsync(uri, filename, CancellationToken.None); 25. } 26. catch (Exception ex) 27. { 28. Console.WriteLine($"Failed to download the file: {ex.Message}"); 29. return; 30. } 31. 32. Console.WriteLine("\nStart uploading the file...\n"); 33. 34. try 35. { 36. await UploadFileAsync(uri, filename, 4096, CancellationToken.None); 37. } 38. catch (Exception ex) 39. { 40. Console.WriteLine($"Failed to upload the file: {ex.Message}"); 41. return; 42. } 43. 44. Console.WriteLine("Done."); 45. } 46. 47. private static async Task DownloadFileAsync(Uri uri, string filename, CancellationToken cancellationToken = default) 48. { 49. long startPosition; 50. var fileInfo = new FileInfo(filename); 51. 52. if (fileInfo.Exists) 53. { 54. startPosition = fileInfo.Length; 55. if (startPosition == uri.GetFileSize()) 56. { 57. Console.WriteLine($"The file '{filename}' has already been downloaded."); 58. return; 59. } 60. } 61. else 62. { 63. startPosition = 0; 64. } 65. 66. using var fs = new FileStream(filename, startPosition == 0 ? FileMode.Create : FileMode.Append); 67. 68. var rangeHeader = new RangeHeaderValue(startPosition, null); 69. 70. var request = new HttpRequestMessage(HttpMethod.Get, uri); 71. request.Headers.Range = rangeHeader; 72. 73. using var response = await HttpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken); 74. var contentLength = response.Content.Headers.ContentLength; 75. if (!contentLength.HasValue) 76. { 77. throw new InvalidOperationException("The server did not provide the content length."); 78. } 79. 80. var totalSize = contentLength.Value + startPosition; 81. 82. using var stream = await response.Content.ReadAsStreamAsync(cancellationToken); 83. await stream.CopyToAsync(fs); 84. 85. if (fs.Length != totalSize) 86. { 87. fileInfo.Refresh(); 88. if (fileInfo.Length < totalSize) 89. { 90. throw new InvalidOperationException($"The file '{filename}' was not downloaded correctly."); 91. } 92. } 93. 94. Console.WriteLine($"The file '{filename}' has been downloaded."); 95. } 96. 97. public static async Task UploadFileAsync(Uri uri, string filename, int bufferSize = 4096, CancellationToken cancellationToken = default) 98. { 99. long startPosition; 100. var fileInfo = new FileInfo(filename); 101. 102. if (fileInfo.Exists) 103. { 104. startPosition = fileInfo.Length; 105. } 106. else 107. { 108. throw new FileNotFoundException("The file was not found.", filename); 109. } 110. 111. using var fs = new FileStream(filename, FileMode.Open); 112. 113. var rangeHeader = new RangeHeaderValue(startPosition, fs.Length - 1); 114. 115. var request = new HttpRequestMessage(HttpMethod.Put, uri); 116. request.Headers.Range = rangeHeader; 117. request.Headers.TryAddWithoutValidation("Content-Type", "application/octet-stream"); 118. 119. var content = new StreamContent(fs, bufferSize); 120. request.Content = content; 121. 122. using var response = await HttpClient.SendAsync(request, cancellationToken); 123. 124. if (!response.IsSuccessStatusCode) 125. { 126. var responseMessage = await response.Content.ReadAsStringAsync(); 127. throw new InvalidOperationException($"Failed to upload file: {response.StatusCode} {responseMessage}"); 128. } 129. 130. Console.WriteLine($"The file '{filename}' has been uploaded."); 131. } 132. } 133. 134. public static class UriExtensions 135. { 136. public static long GetFileSize(this Uri uri) 137. { 138. using var client = new HttpClient(); 139. using var response = client.Send(new HttpRequestMessage(HttpMethod.Head, uri)); 140. var contentLength = response.Content.Headers.ContentLength; 141. if (!contentLength.HasValue) 142. { 143. throw new InvalidOperationException("The server did not provide the content length."); 144. } 145. 146. return contentLength.Value; 147. } 148. } 149. } 總結(jié) 斷點(diǎn)續(xù)傳功能可以在文件傳輸?shù)倪^(guò)程中,提高傳輸質(zhì)量和效率,確保數(shù)據(jù)傳輸?shù)陌踩院头€(wěn)定性。在本文中,我們介紹了C#中實(shí)現(xiàn)HTTP協(xié)議斷點(diǎn)續(xù)傳的方法,并提供了完整的代碼示例。希望讀者通過(guò)本文的介紹,能夠成功實(shí)現(xiàn)斷點(diǎn)續(xù)傳功能,并在實(shí)際工作中應(yīng)用到相應(yīng)的場(chǎng)景中去。 該文章在 2024/3/12 23:35:13 編輯過(guò) |
關(guān)鍵字查詢
相關(guān)文章
正在查詢... |