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

ASP.NET教程 2025-08-25

目录

  • 前言
  • 项目介绍
  • 项目功能
    • 核心功能
    • 附加功能
  • 项目特点
    • 项目技术
      • 前端技术
      • 后端通信
      • 数据处理
      • 本地存储
      • NuGet 包依赖
    • 项目代码
      • 项目效果

        前言

        随着数字化和信息化的发展,大文件传输在企业、科研以及个人用户中变得越来越常见。传统的文件传输方式在面对大文件(如几十GB甚至上百GB的视频、工程数据)时,常常因网络不稳定、程序崩溃等原因导致传输失败,而重新上传又浪费大量时间和带宽资源。

        为了解决这一问题,本文推荐一个基于WinForm.NET gRPC技术实现的大文件断点续传工具。该工具不仅支持最大100GB文件的高效传输,还具备在网络中断后从中断点继续传输的能力,大大提高了传输效率与稳定性。

        项目介绍

        项目是一个面向桌面端用户的大文件断点续传工具,采用WinForm构建前端界面,使用ASP.NET Core gRPC实现后端服务通信。

        其核心目标是提供一种轻量级、可靠且易于扩展的文件传输解决方案,适用于需要频繁进行大文件上传的企业或开发人员。

        该项目不依赖复杂的第三方组件,完全基于.NET 生态构建,具有良好的跨平台潜力和可维护性。

        项目功能

        核心功能

        • 大文件支持:支持最大100GB的单个文件上传。
        • 断点续传机制:在网络中断或客户端异常退出后,能够从中断位置继续上传。
        • 分块传输策略:将大文件切分为多个小块进行传输,提升传输稳定性和并发处理能力。
        • 实时进度显示:在界面上动态展示当前上传进度、速度及剩余时间。
        • 传输管理控制:支持暂停、继续、取消等操作,增强用户体验。

        附加功能

        • 文件校验机制:通过MD5或SHA1算法验证上传前后文件的一致性,确保数据完整性。
        • 传输日志记录:自动记录每次上传的日志信息,便于追踪和故障排查。
        • 本地状态持久化:使用SQLite数据库保存传输状态,保障断点信息不丢失。

        项目特点

        • 技术先进:采用最新的.NET 8框架,结合gRPC协议,实现了高性能的远程调用和流式传输。
        • 架构清晰:前后端分离设计,前端负责交互,后端专注业务逻辑与数据传输,便于后期扩展。
        • 协议高效:基于HTTP/2gRPC协议,具备低延迟、高吞吐量的优势,非常适合大文件流式上传。
        • 本地状态管理:使用 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
        

        项目代码

        /// summary
        /// 初始化数据库表 UploadSessions,用于记录上传会话信息。
        /// 如果表不存在,则创建该表。
        /// /summary
        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();
        }
        
        /// summary
        /// 创建一个新的上传会话,并插入数据库中。
        /// /summary
        /// param name="fileName"上传文件的原始名称/param
        /// param name="fileSize"文件总大小/param
        /// param name="fileHash"文件的哈希值,用于校验完整性/param
        /// returns生成的会话ID/returns
        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;
        }
        
        /// summary
        /// 根据会话ID获取上传会话的信息。
        /// /summary
        /// param name="sessionId"会话ID/param
        /// returnsUploadSession 对象,若未找到则返回 null/returns
        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;
        }
        
        /// summary
        /// 根据文件名和哈希查找最近的一次上传会话。
        /// 主要用于断点续传时查找已有会话。
        /// /summary
        /// param name="fileName"文件名/param
        /// param name="fileHash"文件哈希值/param
        /// returns最近一次的 UploadSession 对象,若未找到则返回 null/returns
        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;
        }
        
        /// summary
        /// 更新指定会话的已上传字节数。
        /// /summary
        /// param name="sessionId"会话ID/param
        /// param name="uploadedBytes"当前已上传字节数/param
        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();
        }
        
        /// summary
        /// 获取指定会话的已上传字节数。
        /// /summary
        /// param name="sessionId"会话ID/param
        /// returns已上传字节数/returns
        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;
        }
        
        /// summary
        /// 将指定会话标记为已完成。
        /// /summary
        /// param name="sessionId"会话ID/param
        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();
        }
        
        /// summary
        /// 终止指定会话并删除临时文件及数据库记录。
        /// /summary
        /// param name="sessionId"会话ID/param
        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();
            }
        }
        
        /// summary
        /// 清理过期的上传会话(未完成且超过指定时间)。
        /// 同时删除对应的临时文件和数据库记录。
        /// /summary
        /// param name="expirationTime"会话的过期时间跨度/param
        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();
            }
        }
        

        项目效果

        到此这篇关于.NET8 gRPC实现高效100G大文件断点续传工具 的文章就介绍到这了,更多相关.NET大文件断点续传内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!

        您可能感兴趣的文章:
        • asp.net大文件上传解决方案实例代码
        • ASP.NET实现文件上传功能
        • ASP.NETCore实现文件上传和下载
        • asp.netcore多文件分块同时上传组件使用详解
        • .NET8实现大文件分片上传的高效方案汇总
        • ASP.NETCore中实现高效的文件上传的示例代码