测试文件下载暂停/续传时遇System.Net.WebException报错如何解决?
问题描述
测试文件下载的暂停/续传功能时,持续遇到System.Net.WebException异常:
The underlying connection was closed: The connection was closed unexpectedly.
直接从网站下载该文件一切正常,站点本身无问题。
异常详情
System.Net.WebException HResult=0x80131509 Message=The underlying connection was closed: The connection was closed unexpectedly. Source=System StackTrace: at System.Net.HttpWebRequest.GetResponse() at File_Download.FileDownloader.GetContentLength() in D:\Csharp Projects\File Download\FileDownloader.cs:line 56 at System.Lazy`1.CreateValue()
异常发生在第56行:
var response = request.GetResponse()
FileDownloader类代码
using System; using System.IO; using System.Net; using System.Threading.Tasks; namespace File_Download { public class FileDownloader { private volatile bool _allowedToRun; private readonly string _sourceUrl; private readonly string _destination; private readonly int _chunkSize; private readonly IProgress<double> _progress; private readonly Lazy<long> _contentLength; public long BytesWritten { get; private set; } public long ContentLength => _contentLength.Value; public bool Done => ContentLength == BytesWritten; public FileDownloader(string source, string destination, int chunkSizeInBytes = 10000 /*Default to 0.01 mb*/, IProgress<double> progress = null) { if (string.IsNullOrEmpty(source)) throw new ArgumentNullException("source is empty"); if (string.IsNullOrEmpty(destination)) throw new ArgumentNullException("destination is empty"); _allowedToRun = true; _sourceUrl = source; _destination = destination; _chunkSize = chunkSizeInBytes; _contentLength = new Lazy<long>(GetContentLength); _progress = progress; if (!File.Exists(destination)) BytesWritten = 0; else { try { BytesWritten = new FileInfo(destination).Length; } catch { BytesWritten = 0; } } } private long GetContentLength() { var request = (HttpWebRequest)WebRequest.Create(_sourceUrl); request.Method = "HEAD"; using (var response = request.GetResponse()) return response.ContentLength; } private async Task Start(long range) { if (!_allowedToRun) throw new InvalidOperationException(); if (Done) //file has been found in folder destination and is already fully downloaded return; var request = (HttpWebRequest)WebRequest.Create(_sourceUrl); request.Method = "GET"; request.UserAgent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; .NET CLR 1.0.3705;)"; request.AddRange(range); using (var response = await request.GetResponseAsync()) { using (var responseStream = response.GetResponseStream()) { using (var fs = new FileStream(_destination, FileMode.Append, FileAccess.Write, FileShare.ReadWrite)) { while (_allowedToRun) { var buffer = new byte[_chunkSize]; var bytesRead = await responseStream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false); if (bytesRead == 0) break; await fs.WriteAsync(buffer, 0, bytesRead); BytesWritten += bytesRead; _progress?.Report((double)BytesWritten / ContentLength); } await fs.FlushAsync(); } } } } public Task Start() { _allowedToRun = true; return Start(BytesWritten); } public void Pause() { _allowedToRun = false; } } }
Form1调用代码
using System; using System.Windows.Forms; namespace File_Download { public partial class Form1 : Form { private FileDownloader fw; public Form1() { InitializeComponent(); } private void ReportProgress(double progress) { progressBar1.Value = (int)(progress * 100); label1.Text = string.Format("ContentLength: {0} | BytesWritten: {1}", fw.ContentLength, fw.BytesWritten); } private void btnStart_Click(object sender, EventArgs e) { // Start the download only when the "Start" button is clicked if (fw == null || fw.Done) { fw = new FileDownloader("https://hil-speed.hetzner.com/100MB.bin", @"D:\100MB.bin", 5120, new Progress<double>(ReportProgress)); } // Start the download fw.Start().ContinueWith(t => label1.Text = "Done"); } private void btnPause_Click(object sender, EventArgs e) { if (btnPause.Text == "Pause") { fw.Pause(); btnPause.Text = "Resume"; } else { // Resume the download fw.Start().ContinueWith(t => label1.Text = "Done"); } } } }
解决方法
1. 为HEAD请求添加User-Agent
很多服务器会拒绝无User-Agent标识的请求,或识别为非合法客户端断开连接。修改GetContentLength方法,添加与GET请求一致的User-Agent:
private long GetContentLength() { var request = (HttpWebRequest)WebRequest.Create(_sourceUrl); request.Method = "HEAD"; // 同步GET请求的User-Agent request.UserAgent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; .NET CLR 1.0.3705;)"; using (var response = request.GetResponse()) return response.ContentLength; }
2. 启用HTTP Keep-Alive
开启连接保持可减少频繁建连开销,避免服务器主动关闭空闲连接,在请求中添加:
request.KeepAlive = true;
该配置需同时添加到GetContentLength和Start方法的请求中。
3. 添加重试机制
网络波动可能导致连接意外关闭,为GetContentLength添加重试逻辑,避免单次失败抛出异常:
private long GetContentLength() { int retryCount = 3; while (retryCount > 0) { try { var request = (HttpWebRequest)WebRequest.Create(_sourceUrl); request.Method = "HEAD"; request.UserAgent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; .NET CLR 1.0.3705;)"; request.KeepAlive = true; using (var response = request.GetResponse()) return response.ContentLength; } catch (WebException) { retryCount--; if (retryCount == 0) throw; // 重试前等待1秒,避免频繁请求 Task.Delay(1000).Wait(); } } throw new WebException("多次重试后仍无法获取文件长度"); }
4. 替换为HttpClient(推荐方案)
HttpWebRequest是较旧API,HttpClient是.NET官方推荐的HTTP客户端,对连接管理、异常处理更友好,稳定性更高。重写核心代码如下:
using System; using System.IO; using System.Net.Http; using System.Threading.Tasks; namespace File_Download { public class FileDownloader { private volatile bool _allowedToRun; private readonly string _sourceUrl; private readonly string _destination; private readonly int _chunkSize; private readonly IProgress<double> _progress; private readonly Lazy<long> _contentLength; // 全局复用HttpClient,避免频繁创建实例 private static readonly HttpClient _httpClient = new HttpClient(); static FileDownloader() { _httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; .NET CLR 1.0.3705;)"); } // 构造函数、属性保持不变... private async Task<long> GetContentLengthAsync() { using (var response = await _httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Head, _sourceUrl))) { response.EnsureSuccessStatusCode(); return response.Content.Headers.ContentLength ?? 0; } } private async Task Start(long range) { if (!_allowedToRun) throw new InvalidOperationException(); if (Done) return; var request = new HttpRequestMessage(HttpMethod.Get, _sourceUrl); request.Headers.Range = new System.Net.Http.Headers.RangeHeaderValue(range, null); using (var response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead)) { response.EnsureSuccessStatusCode(); using (var responseStream = await response.Content.ReadAsStreamAsync()) using (var fs = new FileStream(_destination, FileMode.Append, FileAccess.Write, FileShare.ReadWrite)) { while (_allowedToRun) { var buffer = new byte[_chunkSize]; var bytesRead = await responseStream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false); if (bytesRead == 0) break; await fs.WriteAsync(buffer, 0, bytesRead); BytesWritten += bytesRead; _progress?.Report((double)BytesWritten / ContentLength); } await fs.FlushAsync(); } } } // Start、Pause方法保持不变... } }
内容的提问来源于stack exchange,提问作者Daniel Lip
相关产品推荐
相关产品推荐

