找回密码
 立即注册
首页 业界区 业界 .NET 8 gRPC 实现高效100G大文件断点续传工具 ...

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

裸历 2025-6-23 09:18:30
前言

随着数字化和信息化的发展,大文件传输在企业、科研以及个人用户中变得越来越常见。传统的文件传输方式在面对大文件(如几十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# 代码。
安装命令如下:
  1. Install-Package Grpc.Net.Client
  2. Install-Package Google.Protobuf
  3. Install-Package Grpc.Tools
复制代码
项目代码
  1. /// <summary>
  2. /// 初始化数据库表 UploadSessions,用于记录上传会话信息。
  3. /// 如果表不存在,则创建该表。
  4. /// </summary>
  5. private void InitializeDatabase()
  6. {
  7.     using var connection = new SqliteConnection(_connectionString);
  8.     connection.Open();
  9.     var command = connection.CreateCommand();
  10.     command.CommandText = @"
  11.         CREATE TABLE IF NOT EXISTS UploadSessions (
  12.             SessionId TEXT PRIMARY KEY,       -- 会话唯一标识符(GUID)
  13.             FileName TEXT NOT NULL,           -- 文件名
  14.             FileSize INTEGER NOT NULL,        -- 文件总大小(字节)
  15.             FileHash TEXT NOT NULL,           -- 文件哈希值(用于断点续传校验)
  16.             UploadedBytes INTEGER NOT NULL,   -- 已上传字节数(初始为0)
  17.             TempFilePath TEXT NOT NULL,       -- 临时文件路径
  18.             CreatedAt TEXT NOT NULL,          -- 创建时间(UTC格式字符串)
  19.             CompletedAt TEXT                  -- 完成时间(可为空)
  20.         )";
  21.     command.ExecuteNonQuery();
  22. }
  23. /// <summary>
  24. /// 创建一个新的上传会话,并插入数据库中。
  25. /// </summary>
  26. /// <param name="fileName">上传文件的原始名称</param>
  27. /// <param name="fileSize">文件总大小</param>
  28. /// <param name="fileHash">文件的哈希值,用于校验完整性</param>
  29. /// <returns>生成的会话ID</returns>
  30. public string CreateSession(string fileName, long fileSize, string fileHash)
  31. {
  32.     var sessionId = Guid.NewGuid().ToString(); // 生成唯一会话ID
  33.     var tempFilePath = Path.Combine(_tempStoragePath, $"temp_{sessionId}_{Path.GetFileName(fileName)}");
  34.     using var connection = new SqliteConnection(_connectionString);
  35.     connection.Open();
  36.     var command = connection.CreateCommand();
  37.     command.CommandText = @"
  38.         INSERT INTO UploadSessions
  39.         (SessionId, FileName, FileSize, FileHash, UploadedBytes, TempFilePath, CreatedAt)
  40.         VALUES
  41.         (@SessionId, @FileName, @FileSize, @FileHash, 0, @TempFilePath, @CreatedAt)";
  42.     command.Parameters.AddWithValue("@SessionId", sessionId);
  43.     command.Parameters.AddWithValue("@FileName", fileName);
  44.     command.Parameters.AddWithValue("@FileSize", fileSize);
  45.     command.Parameters.AddWithValue("@FileHash", fileHash);
  46.     command.Parameters.AddWithValue("@TempFilePath", tempFilePath);
  47.     command.Parameters.AddWithValue("@CreatedAt", DateTime.UtcNow.ToString("o")); // ISO8601 格式时间
  48.     command.ExecuteNonQuery();
  49.     return sessionId;
  50. }
  51. /// <summary>
  52. /// 根据会话ID获取上传会话的信息。
  53. /// </summary>
  54. /// <param name="sessionId">会话ID</param>
  55. /// <returns>UploadSession 对象,若未找到则返回 null</returns>
  56. public UploadSession GetSession(string sessionId)
  57. {
  58.     using var connection = new SqliteConnection(_connectionString);
  59.     connection.Open();
  60.     var command = connection.CreateCommand();
  61.     command.CommandText = "SELECT * FROM UploadSessions WHERE SessionId = @SessionId";
  62.     command.Parameters.AddWithValue("@SessionId", sessionId);
  63.     using var reader = command.ExecuteReader();
  64.     if (reader.Read())
  65.     {
  66.         return new UploadSession
  67.         {
  68.             SessionId = reader.GetString(0),
  69.             FileName = reader.GetString(1),
  70.             FileSize = reader.GetInt64(2),
  71.             FileHash = reader.GetString(3),
  72.             UploadedBytes = reader.GetInt64(4),
  73.             TempFilePath = reader.GetString(5),
  74.             CreatedAt = DateTime.Parse(reader.GetString(6)),
  75.             CompletedAt = reader.IsDBNull(7) ? null : DateTime.Parse(reader.GetString(7))
  76.         };
  77.     }
  78.     return null;
  79. }
  80. /// <summary>
  81. /// 根据文件名和哈希查找最近的一次上传会话。
  82. /// 主要用于断点续传时查找已有会话。
  83. /// </summary>
  84. /// <param name="fileName">文件名</param>
  85. /// <param name="fileHash">文件哈希值</param>
  86. /// <returns>最近一次的 UploadSession 对象,若未找到则返回 null</returns>
  87. public UploadSession FindSession(string fileName, string fileHash)
  88. {
  89.     using var connection = new SqliteConnection(_connectionString);
  90.     connection.Open();
  91.     var command = connection.CreateCommand();
  92.     command.CommandText = @"
  93.         SELECT * FROM UploadSessions
  94.         WHERE FileName = @FileName AND FileHash = @FileHash
  95.         ORDER BY CreatedAt DESC
  96.         LIMIT 1";
  97.     command.Parameters.AddWithValue("@FileName", fileName);
  98.     command.Parameters.AddWithValue("@FileHash", fileHash);
  99.     using var reader = command.ExecuteReader();
  100.     if (reader.Read())
  101.     {
  102.         return new UploadSession
  103.         {
  104.             SessionId = reader.GetString(0),
  105.             FileName = reader.GetString(1),
  106.             FileSize = reader.GetInt64(2),
  107.             FileHash = reader.GetString(3),
  108.             UploadedBytes = reader.GetInt64(4),
  109.             TempFilePath = reader.GetString(5),
  110.             CreatedAt = DateTime.Parse(reader.GetString(6)),
  111.             CompletedAt = reader.IsDBNull(7) ? null : DateTime.Parse(reader.GetString(7))
  112.         };
  113.     }
  114.     return null;
  115. }
  116. /// <summary>
  117. /// 更新指定会话的已上传字节数。
  118. /// </summary>
  119. /// <param name="sessionId">会话ID</param>
  120. /// <param name="uploadedBytes">当前已上传字节数</param>
  121. public void UpdateSessionProgress(string sessionId, long uploadedBytes)
  122. {
  123.     using var connection = new SqliteConnection(_connectionString);
  124.     connection.Open();
  125.     var command = connection.CreateCommand();
  126.     command.CommandText = @"
  127.         UPDATE UploadSessions
  128.         SET UploadedBytes = @UploadedBytes
  129.         WHERE SessionId = @SessionId";
  130.     command.Parameters.AddWithValue("@SessionId", sessionId);
  131.     command.Parameters.AddWithValue("@UploadedBytes", uploadedBytes);
  132.     command.ExecuteNonQuery();
  133. }
  134. /// <summary>
  135. /// 获取指定会话的已上传字节数。
  136. /// </summary>
  137. /// <param name="sessionId">会话ID</param>
  138. /// <returns>已上传字节数</returns>
  139. public long GetUploadedBytes(string sessionId)
  140. {
  141.     using var connection = new SqliteConnection(_connectionString);
  142.     connection.Open();
  143.     var command = connection.CreateCommand();
  144.     command.CommandText = "SELECT UploadedBytes FROM UploadSessions WHERE SessionId = @SessionId";
  145.     command.Parameters.AddWithValue("@SessionId", sessionId);
  146.     var result = command.ExecuteScalar();
  147.     return result != null ? Convert.ToInt64(result) : 0;
  148. }
  149. /// <summary>
  150. /// 将指定会话标记为已完成。
  151. /// </summary>
  152. /// <param name="sessionId">会话ID</param>
  153. public void CompleteSession(string sessionId)
  154. {
  155.     using var connection = new SqliteConnection(_connectionString);
  156.     connection.Open();
  157.     var command = connection.CreateCommand();
  158.     command.CommandText = @"
  159.         UPDATE UploadSessions
  160.         SET CompletedAt = @CompletedAt
  161.         WHERE SessionId = @SessionId";
  162.     command.Parameters.AddWithValue("@SessionId", sessionId);
  163.     command.Parameters.AddWithValue("@CompletedAt", DateTime.UtcNow.ToString("o"));
  164.     command.ExecuteNonQuery();
  165. }
  166. /// <summary>
  167. /// 终止指定会话并删除临时文件及数据库记录。
  168. /// </summary>
  169. /// <param name="sessionId">会话ID</param>
  170. public void AbortSession(string sessionId)
  171. {
  172.     var session = GetSession(sessionId);
  173.     if (session != null)
  174.     {
  175.         try
  176.         {
  177.             if (File.Exists(session.TempFilePath))
  178.             {
  179.                 File.Delete(session.TempFilePath); // 删除临时文件
  180.             }
  181.         }
  182.         catch
  183.         {
  184.             // 可选:记录日志或处理异常
  185.         }
  186.         using var connection = new SqliteConnection(_connectionString);
  187.         connection.Open();
  188.         var command = connection.CreateCommand();
  189.         command.CommandText = "DELETE FROM UploadSessions WHERE SessionId = @SessionId";
  190.         command.Parameters.AddWithValue("@SessionId", sessionId);
  191.         command.ExecuteNonQuery();
  192.     }
  193. }
  194. /// <summary>
  195. /// 清理过期的上传会话(未完成且超过指定时间)。
  196. /// 同时删除对应的临时文件和数据库记录。
  197. /// </summary>
  198. /// <param name="expirationTime">会话的过期时间跨度</param>
  199. public void CleanupExpiredSessions(TimeSpan expirationTime)
  200. {
  201.     var cutoff = DateTime.UtcNow - expirationTime;
  202.     using var connection = new SqliteConnection(_connectionString);
  203.     connection.Open();
  204.     // 首先查询所有过期会话
  205.     var selectCommand = connection.CreateCommand();
  206.     selectCommand.CommandText = @"
  207.         SELECT SessionId, TempFilePath FROM UploadSessions
  208.         WHERE CreatedAt < @Cutoff AND (CompletedAt IS NULL OR CompletedAt < @Cutoff)";
  209.     selectCommand.Parameters.AddWithValue("@Cutoff", cutoff.ToString("o"));
  210.     var sessionsToDelete = new List<(string SessionId, string TempFilePath)>();
  211.     using (var reader = selectCommand.ExecuteReader())
  212.     {
  213.         while (reader.Read())
  214.         {
  215.             sessionsToDelete.Add((reader.GetString(0), reader.GetString(1)));
  216.         }
  217.     }
  218.     // 然后依次删除临时文件和数据库记录
  219.     foreach (var (sessionId, tempFilePath) in sessionsToDelete)
  220.     {
  221.         try
  222.         {
  223.             if (File.Exists(tempFilePath))
  224.             {
  225.                 File.Delete(tempFilePath);
  226.             }
  227.         }
  228.         catch
  229.         {
  230.             // 可选:记录日志或处理异常
  231.         }
  232.         var deleteCommand = connection.CreateCommand();
  233.         deleteCommand.CommandText = "DELETE FROM UploadSessions WHERE SessionId = @SessionId";
  234.         deleteCommand.Parameters.AddWithValue("@SessionId", sessionId);
  235.         deleteCommand.ExecuteNonQuery();
  236.     }
  237. }
复制代码
项目效果

1.png

项目源码

Gitee:https://gitee.com/sujimin/gRPC
总结

以上仅展示了大文件断点续传工具的部分功能。更多实用特性和详细信息,请大家访问项目源码。
希望通过本文能为.NET 在大文件断点续传工具开发方面提供有价值的参考。感谢您阅读本篇文章,欢迎在评论区留言交流,分享您的宝贵经验和建议。
关键词:WinForm、gRPC、大文件传输、断点续传、分块上传、Protocol Buffers、SQLite、ASP.NET Core、MD5校验、SHA1校验、.NET 8、文件完整性验证、传输日志、开源项目
最后

如果你觉得这篇文章对你有帮助,不妨点个赞支持一下!你的支持是我继续分享知识的动力。如果有任何疑问或需要进一步的帮助,欢迎随时留言。
也可以加入微信公众号[DotNet技术匠] 社区,与其他热爱技术的同行一起交流心得,共同成长!
2.png


来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册