.NET8 gRPC實現(xiàn)高效100G大文件斷點續(xù)傳工具
前言
隨著數(shù)字化和信息化的發(fā)展,大文件傳輸在企業(yè)、科研以及個人用戶中變得越來越常見。傳統(tǒng)的文件傳輸方式在面對大文件(如幾十GB甚至上百GB的視頻、工程數(shù)據(jù))時,常常因網(wǎng)絡(luò)不穩(wěn)定、程序崩潰等原因?qū)е聜鬏斒?,而重新上傳又浪費大量時間和帶寬資源。
為了解決這一問題,本文推薦一個基于WinForm 和 .NET gRPC 技術(shù)實現(xiàn)的大文件斷點續(xù)傳工具。該工具不僅支持最大100GB文件的高效傳輸,還具備在網(wǎng)絡(luò)中斷后從中斷點繼續(xù)傳輸?shù)哪芰Γ蟠筇岣吡藗鬏斝逝c穩(wěn)定性。
項目介紹
項目是一個面向桌面端用戶的 大文件斷點續(xù)傳工具,采用 WinForm 構(gòu)建前端界面,使用 ASP.NET Core gRPC 實現(xiàn)后端服務(wù)通信。
其核心目標是提供一種輕量級、可靠且易于擴展的文件傳輸解決方案,適用于需要頻繁進行大文件上傳的企業(yè)或開發(fā)人員。
該項目不依賴復(fù)雜的第三方組件,完全基于.NET 生態(tài)構(gòu)建,具有良好的跨平臺潛力和可維護性。
項目功能
核心功能
- 大文件支持:支持最大100GB的單個文件上傳。
- 斷點續(xù)傳機制:在網(wǎng)絡(luò)中斷或客戶端異常退出后,能夠從中斷位置繼續(xù)上傳。
- 分塊傳輸策略:將大文件切分為多個小塊進行傳輸,提升傳輸穩(wěn)定性和并發(fā)處理能力。
- 實時進度顯示:在界面上動態(tài)展示當前上傳進度、速度及剩余時間。
- 傳輸管理控制:支持暫停、繼續(xù)、取消等操作,增強用戶體驗。
附加功能
- 文件校驗機制:通過MD5或SHA1算法驗證上傳前后文件的一致性,確保數(shù)據(jù)完整性。
- 傳輸日志記錄:自動記錄每次上傳的日志信息,便于追蹤和故障排查。
- 本地狀態(tài)持久化:使用SQLite數(shù)據(jù)庫保存?zhèn)鬏敔顟B(tài),保障斷點信息不丟失。
項目特點
- 技術(shù)先進:采用最新的 .NET 8 框架,結(jié)合 gRPC 協(xié)議,實現(xiàn)了高性能的遠程調(diào)用和流式傳輸。
- 架構(gòu)清晰:前后端分離設(shè)計,前端負責(zé)交互,后端專注業(yè)務(wù)邏輯與數(shù)據(jù)傳輸,便于后期擴展。
- 協(xié)議高效:基于 HTTP/2 的 gRPC 協(xié)議,具備低延遲、高吞吐量的優(yōu)勢,非常適合大文件流式上傳。
- 本地狀態(tài)管理:使用 SQLite 存儲上傳狀態(tài),實現(xiàn)斷點信息的持久化。
- 序列化統(tǒng)一:采用 Protocol Buffers (Protobuf) 進行數(shù)據(jù)結(jié)構(gòu)定義和序列化,保證數(shù)據(jù)傳輸?shù)陌踩c高效。
項目技術(shù)
本項目從前端到后端完整地構(gòu)建了一個基于 WinForm 和 gRPC 的大文件傳輸系統(tǒng)。
以下是關(guān)鍵技術(shù)棧和實現(xiàn)要點:
前端技術(shù)
使用 WinForm (.NET 8) 開發(fā)圖形用戶界面;
支持多線程處理上傳任務(wù),避免界面卡頓;
集成進度條控件和日志輸出模塊,提升交互體驗。
后端通信
基于 ASP.NET Core gRPC (.NET 8) 構(gòu)建服務(wù)端接口;
定義 .proto
文件描述文件上傳的數(shù)據(jù)結(jié)構(gòu)和服務(wù)方法;
利用 gRPC 的雙向流特性 實現(xiàn)大文件的分塊上傳和實時響應(yīng)。
數(shù)據(jù)處理
使用 Google.Protobuf 庫完成 Protobuf 數(shù)據(jù)的序列化與反序列化;
文件分塊上傳過程中,每一塊都攜帶偏移量和標識符,用于服務(wù)器端拼接和斷點恢復(fù);
使用 MD5 / SHA1 對原始文件與接收后的文件進行哈希比對,確保一致性。
本地存儲
使用 SQLite 數(shù)據(jù)庫存儲每個上傳任務(wù)的狀態(tài)信息,包括已上傳大小、文件路徑、服務(wù)器地址等;
在應(yīng)用重啟或網(wǎng)絡(luò)中斷后,讀取本地記錄恢復(fù)上傳上下文。
NuGet 包依賴
Grpc.Net.Client
:用于構(gòu)建 gRPC 客戶端連接;Google.Protobuf
:提供 Protobuf 數(shù)據(jù)模型支持;
Grpc.Tools
:編譯 .proto
文件生成 C# 代碼。
安裝命令如下:
Install-Package Grpc.Net.Client Install-Package Google.Protobuf Install-Package Grpc.Tools
項目代碼
/// <summary> /// 初始化數(shù)據(jù)庫表 UploadSessions,用于記錄上傳會話信息。 /// 如果表不存在,則創(chuàng)建該表。 /// </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, -- 文件總大小(字節(jié)) FileHash TEXT NOT NULL, -- 文件哈希值(用于斷點續(xù)傳校驗) UploadedBytes INTEGER NOT NULL, -- 已上傳字節(jié)數(shù)(初始為0) TempFilePath TEXT NOT NULL, -- 臨時文件路徑 CreatedAt TEXT NOT NULL, -- 創(chuàng)建時間(UTC格式字符串) CompletedAt TEXT -- 完成時間(可為空) )"; command.ExecuteNonQuery(); } /// <summary> /// 創(chuàng)建一個新的上傳會話,并插入數(shù)據(jù)庫中。 /// </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> /// 根據(jù)會話ID獲取上傳會話的信息。 /// </summary> /// <param name="sessionId">會話ID</param> /// <returns>UploadSession 對象,若未找到則返回 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> /// 根據(jù)文件名和哈希查找最近的一次上傳會話。 /// 主要用于斷點續(xù)傳時查找已有會話。 /// </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> /// 更新指定會話的已上傳字節(jié)數(shù)。 /// </summary> /// <param name="sessionId">會話ID</param> /// <param name="uploadedBytes">當前已上傳字節(jié)數(shù)</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> /// 獲取指定會話的已上傳字節(jié)數(shù)。 /// </summary> /// <param name="sessionId">會話ID</param> /// <returns>已上傳字節(jié)數(shù)</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> /// 終止指定會話并刪除臨時文件及數(shù)據(jù)庫記錄。 /// </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> /// 清理過期的上傳會話(未完成且超過指定時間)。 /// 同時刪除對應(yīng)的臨時文件和數(shù)據(jù)庫記錄。 /// </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))); } } // 然后依次刪除臨時文件和數(shù)據(jù)庫記錄 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(); } }
項目效果
到此這篇關(guān)于.NET8 gRPC實現(xiàn)高效100G大文件斷點續(xù)傳工具 的文章就介紹到這了,更多相關(guān).NET大文件斷點續(xù)傳內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
ASP.NET中利用Segments取得URL的文件名的一種方法分享
在ASP.NET中,取得請求頁的URL地址有多種方式,其中有一種方式取得網(wǎng)頁文件名。2011-09-09ASP.NET MVC阿里大于短信接口開發(fā)短信群發(fā)能
這篇文章主要為大家詳細介紹了ASP.NET MVC阿里大于短信接口來開發(fā)例會短信群發(fā)能,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-10-10.NET IoC模式依賴反轉(zhuǎn)(DIP)、控制反轉(zhuǎn)(Ioc)、依賴注入(DI)
這篇文章主要介紹了.NET IoC模式依賴反轉(zhuǎn)(DIP)、控制反轉(zhuǎn)(Ioc)、依賴注入(DI),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-06-06Asp.net 中mvc 實現(xiàn)超時彈窗后跳轉(zhuǎn)功能
這篇文章主要介紹了Asp.net 中mvc 實現(xiàn)超時彈窗后跳轉(zhuǎn)功能,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2017-02-02理解ASP.NET?Core?錯誤處理機制(Handle?Errors)
這篇文章主要介紹了理解ASP.NET?Core?錯誤處理(Handle?Errors)?,在這里需要注意的是,與“異常處理”有關(guān)的中間件,一定要盡早添加,這樣,它可以最大限度的捕獲后續(xù)中間件拋出的未處理異常。感興趣的朋友跟隨小編一起看看吧2021-11-11