[c#源碼分享]TCP通信中的大文件傳送


NetworkComms網絡通信框架序言

源碼   (為節省空間,不包含通信框架源碼,通信框架源碼請另行下載)

文件傳送在TCP通信中是經常用到的,本文針對文件傳送進行探討

經過測試,可以發送比較大的文件,比如1個G或者2個G

本文只對文件傳送做了簡單的探討,示例程序可能也不是很成熟,希望本文起到拋磚引玉的作用,有興趣的朋友幫忙補充完善

首先看一下實現的效果

服務器端:

 

客戶端(一次只能發送一個文件):

 

服務器端收到的文件,存放到了D盤根目錄下(存放的路徑可以根據情況修改)

本程序基於開源的networkcomms2.3.1通信框架

下面來看一下實現的步驟:

1、客戶端

   (1): 先連接服務器:   


//給連接信息對象賦值
connInfo = new ConnectionInfo(txtIP.Text, int.Parse(txtPort.Text));

//如果不成功,會彈出異常信息
newTcpConnection = TCPConnection.GetConnection(connInfo);

TCPConnection.StartListening(connInfo.LocalEndPoint);

button1.Enabled = false;
button1.Text = "連接成功";

(2)發送大文件(分段發送)     


private void SendFileButton_Click(object sender, EventArgs e)
{
//打開對話框,獲取文件
if (openFileDialog1.ShowDialog() == DialogResult.OK)
{
//暫時禁用發送按鈕
sendFileButton.Enabled = false;

//獲取對話框中選擇的文件的名稱
string filename = openFileDialog1.FileName;

//設置進度條顯示為0
UpdateSendProgress(0);

try
{
//創建一個文件流
FileStream stream = new FileStream(filename, FileMode.Open, FileAccess.Read);

//創建一個線程安全流
ThreadSafeStream safeStream = new ThreadSafeStream(stream);

//獲取不包含路徑的文件名稱
string shortFileName = System.IO.Path.GetFileName(filename);

//每次發送的字節數 可根據實際情況進行設定
long sendChunkSizeBytes = 40960;
//已發送的字節數
long totalBytesSent = 0;
do
{
//檢查剩余的字節數 小於 上面指定的字節數 則發送"剩余的字節數" 否則發送"指定的字節數"
long bytesToSend = (totalBytesSent + sendChunkSizeBytes < stream.Length ? sendChunkSizeBytes : stream.Length - totalBytesSent);


//包裝一個ThreadSafeStream 使之可以分段發送
StreamSendWrapper streamWrapper = new StreamSendWrapper(safeStream, totalBytesSent, bytesToSend);

//順序號
long packetSequenceNumber;
//發送指定數據
newTcpConnection.SendObject("PartialFileData", streamWrapper, customOptions, out packetSequenceNumber);
//發送指定的數據相關的信息
newTcpConnection.SendObject("PartialFileDataInfo", new SendInfo(shortFileName, stream.Length, totalBytesSent, packetSequenceNumber), customOptions);

totalBytesSent += bytesToSend;


UpdateSendProgress((double)totalBytesSent / stream.Length);
//兩次發送之間間隔一定時間
System.Threading.Thread.Sleep(30);


} while (totalBytesSent < stream.Length);

 

}
catch (CommunicationException)
{

}
catch (Exception ex)
{

NetworkComms.LogError(ex, "SendFileError");

}

}

}

2:服務器端接收文件:

 (1)開始監聽

   


//服務器開始監聽客戶端的請求
//開始監聽某T端口
IPEndPoint thePoint = new IPEndPoint(IPAddress.Parse(txtIP.Text), int.Parse(txtPort.Text));
TCPConnection.StartListening(thePoint, false);
button1.Text = "監聽中";
button1.Enabled = false;

//此方法中包含服務器具體的處理方法。
StartListening();

(2)添加接收文件處理方法

//處理收到的文件字節數據
NetworkComms.AppendGlobalIncomingPacketHandler<byte[]>("PartialFileData", IncomingPartialFileData);
//處理收到的文件信息數據
NetworkComms.AppendGlobalIncomingPacketHandler<SendInfo>("PartialFileDataInfo", IncomingPartialFileDataInfo);

//處理收到的文件字節數據

private void IncomingPartialFileData(PacketHeader header, Connection connection, byte[] data)
{
try
{
SendInfo info = null;
ReceivedFile file = null;


lock (syncRoot)
{
//獲取順序號
long sequenceNumber = header.GetOption(PacketHeaderLongItems.PacketSequenceNumber);

if (incomingDataInfoCache.ContainsKey(connection.ConnectionInfo) && incomingDataInfoCache[connection.ConnectionInfo].ContainsKey(sequenceNumber))
{

//如果已經收到此部分 “文件字節數據” 對應的 “文件信息數據”
info = incomingDataInfoCache[connection.ConnectionInfo][sequenceNumber];
incomingDataInfoCache[connection.ConnectionInfo].Remove(sequenceNumber);

if (!receivedFilesDict.ContainsKey(connection.ConnectionInfo))
receivedFilesDict.Add(connection.ConnectionInfo, new Dictionary<string, ReceivedFile>());

//如果當前收到字節數據,還沒有對應的ReceivedFile類,則創建一個
if (!receivedFilesDict[connection.ConnectionInfo].ContainsKey(info.Filename))
{
receivedFilesDict[connection.ConnectionInfo].Add(info.Filename, new ReceivedFile(info.Filename, connection.ConnectionInfo, info.TotalBytes));

}

file = receivedFilesDict[connection.ConnectionInfo][info.Filename];
}
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)
{

NetworkComms.LogError(ex, "IncomingPartialFileDataError");
}
}


//處理收到的文件信息數據
private void IncomingPartialFileDataInfo(PacketHeader header, Connection connection, SendInfo info)
{
try
{
byte[] data = null;
ReceivedFile file = null;


lock (syncRoot)
{
//獲取順序號
long sequenceNumber = info.PacketSequenceNumber;

if (incomingDataCache.ContainsKey(connection.ConnectionInfo) && incomingDataCache[connection.ConnectionInfo].ContainsKey(sequenceNumber))
{
//如果當前文件信息類對應的文件字節部分已經存在
data = incomingDataCache[connection.ConnectionInfo][sequenceNumber];
incomingDataCache[connection.ConnectionInfo].Remove(sequenceNumber);


if (!receivedFilesDict.ContainsKey(connection.ConnectionInfo))
receivedFilesDict.Add(connection.ConnectionInfo, new Dictionary<string, ReceivedFile>());


if (!receivedFilesDict[connection.ConnectionInfo].ContainsKey(info.Filename))
{
receivedFilesDict[connection.ConnectionInfo].Add(info.Filename, new ReceivedFile(info.Filename, connection.ConnectionInfo, info.TotalBytes));

}

file = receivedFilesDict[connection.ConnectionInfo][info.Filename];
}
else
{

if (!incomingDataInfoCache.ContainsKey(connection.ConnectionInfo))
incomingDataInfoCache.Add(connection.ConnectionInfo, new Dictionary<long, SendInfo>());

incomingDataInfoCache[connection.ConnectionInfo].Add(sequenceNumber, info);
}
}


if (data != null && file != null && !file.IsCompleted)
{
file.AddData(info.BytesStart, 0, data.Length, data);
file = null;
data = null;

}
else if (data == null ^ file == null)
throw new Exception("Either both are null or both are set. Data is " + (data == null ? "null." : "set.") + " File is " + (file == null ? "null." : "set.") + " File is " + (file.IsCompleted ? "completed." : "not completed."));
}
catch (Exception ex)
{
NetworkComms.LogError(ex, "IncomingPartialFileDataInfo");
}
}

 

 臨時存儲文件數據用到的字典類
 ReceivedFile方法
3.在MessageContract類庫中添加SendInfo契約類方法,此方法用於傳遞文件信息,客戶端和服務器端都需要使用

 

 


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using ProtoBuf;

namespace MessageContract
{
/// <summary>
/// 文件信息類
/// </summary>
[ProtoContract]
public class SendInfo
{
/// <summary>
/// 文件名稱
/// </summary>
[ProtoMember(1)]
public string Filename { get; private set; }

/// <summary>
/// 文件發送-開始位置
/// </summary>
[ProtoMember(2)]
public long BytesStart { get; private set; }

/// <summary>
/// 文件大小
/// </summary>
[ProtoMember(3)]
public long TotalBytes { get; private set; }

/// <summary>
/// 順序號
/// </summary>
[ProtoMember(4)]
public long PacketSequenceNumber { get; private set; }

/// <summary>
/// 私有構造函數 用來反序列化
/// </summary>
private SendInfo() { }

/// <summary>
/// 創建一個新的實例
/// </summary>
/// <param name="filename">文件名稱 Filename corresponding to data</param>
/// <param name="totalBytes">文件大小 Total bytes of the whole ReceivedFile</param>
/// <param name="bytesStart">開始位置 The starting point for the associated data</param>
/// <param name="packetSequenceNumber">順序號 Packet sequence number corresponding to the associated data</param>
public SendInfo(string filename, long totalBytes, long bytesStart, long packetSequenceNumber)
{
this.Filename = filename;
this.TotalBytes = totalBytes;
this.BytesStart = bytesStart;
this.PacketSequenceNumber = packetSequenceNumber;
}
}
}

---------------------
作者:networkcomms
來源:CSDN
原文:https://blog.csdn.net/networkcomms/article/details/44217851
版權聲明:本文為博主原創文章,轉載請附上博文鏈接!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM