C#断点续传的实现示例

简介: C#断点续传的实现示例

断点续传是一种可以在文件传输过程中出现断电、网络故障等情况时,能够保证传输内容不会全部丢失,而是可以从已传输的位置继续传输的机制。在文件传输较大、较复杂的情况下,使用断点续传可以提高传输质量、稳定性和效率。

在C#中,可以使用HTTP协议的Range头部域来实现断点续传。使用HTTP Range头部域,可以控制取哪个字节范围内的字节。具体实现方法,在HTTP请求头中填写Range头部信息,指明下载区间:

Range: bytes=[start]-[end]

start和end的值为0和文件大小减1,表示下载全部数据;若要实现断点续传,则start的值为当前已下载的数据大小,end的值不变。

在本篇文章中,我们将详细介绍如何使用C#实现HTTP协议的断点续传功能,并提供了完整的代码示例。

实现步骤

C#实现断点续传功能的步骤,简要描述如下:

1.定义HTTP请求,并填写Range头部信息,指明下载区间信息。

2.执行HTTP请求,接收服务端返回的字节流,并将流写入本地文件。

3.检查最终下载文件的大小,与服务端的文件大小是否一致,若不一致则下载失败。

4.上传文件时,同样需制定Range信息,然后发送PUT请求进行上传。

代码实现

我们将使用HttpClient来执行请求,使用FileStream来读写文件。下面是代码实现的详细过程。

1.下载文件

下载文件时,首先需要判断本地是否已经存在相同的文件,如果存在,则需要计算出当前已下载数据的大小(即起始位置startPosition),否则从头开始下载。

下载时,需要在HTTP请求头中填写Range头部信息,指明下载区间。同时,需要注意控制下载缓冲区大小,以避免内存不足的情况。

最后,需要检查下载完成后文件的大小是否与服务端的文件大小一致,若不一致,则下载失败。

代码示例:

private static async Task DownloadFileAsync(Uri uri, string filename, CancellationToken cancellationToken = default)
{
    long startPosition;
    var fileInfo = new FileInfo(filename);
 
    if (fileInfo.Exists)
    {
        startPosition = fileInfo.Length;
        if (startPosition == uri.GetFileSize())
        {
            Console.WriteLine($"The file '{filename}' has already been downloaded.");
            return;
        }
    }
    else
    {
        startPosition = 0;
    }
 
    using var fs = new FileStream(filename, startPosition == 0 ? FileMode.Create : FileMode.Append);
 
    var rangeHeader = new RangeHeaderValue(startPosition, null);
 
    var request = new HttpRequestMessage(HttpMethod.Get, uri);
    request.Headers.Range = rangeHeader;
 
    using var response = await HttpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
    var contentLength = response.Content.Headers.ContentLength;
    if (!contentLength.HasValue)
    {
        throw new InvalidOperationException("The server did not provide the content length.");
    }
 
    var totalSize = contentLength.Value + startPosition;
 
    using var stream = await response.Content.ReadAsStreamAsync(cancellationToken);
    await stream.CopyToAsync(fs);
 
    if (fs.Length != totalSize)
    {
        fileInfo.Refresh();
        if (fileInfo.Length < totalSize)
        {
            throw new InvalidOperationException($"The file '{filename}' was not downloaded correctly.");
        }
    }
 
    Console.WriteLine($"The file '{filename}' has been downloaded.");
}

2.上传文件

上传文件与下载文件相似,同样需要在HTTP请求头中填写Range头部信息,以限制上传的范围。同时,需要指定Content-Type,以明确上传数据的类型。

上传文件需要注意的一点是,如果文件较大,则需要分多次上传。可以将文件分割成多个大小相同的片段,逐个上传,确保操作的稳定性和效率。

上传完成后,会收到服务端的响应。如果响应码为2xx,则表示上传成功;否则,表示上传失败。

代码示例:

