You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

测试文件下载暂停/续传时遇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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 14:48:12