商城首页欢迎来到中国正版软件门户

您的位置: 首页 > 文章列表 > 编程开发 > .NET8gRPC实现高效100G大文件断点续传工具

.NET8gRPC实现高效100G大文件断点续传工具

  发布于2026-07-23 阅读(0)

扫一扫,手机访问

前言

数字化和信息化一路高歌猛进,大文件传输这件事,在企业、科研圈乃至个人用户那里,早已不是什么新鲜话题。几十GB甚至上百GB的视频素材、工程数据,说实话,传统的传输方式碰上它们,常常让人头疼——网络一抖,程序一崩,就得从头再来,时间和带宽就这么白白耗掉了。

那有没有什么办法能“断了接着传”?当然有。这里要聊的,就是一个基于WinForm.NET gRPC技术实现的大文件断点续传工具。它不仅能扛住最大100GB的文件传输,更关键的是,网络断了之后,它能从断点处继续,而不是让你重头来过。效率和稳定性,都能上一个台阶。

项目介绍

简单来说,这是个面向桌面端用户的大文件断点续传工具。前端界面用的是WinForm,后端服务通信则交给了ASP.NET Core gRPC

它的核心目标很明确:提供一种轻量、可靠、且易于扩展的文件传输方案。适合谁用?那些需要频繁折腾大文件上传的企业或开发者。值得一提的是,整个项目不依赖复杂的第三方组件,完全在.NET生态里打转,跨平台潜力和可维护性都还不错。

项目功能

核心功能

  • 大文件支持:单个文件最大能到100GB,足够应对绝大多数场景。
  • 断点续传机制:网络断了也好,客户端异常退出也罢,只要恢复,就能从上次中断的地方继续上传。
  • 分块传输策略:把大文件切成小块再传,传输稳定性更高,并发处理也更从容。
  • 实时进度显示:上传进度、当前速度、剩余时间,都在界面上动态展示。
  • 传输管理控制:暂停、继续、取消,这些基本操作一应俱全,用户体验还算顺手。

附加功能

  • 文件校验机制:通过MD5或SHA1算法,在上传前后比对文件一致性,确保数据没被篡改。
  • 传输日志记录:每次上传的日志都自动记录下来,方便后续追踪和排查问题。
  • 本地状态持久化:用SQLite数据库保存传输状态,就算应用重启,断点信息也不会丢。

项目特点

  • 技术先进:基于最新的.NET 8框架,配合gRPC协议,远程调用和流式传输的性能确实够猛。
  • 架构清晰:前后端分离设计——前端管交互,后端管业务逻辑和数据传输。后期扩展起来很省心。
  • 协议高效gRPC跑在HTTP/2上,低延迟、高吞吐,天生适合大文件的流式上传。
  • 本地状态管理:SQLite存储上传状态,断点信息持久化,数据不会轻易丢失。
  • 序列化统一:用Protocol Buffers (Protobuf)定义数据结构和序列化,数据传输既安全又高效。

项目技术

从前端到后端,这个项目完整地构建了一套基于WinForm和gRPC的大文件传输系统。这里列一下关键技术栈和实现要点。

前端技术

使用WinForm (.NET 8)开发图形用户界面;支持多线程处理上传任务,界面不会卡顿;集成了进度条控件和日志输出模块,交互体验更友好。

后端通信

基于ASP.NET Core gRPC (.NET 8)构建服务端接口;通过.proto文件定义文件上传的数据结构和服务方法;利用gRPC的双向流特性,实现大文件的分块上传和实时响应。

数据处理

使用Google.Protobuf库完成Protobuf数据的序列化与反序列化;文件分块上传时,每块都携带偏移量和标识符,服务器端据此拼接和恢复断点;最后用MD5 / SHA1做哈希比对,确保文件一致性。

本地存储

使用SQLite数据库存储每个上传任务的状态信息,包括已上传大小、文件路径、服务器地址等;应用重启或网络中断后,读取本地记录,恢复上传上下文。

NuGet 包依赖

  • Grpc.Net.Client:构建gRPC客户端连接。
  • Google.Protobuf:提供Protobuf数据模型支持。

Grpc.Tools:编译.proto文件生成C#代码。

安装命令如下:

Install-Package Grpc.Net.Client
Install-Package Google.Protobuf
Install-Package Grpc.Tools

项目代码

