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

使用C# ZipFile创建含音乐的ZIP文件时遇传输流EOF异常

解决ZIP打包时“Received an unexpected EOF or 0 bytes from the transport stream”异常

这个EOF异常我之前处理过好几次,大概率是网络流没读完整就传给Zip库导致的——毕竟网络流是一次性、不可回溯的,而且很容易因为网络波动中断,Zip库需要完整的数据流才能正确打包。给你几个靠谱的解决方案:

核心思路:先缓存完整数据流,再打包

不要直接把HTTP响应的网络流传给Zip库,先把流完整读取到内存或临时文件里,确保数据没有缺失后再加入ZIP包。

1. 缓存到内存流(适合小文件)

用内存流先把音乐文件完整下载下来,再写入ZIP。这里以你用到的Ionic DotNetZip库为例:

// 建议复用HttpClient,避免socket资源耗尽
private static readonly HttpClient _httpClient = new HttpClient();

public async Task CreateMusicZip(List<string> musicUrls, string outputZipPath)
{
    using (var zip = new Ionic.Zip.ZipFile())
    {
        foreach (var url in musicUrls)
        {
            try
            {
                using (var responseStream = await _httpClient.GetStreamAsync(url))
                using (var memoryStream = new MemoryStream())
                {
                    // 把网络流完整复制到内存流
                    await responseStream.CopyToAsync(memoryStream);
                    // 重置内存流位置到开头,不然Zip库读不到内容
                    memoryStream.Position = 0;
                    
                    // 从URL提取文件名作为ZIP内的文件名
                    var fileName = Path.GetFileName(new Uri(url).LocalPath);
                    zip.AddEntry(fileName, memoryStream);
                }
            }
            catch (Exception ex)
            {
                // 可以在这里记录单个文件下载失败的日志,不影响其他文件
                Console.WriteLine($"Failed to download {url}: {ex.Message}");
            }
        }
        await zip.SaveAsync(outputZipPath);
    }
}

2. 缓存到临时文件(适合大文件)

如果音乐文件体积较大,用内存流会占用过多内存,这时候可以先下载到临时文件,再加入ZIP,最后清理临时文件:

public async Task CreateMusicZipWithTempFiles(List<string> musicUrls, string outputZipPath)
{
    using (var zip = new Ionic.Zip.ZipFile())
    {
        foreach (var url in musicUrls)
        {
            string tempFilePath = null;
            try
            {
                tempFilePath = Path.GetTempFileName();
                // .NET 6+ 自带DownloadFileAsync,低版本可以自己实现流复制
                await _httpClient.DownloadFileAsync(url, tempFilePath);
                
                var fileName = Path.GetFileName(new Uri(url).LocalPath);
                zip.AddFile(tempFilePath, ""); // 第二个参数是ZIP内的目录,空表示根目录
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Failed to process {url}: {ex.Message}");
            }
            finally
            {
                if (tempFilePath != null && File.Exists(tempFilePath))
                {
                    File.Delete(tempFilePath);
                }
            }
        }
        await zip.SaveAsync(outputZipPath);
    }
}

3. 增加重试机制,应对网络波动

网络请求偶尔会因为波动失败,加上重试能大幅降低异常概率:

private async Task<MemoryStream> DownloadWithRetry(string url, int maxRetries = 3)
{
    for (int retry = 0; retry < maxRetries; retry++)
    {
        try
        {
            using (var responseStream = await _httpClient.GetStreamAsync(url))
            {
                var memoryStream = new MemoryStream();
                await responseStream.CopyToAsync(memoryStream);
                memoryStream.Position = 0;
                return memoryStream;
            }
        }
        catch (HttpRequestException)
        {
            if (retry == maxRetries - 1) throw; // 最后一次重试失败,抛出异常
            await Task.Delay(1000 * (retry + 1)); // 指数退避,间隔1s、2s、3s重试
        }
    }
    throw new InvalidOperationException("Failed to download after max retries");
}

然后在打包方法里调用这个重试方法即可。

4. 验证下载完整性(可选)

如果HTTP响应返回了Content-Length头,可以验证下载的流长度是否和预期一致,提前发现不完整的下载:

using (var response = await _httpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead))
{
    response.EnsureSuccessStatusCode(); // 确保HTTP请求成功(状态码200-299)
    
    if (response.Content.Headers.ContentLength.HasValue)
    {
        long expectedLength = response.Content.Headers.ContentLength.Value;
        using (var responseStream = await response.Content.ReadAsStreamAsync())
        using (var memoryStream = new MemoryStream())
        {
            await responseStream.CopyToAsync(memoryStream);
            if (memoryStream.Length != expectedLength)
            {
                throw new IOException($"Incomplete download: expected {expectedLength} bytes, got {memoryStream.Length}");
            }
            memoryStream.Position = 0;
            return memoryStream;
        }
    }
    
    // 没有Content-Length的情况,直接返回流
    var stream = await response.Content.ReadAsStreamAsync();
    var memoryStream = new MemoryStream();
    await stream.CopyToAsync(memoryStream);
    memoryStream.Position = 0;
    return memoryStream;
}

额外注意点

  • 不要每次创建新的HttpClient实例,复用一个实例可以避免socket资源耗尽的问题。
  • 如果用的是.NET自带的System.IO.Compression.ZipArchive,核心逻辑一样:先缓存完整流,再写入ZIP条目。

内容的提问来源于stack exchange,提问作者Ankit Patel

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.20 12:24:24