C# 以同步或异步的方式下载文件,包括断点续传、计算下载速度、下载进度。
- 同步方法
/// <summary>
/// 同步下载方法,支持断点续传,计算下载速度,下载进度
/// </summary>
/// <param name="url">下载链接</param>
/// <param name="savePath">文件保存路径,C://xxx.zip</param>
/// <param name="cancellationToken"></param>
/// <param name="progressCallBack">进度回调</param>
/// <param name="downloadMsgCallBack">速度回调</param>
/// <returns></returns>
public static string GetHttp(string url, string savePath, CancellationTokenSource cancellationToken, Action<double> progressCallBack = null, Action<string> downloadMsgCallBack = null)
{
ServicePointManager.DefaultConnectionLimit = 50;
string responseContent = "";
HttpWebRequest httpWebRequest = null;
try
{
if (string.IsNullOrEmpty(savePath))
return responseContent;
long rangeBegin = 0;//记录已经下载了多少
if (File.Exists(savePath))
rangeBegin = new FileInfo(savePath).Length;
if (url.StartsWith("https", StringComparison.OrdinalIgnoreCase))
{
ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(CheckValidationResult);
ServicePointManager.Expect100Continue = true;
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
httpWebRequest = WebRequest.Create(url) as HttpWebRequest;
}
else
httpWebRequest = (HttpWebRequest)WebRequest.Create(url);
//httpWebRequest.Timeout = 300000;
//httpWebRequest.ReadWriteTimeout = 60 * 1000;
httpWebRequest.Method = "GET";
httpWebRequest.KeepAlive = false;
httpWebRequest.ServicePoint.Expect100Continue = false;
httpWebRequest.Proxy = null;
if (rangeBegin > 0)
httpWebRequest.AddRange(rangeBegin);
using (HttpWebResponse httpWebResponse = (HttpWebResponse)httpWebRequest.GetResponse())
{
using (Stream responseStream = httpWebResponse.GetResponseStream())
{
if (responseStream == null)
return responseContent;
long totalLength = httpWebResponse.ContentLength;
if (totalLength == rangeBegin)
{
progressCallBack?.Invoke(100);
return savePath;
}
if (totalLength == 0 || rangeBegin > totalLength)
{
progressCallBack?.Invoke(0);
return responseContent;
}
long offset = rangeBegin;
long oneSecondDonwload = 0;
var beginSecond = DateTime.Now.Second;
//savePath = Path.Combine(savePath, httpWebResponse.ResponseUri.LocalPath.Substring(httpWebResponse.ResponseUri.LocalPath.LastIndexOf('/') + 1)); //自动获取文件名
using (Stream stream = new FileStream(savePath, FileMode.Append, FileAccess.Write))
{
byte[] bArr = new byte[1024 * 8 * 1024];
int size = 0;
while ((size = responseStream.Read(bArr, 0, (int)bArr.Length)) > 0)
{
try
{
stream.Write(bArr, 0, size);
oneSecondDonwload += size;
offset = offset + size;
var endSencond = DateTime.Now.Second;
if (beginSecond != endSencond)
{
string outSize = GetFileSizeTypeBySize(oneSecondDonwload / (endSencond - beginSecond));
downloadMsgCallBack?.Invoke($"{outSize}/S");
beginSecond = DateTime.Now.Second;
oneSecondDonwload = 0;
}
if (cancellationToken.IsCancellationRequested)
{
savePath = "";
break;
}
progressCallBack?.Invoke(Math.Round((offset / (totalLength * 1.0)) * 100, 2));
}
catch (Exception ex)
{
throw ex;
}
}
responseContent = savePath;
}
}
httpWebResponse?.Close();
}
}
catch (Exception ex)
{
responseContent = "";
}
finally
{
httpWebRequest?.Abort();
}
return responseContent;
}
- 异步方法
/// <summary>
/// 异步下载方法,支持断点续传,计算下载速度,下载进度
/// </summary>
/// <param name="url">下载链接</param>
/// <param name="savePath">文件保存路径,C://xxx.zip</param>
/// <param name="cancellationToken"></param>
/// <param name="progressCallBack">进度回调</param>
/// <param name="downloadMsgCallBack">消息回调</param>
/// <returns></returns>
public static async Task<string> GetHttpAsync(string url, string savePath, CancellationTokenSource cancellationToken, Action<double> progressCallBack = null, Action<string> downloadMsgCallBack = null)
{
string responseContent = "";
try
{
if (string.IsNullOrEmpty(url))
return responseContent;
if (string.IsNullOrEmpty(savePath))
return responseContent;
long rangeBegin = 0;
if (File.Exists(savePath))
rangeBegin = new FileInfo(savePath).Length;
using (HttpClient http = new HttpClient())
{
var request = new HttpRequestMessage
{
RequestUri = new Uri(url),
};
//http.Timeout = Timeout.InfiniteTimeSpan; //慎用最大值
request.Headers.Range = new RangeHeaderValue(rangeBegin, null);//记录已经下载了多少
var httpResponseMessage = await http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
long contentLength = (long)httpResponseMessage.Content.Headers.ContentLength;//本次请求的内容大小
if (httpResponseMessage.Content.Headers.ContentRange != null)//为空则不支持断点续传
contentLength = (long)httpResponseMessage.Content.Headers.ContentRange.Length;//服务器上文件大小
if (contentLength == rangeBegin)
{
progressCallBack?.Invoke(100);
return savePath;
}
if (contentLength == 0 || rangeBegin > contentLength)
{
progressCallBack?.Invoke(0);
return responseContent;
}
using (var stream = await httpResponseMessage.Content.ReadAsStreamAsync())
{
int writeLength;//记录读到的真实字节数
long oneSecondDownload = 0;//一秒内下载的字节
var readLength = 1024 * 1024 * 8;
byte[] bytes = new byte[readLength];
var beginSecond = DateTime.Now.Second;
while ((writeLength = stream.Read(bytes, 0, readLength)) > 0)
{
using (FileStream fs = new FileStream(savePath, FileMode.Append, FileAccess.Write))
{
fs.Write(bytes, 0, writeLength);
}
oneSecondDownload += writeLength;
rangeBegin += writeLength;
var endSecond = DateTime.Now.Second;
if (endSecond != beginSecond)
{
string outSize = GetFileSizeTypeBySize(oneSecondDownload / (endSecond - beginSecond));
downloadMsgCallBack?.Invoke($"{outSize}/S");
beginSecond = DateTime.Now.Second;
oneSecondDownload = 0;
}
if (cancellationToken.IsCancellationRequested)
break;
progressCallBack?.Invoke(Math.Round(rangeBegin / (contentLength * 1.0) * 100, 2));
}
if (rangeBegin == contentLength)
{
responseContent = savePath;
}
}
}
return responseContent;
}
catch (Exception ex)
{
return "";
}
}
3.辅助方法
private static bool CheckValidationResult(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
return true;
}
private static string GetFileSizeTypeBySize(long fileSize)
{
if (fileSize < 1024)
return $"{fileSize}B";
if (fileSize / 1024 < 1024)
return $"{fileSize / 1024}KB";
if (fileSize / (1024 * 1024) < 1024)
return $"{fileSize / (1024 * 1024)}MB";
else
return $"{fileSize / (1024 * 1024 * 1024)}GB";
}