/// 
/// 初始化数据库表 UploadSessions,用于记录上传会话信息。
/// 如果表不存在,则创建该表。
/// 
private void InitializeDatabase()
{
    using var connection = new SqliteConnection(_connectionString);
    connection.Open();
    var command = connection.CreateCommand();
    command.CommandText = @"
        CREATE TABLE IF NOT EXISTS UploadSessions (
            SessionId TEXT PRIMARY KEY,       -- 会话唯一标识符(GUID)
            FileName TEXT NOT NULL,           -- 文件名
            FileSize INTEGER NOT NULL,        -- 文件总大小(字节)
            FileHash TEXT NOT NULL,           -- 文件哈希值(用于断点续传校验)
            UploadedBytes INTEGER NOT NULL,   -- 已上传字节数(初始为0)
            TempFilePath TEXT NOT NULL,       -- 临时文件路径
            CreatedAt TEXT NOT NULL,          -- 创建时间(UTC格式字符串)
            CompletedAt TEXT                  -- 完成时间(可为空)
        )";
    command.ExecuteNonQuery();
}

/// 
/// 创建一个新的上传会话,并插入数据库中。
/// 
/// 上传文件的原始名称
/// 文件总大小
/// 文件的哈希值,用于校验完整性
/// 生成的会话ID
public string CreateSession(string fileName, long fileSize, string fileHash)
{
    var sessionId = Guid.NewGuid().ToString(); // 生成唯一会话ID
    var tempFilePath = Path.Combine(_tempStoragePath, $"temp_{sessionId}_{Path.GetFileName(fileName)}");
    
    using var connection = new SqliteConnection(_connectionString);
    connection.Open();
    var command = connection.CreateCommand();
    command.CommandText = @"
        INSERT INTO UploadSessions 
        (SessionId, FileName, FileSize, FileHash, UploadedBytes, TempFilePath, CreatedAt)
        VALUES 
        (@SessionId, @FileName, @FileSize, @FileHash, 0, @TempFilePath, @CreatedAt)";
    command.Parameters.AddWithValue("@SessionId", sessionId);
    command.Parameters.AddWithValue("@FileName", fileName);
    command.Parameters.AddWithValue("@FileSize", fileSize);
    command.Parameters.AddWithValue("@FileHash", fileHash);
    command.Parameters.AddWithValue("@TempFilePath", tempFilePath);
    command.Parameters.AddWithValue("@CreatedAt", DateTime.UtcNow.ToString("o")); // ISO8601 格式时间
    command.ExecuteNonQuery();

    return sessionId;
}

/// 
/// 根据会话ID获取上传会话的信息。
/// 
/// 会话ID
/// UploadSession 对象,若未找到则返回 null
public UploadSession GetSession(string sessionId)
{
    using var connection = new SqliteConnection(_connectionString);
    connection.Open();
    var command = connection.CreateCommand();
    command.CommandText = "SELECT * FROM UploadSessions WHERE SessionId = @SessionId";
    command.Parameters.AddWithValue("@SessionId", sessionId);

    using var reader = command.ExecuteReader();
    if (reader.Read())
    {
        return new UploadSession
        {
            SessionId = reader.GetString(0),
            FileName = reader.GetString(1),
            FileSize = reader.GetInt64(2),
            FileHash = reader.GetString(3),
            UploadedBytes = reader.GetInt64(4),
            TempFilePath = reader.GetString(5),
            CreatedAt = DateTime.Parse(reader.GetString(6)),
            CompletedAt = reader.IsDBNull(7) ? null : DateTime.Parse(reader.GetString(7))
        };
    }
    return null;
}

/// 
/// 根据文件名和哈希查找最近的一次上传会话。
/// 主要用于断点续传时查找已有会话。
/// 
/// 文件名
/// 文件哈希值
/// 最近一次的 UploadSession 对象,若未找到则返回 null
public UploadSession FindSession(string fileName, string fileHash)
{
    using var connection = new SqliteConnection(_connectionString);
    connection.Open();
    var command = connection.CreateCommand();
    command.CommandText = @"
        SELECT * FROM UploadSessions 
        WHERE FileName = @FileName AND FileHash = @FileHash
        ORDER BY CreatedAt DESC
        LIMIT 1";
    command.Parameters.AddWithValue("@FileName", fileName);
    command.Parameters.AddWithValue("@FileHash", fileHash);

    using var reader = command.ExecuteReader();
    if (reader.Read())
    {
        return new UploadSession
        {
            SessionId = reader.GetString(0),
            FileName = reader.GetString(1),
            FileSize = reader.GetInt64(2),
            FileHash = reader.GetString(3),
            UploadedBytes = reader.GetInt64(4),
            TempFilePath = reader.GetString(5),
            CreatedAt = DateTime.Parse(reader.GetString(6)),
            CompletedAt = reader.IsDBNull(7) ? null : DateTime.Parse(reader.GetString(7))
        };
    }
    return null;
}

