NetworkComms網絡通信框架序言
文件傳輸在客戶端,服務器端程序的應用是非常廣泛的,穩定的文件傳輸應該可以說是Tcp通訊的核心功能。下面我們來看一下如何基于networkcomms2.3.1來進行文件傳輸。最新的 v3版本做了一些加強,變化不是很大。
使用networkcomms2.3.1框架,您無需考慮粘包等問題,框架已經幫您處理好了。
我們看一下如何發送文件,相關代碼如下:
發送文件:public void StartSendFile() { //聲明一個文件流 FileStream stream = null; try { //FilePath是文件路徑,打開這個文件 //根據選擇的文件創建一個文件流 stream = new FileStream(this.FilePath, FileMode.Open, Fileaccess.Read, FileShare.Read); //包裝成線程安全的數據流 ThreadSafeStream safeStream = new ThreadSafeStream(stream); //獲取不包含路徑信息的文件名 string shortFileName = System.IO.Path.GetFileName(FilePath); //根據參數中設定的值來角色發送的數據包的大小 因為文件很大 可能1個G 2個G 不可以一次性都發送 //每次都只發送一部分 至于每次發送多少 我們創建了一個fileTransOptions類來進行設定 long sendChunkSizeBytes = fileTransOptions.PackageSize; //總的文件大小 this.SizeBytes = stream.Length; long totalBytesSent = 0; //用一個循環方法發送數據,直到發送完成 do { //如果剩下的字節小于上面指定的每次發送的字節,即PackageSize的值,那么發送此次發送的字節數為 剩下的字節數 否則 發送的字節長度為 PackageSize long bytesToSend = (totalBytesSent + sendChunkSizeBytes < stream.Length ? sendChunkSizeBytes : stream.Length - totalBytesSent); //從ThreadSafeStream線程安全流中獲取本次發送的部分 (線程安全流,totalbytesSent 是已發送的數,在此處代表從ThreadSafeStream中截取的文件的開始位置,bytesToSend 代表此次截取的文件的長度) StreamSendWrapper streamWrapper = new StreamSendWrapper(safeStream, totalBytesSent, bytesToSend); //我們希望記錄包的順序號 long packetSequenceNumber; //發送文件的數據部分 并返回此次發送的順序號 這個順序號在下面的發送文件信息時會用到 起到一個對應的作用。 connection.SendObject("PartialFileData", streamWrapper, sendFileOptions, out packetSequenceNumber); //發送上面的文件的數據部分向對應的信息 包括文件ID 文件名 在服務器上存儲的位置 文件的總長度 totalBytesSent是已發送數,在此處用來傳遞給服務器后,服務器用來定位此部分數據存放的位置 進一步合成文件 connection.SendObject("PartialFileDataInfo", new SendInfo(fileID, shortFileName, destFilePath, stream.Length, totalBytesSent, packetSequenceNumber), sendFileOptions); totalBytesSent += bytesToSend; //更新已經發送的字節的屬性 SentBytes += bytesToSend; //觸發一個事件 UI可以依據此事件更新PRogressBar 動態的顯示文件更新的過程 FileTransProgress.Raise(this, new FTProgressEventArgs(FileID, SizeBytes, totalBytesSent)); //每發送一部分文件 都Sleep幾十毫秒,不然cpu會非常高 if (!((this.fileTransOptions.SleepSpan <= 0) || this.canceled)) { Thread.Sleep(this.fileTransOptions.SleepSpan); } } while ((totalBytesSent < stream.Length) && !this.canceled); if (!this.canceled) { //觸發文件傳輸完成事件 UI可以調閱此事件 并彈出窗口報告文件傳輸完成 FileTransCompleted.Raise(this, new FTCompleteEventArgs(fileID)); } else { //觸發文件傳輸中斷事件 FileTransDisruptted.Raise(this, new FTDisruptEventArgs(FileID, FileTransFailReason.Error)); } } catch (CommunicationException ex) { LogTools.LogException(ex, "SendFile.StartSendFile"); FileTransDisruptted.Raise(this, new FTDisruptEventArgs(FileID, FileTransFailReason.Error)); } catch (Exception ex) { LogTools.LogException(ex, "SendFile.StartSendFile"); FileTransDisruptted.Raise(this, new FTDisruptEventArgs(FileID, FileTransFailReason.Error)); } finally { if (stream != null) { stream.Close(); } } }接收文件 首先聲明2個字典類 用來存放接收到的文件 和接收到的文件信息
/// <summary> /// 文件數據緩存 索引是 ConnectionInfo對象 數據包的順序號 值是數據 /// </summary> Dictionary<ConnectionInfo, Dictionary<long, byte[]>> incomingDataCache = new Dictionary<ConnectionInfo, Dictionary<long, byte[]>>(); /// <summary> /// 文件信息數據緩存 索引是 ConnectionInfo對象 數據包的順序號 值是文件信息數據 /// </summary> Dictionary<ConnectionInfo, Dictionary<long, SendInfo>> incomingDataInfoCache = new Dictionary<ConnectionInfo, Dictionary<long, SendInfo>>();
在接收端定義2個相對應的文件接收方法 一個用來接收文件字節部分 一個用來接收文件字節部分對應的信息類
一般在構造函數中聲明
//處理文件數據 NetworkComms.AppendGlobalIncomingPacketHandler<byte[]>("PartialFileData", IncomingPartialFileData); //處理文件信息 NetworkComms.AppendGlobalIncomingPacketHandler<SendInfo>("PartialFileDataInfo", IncomingPartialFileDataInfo);接收文件字節
private void IncomingPartialFileData(PacketHeader header, Connection connection, byte[] data) { try { SendInfo info = null; ReceiveFile file = null; //以線程安全的方式執行操作 lock (syncRoot) { //獲取數據包的順序號 long sequenceNumber = header.GetOption(PacketHeaderLongItems.PacketSequenceNumber); //如果數據信息字典包含 "連接信息" 和 "包順序號" if (incomingDataInfoCache.ContainsKey(connection.ConnectionInfo) && incomingDataInfoCache[connection.ConnectionInfo].ContainsKey(sequenceNumber)) { //根據順序號,獲取相關SendInfo記錄 info = incomingDataInfoCache[connection.ConnectionInfo][sequenceNumber]; //從信息記錄字典中刪除相關記錄 incomingDataInfoCache[connection.ConnectionInfo].Remove(sequenceNumber); //檢查相關連接上的文件是否存在,如果不存在,則添加相關文件{ReceiveFile} if (!recvManager.ContainsFileID(info.FileID)) { recvManager.AddFile(info.FileID, info.Filename, info.FilePath, connection.ConnectionInfo, info.TotalBytes); } file = recvManager.GetFile(info.FileID); } else { //如果不包含順序號,也不包含相關"連接信息",添加相關連接信息 if (!incomingDataCache.ContainsKey(connection.ConnectionInfo)) incomingDataCache.Add(connection.ConnectionInfo, new Dictionary<long, byte[]>()); //在數據字典中添加相關"順序號"的信息 incomingDataCache[connection.ConnectionInfo].Add(sequenceNumber, data); } } if (info != null && file != null && !file.IsCompleted) { file.AddData(info.BytesStart, 0, data.Length, data); file = null; data = null; } else if (info == null ^ file == null) throw new Exception("Either both are null or both are set. Info is " + (info == null ? "null." : "set.") + " File is " + (file == null ? "null." : "set.") + " File is " + (file.IsCompleted ? "completed." : "not completed.")); } catch (Exception ex) { LogTools.LogException(ex, "IncomingPartialFileDataError"); } } private void IncomingPartialFileDataInfo(PacketHeader header, Connection connection, SendInfo info) { try { byte[] data = null; ReceiveFile file = null; //以線程安全的方式執行操作 lock (syncRoot) { //從 SendI
新聞熱點
疑難解答