public static async Task UploadFileAsync(Uri uri, string filename, int bufferSize = 4096, CancellationToken cancellationToken = default)
{
    long startPosition;
    var fileInfo = new FileInfo(filename);
 
    if (fileInfo.Exists)
    {
        startPosition = fileInfo.Length;
    }
    else
    {
        throw new FileNotFoundException("The file was not found.", filename);
    }
 
    using var fs = new FileStream(filename, FileMode.Open);
 
    var rangeHeader = new RangeHeaderValue(startPosition, fs.Length - 1);
 
    var request = new HttpRequestMessage(HttpMethod.Put, uri);
    request.Headers.Range = rangeHeader;
    request.Headers.TryAddWithoutValidation("Content-Type", "application/octet-stream");
 
    var content = new StreamContent(fs, bufferSize);
    request.Content = content;
 
    using var response = await HttpClient.SendAsync(request, cancellationToken);
 
    if (!response.IsSuccessStatusCode)
    {
        var responseMessage = await response.Content.ReadAsStringAsync();
        throw new InvalidOperationException($"Failed to upload file: {response.StatusCode} {responseMessage}");
    }
 
    Console.WriteLine($"The file '{filename}' has been uploaded.");
}

完整代码

上述代码仅为示例,仍然需要加入部分边界检查、异常处理等逻辑,以保证代码的健壮性。下面是完整的实现代码,包含了断点续传功能的完整实现。

using System;
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
 
namespace ConsoleApp
{
    internal static class Program
    {
        private static readonly HttpClient HttpClient = new HttpClient();
 
        private static async Task Main(string[] args)
        {
            var uri = new Uri("https://download.visualstudio.microsoft.com/download/pr/26246709-5c10-4383-ad1a-f22f3e8e5e15/23e2d41d2e57b81fc0f9c72068994e70/vc_redist.x64.exe");
 
            var filename = Path.Combine(Path.GetTempPath(), "vc_redist.x64.exe");
 
            Console.WriteLine("Start downloading the file...");
 
            try
            {
                await DownloadFileAsync(uri, filename, CancellationToken.None);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Failed to download the file: {ex.Message}");
                return;
            }
 
            Console.WriteLine("\nStart uploading the file...\n");
 
            try
            {
                await UploadFileAsync(uri, filename, 4096, CancellationToken.None);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Failed to upload the file: {ex.Message}");
                return;
            }
 
            Console.WriteLine("Done.");
        }
 
        private static async Task DownloadFileAsync(Uri uri, string filename, CancellationToken cancellationToken = default)
        {
            long startPosition;
            var fileInfo = new FileInfo(filename);
 
            if (fileInfo.Exists)
            {
                startPosition = fileInfo.Length;
                if (startPosition == uri.GetFileSize())
                {
                    Console.WriteLine($"The file '{filename}' has already been downloaded.");
                    return;
                }
            }
            else
            {
                startPosition = 0;
            }
 
            using var fs = new FileStream(filename, startPosition == 0 ? FileMode.Create : FileMode.Append);
 
            var rangeHeader = new RangeHeaderValue(startPosition, null);
 
            var request = new HttpRequestMessage(HttpMethod.Get, uri);
            request.Headers.Range = rangeHeader;
 
            using var response = await HttpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
            var contentLength = response.Content.Headers.ContentLength;
            if (!contentLength.HasValue)
            {
                throw new InvalidOperationException("The server did not provide the content length.");
            }
 
            var totalSize = contentLength.Value + startPosition;
 
            using var stream = await response.Content.ReadAsStreamAsync(cancellationToken);
            await stream.CopyToAsync(fs);
 
            if (fs.Length != totalSize)
            {
                fileInfo.Refresh();
                if (fileInfo.Length < totalSize)
                {
                    throw new InvalidOperationException($"The file '{filename}' was not downloaded correctly.");
                }
            }
 
            Console.WriteLine($"The file '{filename}' has been downloaded.");
        }
 
        public static async Task UploadFileAsync(Uri uri, string filename, int bufferSize = 4096, CancellationToken cancellationToken = default)
        {
            long startPosition;
            var fileInfo = new FileInfo(filename);
 
            if (fileInfo.Exists)
            {
                startPosition = fileInfo.Length;
            }
            else
            {
                throw new FileNotFoundException("The file was not found.", filename);
            }
 
            using var fs = new FileStream(filename, FileMode.Open);
 
            var rangeHeader = new RangeHeaderValue(startPosition, fs.Length - 1);
 
            var request = new HttpRequestMessage(HttpMethod.Put, uri);
            request.Headers.Range = rangeHeader;
            request.Headers.TryAddWithoutValidation("Content-Type", "application/octet-stream");
 
            var content = new StreamContent(fs, bufferSize);
            request.Content = content;
 
            using var response = await HttpClient.SendAsync(request, cancellationToken);
 
            if (!response.IsSuccessStatusCode)
            {
                var responseMessage = await response.Content.ReadAsStringAsync();
                throw new InvalidOperationException($"Failed to upload file: {response.StatusCode} {responseMessage}");
            }
 
            Console.WriteLine($"The file '{filename}' has been uploaded.");
        }
    }
 