/// 
/// 更新指定会话的已上传字节数。
/// 
/// 会话ID
/// 当前已上传字节数
public void UpdateSessionProgress(string sessionId, long uploadedBytes)
{
    using var connection = new SqliteConnection(_connectionString);
    connection.Open();
    var command = connection.CreateCommand();
    command.CommandText = @"
        UPDATE UploadSessions 
        SET UploadedBytes = @UploadedBytes 
        WHERE SessionId = @SessionId";
    command.Parameters.AddWithValue("@SessionId", sessionId);
    command.Parameters.AddWithValue("@UploadedBytes", uploadedBytes);
    command.ExecuteNonQuery();
}

/// 
/// 获取指定会话的已上传字节数。
/// 
/// 会话ID
/// 已上传字节数
public long GetUploadedBytes(string sessionId)
{
    using var connection = new SqliteConnection(_connectionString);
    connection.Open();
    var command = connection.CreateCommand();
    command.CommandText = "SELECT UploadedBytes FROM UploadSessions WHERE SessionId = @SessionId";
    command.Parameters.AddWithValue("@SessionId", sessionId);
    var result = command.ExecuteScalar();
    return result != null ? Convert.ToInt64(result) : 0;
}

/// 
/// 将指定会话标记为已完成。
/// 
/// 会话ID
public void CompleteSession(string sessionId)
{
    using var connection = new SqliteConnection(_connectionString);
    connection.Open();
    var command = connection.CreateCommand();
    command.CommandText = @"
        UPDATE UploadSessions 
        SET CompletedAt = @CompletedAt 
        WHERE SessionId = @SessionId";
    command.Parameters.AddWithValue("@SessionId", sessionId);
    command.Parameters.AddWithValue("@CompletedAt", DateTime.UtcNow.ToString("o"));
    command.ExecuteNonQuery();
}

/// 
/// 终止指定会话并删除临时文件及数据库记录。
/// 
/// 会话ID
public void AbortSession(string sessionId)
{
    var session = GetSession(sessionId);
    if (session != null)
    {
        try
        {
            if (File.Exists(session.TempFilePath))
            {
                File.Delete(session.TempFilePath); // 删除临时文件
            }
        }
        catch
        {
            // 可选:记录日志或处理异常
        }

        using var connection = new SqliteConnection(_connectionString);
        connection.Open();
        var command = connection.CreateCommand();
        command.CommandText = "DELETE FROM UploadSessions WHERE SessionId = @SessionId";
        command.Parameters.AddWithValue("@SessionId", sessionId);
        command.ExecuteNonQuery();
    }
}

/// 
/// 清理过期的上传会话(未完成且超过指定时间)。
/// 同时删除对应的临时文件和数据库记录。
/// 
/// 会话的过期时间跨度
public void CleanupExpiredSessions(TimeSpan expirationTime)
{
    var cutoff = DateTime.UtcNow - expirationTime;
    
    using var connection = new SqliteConnection(_connectionString);
    connection.Open();

    // 首先查询所有过期会话
    var selectCommand = connection.CreateCommand();
    selectCommand.CommandText = @"
        SELECT SessionId, TempFilePath FROM UploadSessions 
        WHERE CreatedAt < @Cutoff AND (CompletedAt IS NULL OR CompletedAt < @Cutoff)";
    selectCommand.Parameters.AddWithValue("@Cutoff", cutoff.ToString("o"));
    
    var sessionsToDelete = new List<(string SessionId, string TempFilePath)>();
    using (var reader = selectCommand.ExecuteReader())
    {
        while (reader.Read())
        {
            sessionsToDelete.Add((reader.GetString(0), reader.GetString(1)));
        }
    }

    // 然后依次删除临时文件和数据库记录
    foreach (var (sessionId, tempFilePath) in sessionsToDelete)
    {
        try
        {
            if (File.Exists(tempFilePath))
            {
                File.Delete(tempFilePath);
            }
        }
        catch
        {
            // 可选:记录日志或处理异常
        }

        var deleteCommand = connection.CreateCommand();
        deleteCommand.CommandText = "DELETE FROM UploadSessions WHERE SessionId = @SessionId";
        deleteCommand.Parameters.AddWithValue("@SessionId", sessionId);
        deleteCommand.ExecuteNonQuery();
    }
}

项目效果

.NET8gRPC实现高效100G大文件断点续传工具

本文转载于:https://www.jb51.net/aspnet/344056qic.htm 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。

热门关注