C#实现http断点续传上传和下载的实现示例
断点续传是一种可以在文件传输过程中出现断电、网络故障等情况时,能够保证传输内容不会全部丢失,而是可以从已传输的位置继续传输的机制。在文件传输较大、较复杂的情况下,使用断点续传可以提高传输质量、稳定性和效率。
在C#中,可以使用HTTP协议的Range头部域来实现断点续传。使用HTTP Range头部域,可以控制取哪个字节范围内的字节。具体实现方法,在HTTP请求头中填写Range头部信息,指明下载区间:
start和end的值为0和文件大小减1,表示下载全部数据;若要实现断点续传,则start的值为当前已下载的数据大小,end的值不变。
在本篇文章中,我们将详细介绍如何使用C#实现HTTP协议的断点续传功能,并提供了完整的代码示例。
实现步骤
C#实现断点续传功能的步骤,简要描述如下:
1.定义HTTP请求,并填写Range头部信息,指明下载区间信息。
2.执行HTTP请求,接收服务端返回的字节流,并将流写入本地文件。
3.检查最终下载文件的大小,与服务端的文件大小是否一致,若不一致则下载失败。
4.上传文件时,同样需制定Range信息,然后发送PUT请求进行上传。
代码实现
我们将使用HttpClient来执行请求,使用FileStream来读写文件。下面是代码实现的详细过程。
1.下载文件
下载文件时,首先需要判断本地是否已经存在相同的文件,如果存在,则需要计算出当前已下载数据的大小(即起始位置startPosition),否则从头开始下载。
下载时,需要在HTTP请求头中填写Range头部信息,指明下载区间。同时,需要注意控制下载缓冲区大小,以避免内存不足的情况。
最后,需要检查下载完成后文件的大小是否与服务端的文件大小一致,若不一致,则下载失败。
代码示例:
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请求头中填写Range头部信息,以限制上传的范围。同时,需要指定Content-Type,以明确上传数据的类型。
上传文件需要注意的一点是,如果文件较大,则需要分多次上传。可以将文件分割成多个大小相同的片段,逐个上传,确保操作的稳定性和效率。
上传完成后,会收到服务端的响应。如果响应码为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.}
完整代码
上述代码仅为示例,仍然需要加入部分边界检查、异常处理等逻辑,以保证代码的健壮性。下面是完整的实现代码,包含了断点续传功能的完整实现。
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. }
总结
断点续传功能可以在文件传输的过程中,提高传输质量和效率,确保数据传输的安全性和稳定性。在本文中,我们介绍了C#中实现HTTP协议断点续传的方法,并提供了完整的代码示例。希望读者通过本文的介绍,能够成功实现断点续传功能,并在实际工作中应用到相应的场景中去。