首页 > 编程语言 >C#断点续传的实现示例

C#断点续传的实现示例

时间:2023-11-11 15:34:39浏览次数:37  
标签:断点续传 示例 C# filename startPosition using var new fileInfo

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

在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协议断点续传的方法,并提供了完整的代码示例。希望读者通过本文的介绍,能够成功实现断点续传功能,并在实际工作中应用到相应的场景中去。

参考文章:http://blog.ncmem.com/wordpress/2023/11/11/c%e6%96%ad%e7%82%b9%e7%bb%ad%e4%bc%a0%e7%9a%84%e5%ae%9e%e7%8e%b0%e7%a4%ba%e4%be%8b/

欢迎入群一起讨论

 

 

标签:断点续传,示例,C#,filename,startPosition,using,var,new,fileInfo
From: https://www.cnblogs.com/songsu/p/17825965.html

相关文章

  • 对象定义 Object.create Object.defineProperty
    <!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><metahttp-equiv="X-UA-Compatible"content="IE=edge"><metaname="viewport"content="width=device-......
  • ReactNative进阶(十):WebView 应用详解
    (文章目录)一、WebView组件介绍使用WebView组件可通过url来加载显示一个网页,也可以传入一段html代码来显示。下面对其主要属性和方法进行介绍。1.主要属性source:在WebView中载入一段静态的html代码或是一个url(还可以附带一些header选项);automaticallyAdjustCon......
  • PHP反序列化题型_绕过preg_match1
    ctfshowweb266<?phphighlight_file(__FILE__);include('flag.php');$cs=file_get_contents('php://input');classctfshow{public$username='xxxxxx';public$password='xxxxxx';publicfunction__......
  • 信息收集-CDN绕过
    什么是CDN加速?CDN的全称是ContentDeliveryNetwork,即内容分发网络。CDN是构建在现有网络基础之上的智能虚拟网络,依靠部署在各地的边缘服务器,通过中心平台的负载均衡、内容分发、调度等功能模块,使用户就近获取所需内容,降低网络拥塞,提高用户访问响应速度和命中率。但在安全......
  • CentOS 7编译Linux内核(6.5.7)详细步骤
    CentOS7编译Linux内核(6.5.7)详细步骤参考链接:下载解压部分参考:Linux内核动手编译实用指南-LinuxEden比较详细,可用于了解原理,但没有给出针对CentOS7的方案(实验室用到的openEuler基于CentOS,所以需要CentOS的方案)。配置编译安装参考:CentOS7下编译安装Linux4.14内核-......
  • tensorflow版本与CUDA、cuDNN、Python适配表
    从源代码构建 | TensorFlow(google.cn)......
  • MAC 快捷键
    MAC官方快捷键功能快捷键复制路径option+command+c返回桌面fn+f11强制退出option+command+esc切换appcommand+tab全屏control+command+f,再按一次退出全屏最近使用文件shift+command+f聚焦搜索command+空格......
  • odoo16前端框架源码阅读——rpc_service.js
    odoo16前端框架源码阅读——rpc_service.js先介绍点背景知识,这样方便阅读代码。一、JSONRPC的规范https://www.jsonrpc.org/specification中文翻译版本:https://wiki.geekdream.com/Specification/json-rpc_2.0.htmlJSON-RPC是一个无状态且轻量级的远程过程调用(RPC)协议。本规......
  • leet code 46. 全排列
    46.全排列题目描述给定一个不含重复数字的数组nums,返回其所有可能的全排列。你可以按任意顺序返回答案。示例1:输入:nums=[1,2,3]输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]示例2:输入:nums=[0,1]输出:[[0,1],[1,0]]示例3:输入:nums=[1]输出:[[1]]提示......
  • Ecplise导入Maven项目总结
        最近做项目,需要eclipse中导入maven项目,需要一些配置,总结如下:前提:   电脑中安装了Maven并且已经在Eclipse中加入了Maven插件。   Eclipse中配置好了能运行的Tomcat。1、在Eclipse中点击"File"->Import->Maven,选择ExistingMavenProjects   2、选择你......