    public static class UriExtensions
    {
        public static long GetFileSize(this Uri uri)
        {
            using var client = new HttpClient();
            using var response = client.Send(new HttpRequestMessage(HttpMethod.Head, uri));
            var contentLength = response.Content.Headers.ContentLength;
            if (!contentLength.HasValue)
            {
                throw new InvalidOperationException("The server did not provide the content length.");
            }
 
            return contentLength.Value;
        }
    }
}

总结

断点续传功能可以在文件传输的过程中,提高传输质量和效率,确保数据传输的安全性和稳定性。在本文中,我们介绍了C#中实现HTTP协议断点续传的方法,并提供了完整的代码示例。希望读者通过本文的介绍,能够成功实现断点续传功能,并在实际工作中应用到相应的场景中去。


相关文章
|
3月前
|
C#
如何使用c# 实现断点续传功能
如何使用c# 实现断点续传功能
37 0
|
3月前
|
网络协议 C#
C#:简化版的实现断点续传功能
C#:简化版的实现断点续传功能
46 0
|
3月前
|
XML 存储 缓存
C#使用XML文件的详解及示例
C#使用XML文件的详解及示例
163 0
|
3月前
|
API C#
异步轮询 Web API 的实现与 C# 示例
异步轮询 Web API 的实现与 C# 示例
107 0
|
5月前
|
数据安全/隐私保护 C# UED
利用 Xamarin 开展企业级移动应用开发:从用户登录到客户管理,全面演示C#与Xamarin.Forms构建跨平台CRM应用的实战技巧与代码示例
【8月更文挑战第31天】利用 Xamarin 进行企业级移动应用开发能显著提升效率并确保高质量和高性能。Xamarin 的跨平台特性使得开发者可以通过单一的 C# 代码库构建 iOS、Android 和 Windows 应用,帮助企业快速推出产品并保持一致的用户体验。本文通过一个简单的 CRM 示例应用演示 Xamarin 的使用方法,并提供了具体的代码示例。该应用包括用户登录、客户列表显示和添加新客户等功能。此外,还介绍了如何增强应用的安全性、数据持久化、性能优化及可扩展性,从而构建出功能全面且体验良好的移动应用。
66 0
|
5月前
|
前端开发 开发者 Apache
揭秘Apache Wicket项目结构:如何打造Web应用的钢铁长城,告别混乱代码!
【8月更文挑战第31天】Apache Wicket凭借其组件化设计深受Java Web开发者青睐。本文详细解析了Wicket项目结构,帮助你构建可维护的大型Web应用。通过示例展示了如何使用Maven管理依赖,并组织页面、组件及业务逻辑,确保代码清晰易懂。Wicket提供的页面继承、组件重用等功能进一步增强了项目的可维护性和扩展性。掌握这些技巧,能够显著提升开发效率,构建更稳定的Web应用。
131 0
|
5月前
|
前端开发 程序员 API
从后端到前端的无缝切换:一名C#程序员如何借助Blazor技术实现全栈开发的梦想——深入解析Blazor框架下的Web应用构建之旅,附带实战代码示例与项目配置技巧揭露
【8月更文挑战第31天】本文通过详细步骤和代码示例,介绍了如何利用 Blazor 构建全栈 Web 应用。从创建新的 Blazor WebAssembly 项目开始,逐步演示了前后端分离的服务架构设计,包括 REST API 的设置及 Blazor 组件的数据展示。通过整合前后端逻辑,C# 开发者能够在统一环境中实现高效且一致的全栈开发。Blazor 的引入不仅简化了 Web 应用开发流程,还为习惯于后端开发的程序员提供了进入前端世界的桥梁。
564 0
|
5月前
|
机器学习/深度学习 数据挖掘 C#
ONNX Runtime入门示例:在C#中使用ResNet50v2进行图像识别
ONNX Runtime入门示例:在C#中使用ResNet50v2进行图像识别
121 0
|
7月前
|
C#
C#数值类型介绍及示例
C#数值类型介绍及示例