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

C#超1TB网络文件复制:故障后断点续传实现方案咨询

实现大文件网络复制的断点续传方案(C#)

要处理1TB+规模的网络文件复制并支持断点续传,核心是持久化复制状态+增量复制,具体实现步骤如下:

一、设计状态存储结构

需要本地存储复制任务的进度信息,重启后可直接加载恢复。推荐用JSON文件或SQLite(数据量大时更高效),以下是核心状态类:

// 单个文件的复制状态
public class FileCopyStatus
{
    public string SourcePath { get; set; }
    public string TargetPath { get; set; }
    public long CopiedBytes { get; set; }
    public long TotalBytes { get; set; }
    public CopyStatus Status { get; set; }
    public string SourceHash { get; set; } // 源文件哈希,用于完整性校验
}

// 文件夹创建状态
public class FolderCreateStatus
{
    public string SourcePath { get; set; }
    public string TargetPath { get; set; }
    public bool IsCreated { get; set; }
}

// 整个复制任务的状态
public class CopyTaskStatus
{
    public List<FolderCreateStatus> FolderStatuses { get; set; } = new();
    public List<FileCopyStatus> FileStatuses { get; set; } = new();
    public DateTime LastUpdated { get; set; }
}

public enum CopyStatus
{
    NotStarted,
    InProgress,
    Completed,
    Failed
}

二、初始化/加载任务状态

启动复制前先检查是否存在历史状态:

public static CopyTaskStatus InitializeOrLoadTask(string p_FilePaths)
{
    // 检查是否有已保存的状态文件
    if (File.Exists("CopyTaskStatus.json"))
    {
        string json = File.ReadAllText("CopyTaskStatus.json");
        return JsonSerializer.Deserialize<CopyTaskStatus>(json);
    }

    // 初始化新任务状态
    var newTask = new CopyTaskStatus();
    // 加载所有文件夹并初始化状态
    var directoryLevels = GetAllFolders(p_FilePaths);
    foreach (var (source, target) in directoryLevels)
    {
        newTask.FolderStatuses.Add(new FolderCreateStatus
        {
            SourcePath = source,
            TargetPath = target,
            IsCreated = false
        });
    }

    // 加载所有文件并初始化状态(提前计算源文件哈希)
    var filesLevels = GetAllFiles(p_FilePaths);
    foreach (var (source, target) in filesLevels)
    {
        var sourceFile = new FileInfo(source);
        newTask.FileStatuses.Add(new FileCopyStatus
        {
            SourcePath = source,
            TargetPath = target,
            TotalBytes = sourceFile.Length,
            CopiedBytes = 0,
            Status = CopyStatus.NotStarted,
            SourceHash = CalculateFileHash(source)
        });
    }

    SaveTaskStatus(newTask);
    return newTask;
}

三、文件夹复制的断点处理

跳过已创建的文件夹,仅处理未完成的部分:

public static void ProcessFolders(CopyTaskStatus taskStatus)
{
    foreach (var folderStatus in taskStatus.FolderStatuses)
    {
        if (folderStatus.IsCreated) continue;

        try
        {
            // 创建目标文件夹(自动创建父目录)
            Directory.CreateDirectory(folderStatus.TargetPath);
            folderStatus.IsCreated = true;
            SaveTaskStatus(taskStatus); // 持久化状态
        }
        catch (Exception ex)
        {
            Console.WriteLine($"创建文件夹失败:{folderStatus.TargetPath},错误:{ex.Message}");
            // 可加入重试逻辑,例如重试3次后标记为待处理
            Thread.Sleep(3000);
        }
    }
}

四、文件断点续传核心逻辑

根据状态决定是从头复制还是从断点续传,同时定期保存进度:

public static void ProcessFiles(CopyTaskStatus taskStatus)
{
    foreach (var fileStatus in taskStatus.FileStatuses)
    {
        if (fileStatus.Status == CopyStatus.Completed) continue;

        try
        {
            var targetFile = new FileInfo(fileStatus.TargetPath);
            long startPosition = 0;

            // 处理已存在的目标文件
            if (targetFile.Exists)
            {
                if (fileStatus.Status == CopyStatus.InProgress)
                {
                    // 验证目标文件大小是否与已复制字节数一致(避免中途被修改)
                    if (targetFile.Length == fileStatus.CopiedBytes)
                    {
                        startPosition = fileStatus.CopiedBytes;
                    }
                    else
                    {
                        // 文件异常,删除后从头复制
                        targetFile.Delete();
                        fileStatus.CopiedBytes = 0;
                    }
                }
                else
                {
                    // 未开始但文件存在,删除后从头复制
                    targetFile.Delete();
                }
            }

            // 更新状态为复制中并保存
            fileStatus.Status = CopyStatus.InProgress;
            SaveTaskStatus(taskStatus);

            // 执行增量复制
            using var sourceStream = new FileStream(fileStatus.SourcePath, FileMode.Open, FileAccess.Read, FileShare.Read);
            using var targetStream = new FileStream(fileStatus.TargetPath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None);
            
            sourceStream.Seek(startPosition, SeekOrigin.Begin);
            targetStream.Seek(startPosition, SeekOrigin.Begin);

            byte[] buffer = new byte[81920]; // 80KB缓冲区,可根据网络带宽调整
            int bytesRead;
            while ((bytesRead = sourceStream.Read(buffer, 0, buffer.Length)) > 0)
            {
                targetStream.Write(buffer, 0, bytesRead);
                fileStatus.CopiedBytes += bytesRead;

                // 每复制10MB保存一次进度,避免断电丢失
                if (fileStatus.CopiedBytes % (1024 * 1024 * 10) == 0)
                {
                    SaveTaskStatus(taskStatus);
                }
            }

            // 复制完成后校验哈希
            string targetHash = CalculateFileHash(fileStatus.TargetPath);
            if (targetHash == fileStatus.SourceHash)
            {
                fileStatus.Status = CopyStatus.Completed;
                SaveTaskStatus(taskStatus);
            }
            else
            {
                fileStatus.Status = CopyStatus.Failed;
                SaveTaskStatus(taskStatus);
                Console.WriteLine($"文件校验失败:{fileStatus.TargetPath}");
            }
        }
        catch (IOException ex)
        {
            // 网络中断、服务器故障等异常,保存当前进度
            fileStatus.Status = CopyStatus.InProgress;
            SaveTaskStatus(taskStatus);
            Console.WriteLine($"复制中断:{fileStatus.SourcePath},错误:{ex.Message}");
            Thread.Sleep(5000); // 等待5秒后重试当前文件
        }
        catch (Exception ex)
        {
            fileStatus.Status = CopyStatus.Failed;
            SaveTaskStatus(taskStatus);
            Console.WriteLine($"复制失败:{fileStatus.SourcePath},错误:{ex.Message}");
        }
    }
}

五、辅助方法实现

// 计算文件SHA256哈希
private static string CalculateFileHash(string filePath)
{
    using var sha256 = SHA256.Create();
    using var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
    byte[] hashBytes = sha256.ComputeHash(stream);
    return BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant();
}

// 保存任务状态到JSON文件
private static void SaveTaskStatus(CopyTaskStatus status)
{
    status.LastUpdated = DateTime.Now;
    string json = JsonSerializer.Serialize(status, new JsonSerializerOptions { WriteIndented = true });
    File.WriteAllText("CopyTaskStatus.json", json);
}

六、主方法改造

public static void SubmitDocsToRepository(string p_FilePaths) 
{
    var taskStatus = InitializeOrLoadTask(p_FilePaths);
    ProcessFolders(taskStatus);
    ProcessFiles(taskStatus);

    // 任务完成后可选择删除状态文件或保留作为凭证
    if (taskStatus.FileStatuses.All(f => f.Status == CopyStatus.Completed))
    {
        File.Delete("CopyTaskStatus.json");
    }
}

关键注意事项

  • 状态持久化频率:避免每写一次字节就保存,推荐按固定大小(如10MB)或时间间隔保存,平衡性能与数据安全性
  • 并发控制:如果用多线程复制,需给状态文件读写加锁,避免线程冲突
  • 重试策略:针对网络异常设置合理的重试次数和间隔,避免频繁重试浪费资源
  • 权限处理:确保对源文件有读权限、目标路径有写权限,捕获权限相关异常并提示用户

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.23 10:33:17