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

Sunny5816 2015-03-06 01:07:03
源码 (为节省空间,不包含通信框架源码,通信框架源码请另行下载)

文件传送在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");
}
}


...全文
750 7 打赏 收藏 转发到动态 举报
写回复
用AI写文章
7 条回复
切换为时间正序
请发表友善的回复…
发表回复
p471289908 2016-01-07
  • 打赏
  • 举报
回复
用不了啊,缺少两个DLL
程序员鼓励师 2015-03-07
  • 打赏
  • 举报
回复
Sweeet 2015-03-07
  • 打赏
  • 举报
回复
感谢楼主分享
Sunny5816 2015-03-06
  • 打赏
  • 举报
回复
感谢您的关注
l123156 2015-03-06
  • 打赏
  • 举报
回复
感谢楼主分享
john_QQ:2335298917 2015-03-06
  • 打赏
  • 举报
回复
感谢楼主分享
Sunny5816 2015-03-06
  • 打赏
  • 举报
回复
Dictionary<ConnectionInfo, Dictionary<string, ReceivedFile>> receivedFilesDict = new Dictionary<ConnectionInfo, Dictionary<string, ReceivedFile>>();

//临时存放收到的文件字节数据
Dictionary<ConnectionInfo, Dictionary<long, byte[]>> incomingDataCache = new Dictionary<ConnectionInfo, Dictionary<long, byte[]>>();

//临时存放收到的文件信息数据
Dictionary<ConnectionInfo, Dictionary<long, SendInfo>> incomingDataInfoCache = new Dictionary<ConnectionInfo, Dictionary<long, SendInfo>>();

//收发参数
SendReceiveOptions customOptions = new SendReceiveOptions<ProtobufSerializer>();

object syncRoot = new object();

临时存储文件数据用到的字典类

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

using NetworkCommsDotNet;
using System.ComponentModel;
using System.IO;

namespace AppServer
{
/// <summary>
///接收文件
/// </summary>
public class ReceivedFile
{
/// <summary>
/// 文件名称
/// </summary>
public string Filename { get; private set; }

/// <summary>
/// 连接信息列
/// </summary>
public ConnectionInfo SourceInfo { get; private set; }

/// <summary>
/// 文件大小
/// </summary>
public long SizeBytes { get; private set; }

/// <summary>
/// 目前为止 已收到的文件代销
/// </summary>
public long ReceivedBytes { get; private set; }

/// <summary>
/// 完成的百分比
/// </summary>
public double CompletedPercent
{
get { return (double)ReceivedBytes / SizeBytes; }
set { throw new Exception("An attempt to modify readonly value."); }
}

/// <summary>
/// 源信息
/// </summary>
public string SourceInfoStr
{
get { return "[" + SourceInfo.RemoteEndPoint.Address + ":" + SourceInfo.RemoteEndPoint.Port + "]"; }
}

/// <summary>
/// 已经完成
/// </summary>
public bool IsCompleted
{
get { return ReceivedBytes == SizeBytes; }
}

/// <summary>
/// 同步锁 用来保证线程安全
/// </summary>
object SyncRoot = new object();

/// <summary>
/// 内存流 用来创建文件
/// </summary>
Stream data;


/// <summary>
/// 创建一个接收文件类
/// </summary>
/// <param name="filename">文件名称 Filename associated with this file</param>
/// <param name="sourceInfo">文件信息类 ConnectionInfo corresponding with the file source</param>
/// <param name="sizeBytes">文件大小 The total size in bytes of this file</param>
public ReceivedFile(string filename, ConnectionInfo sourceInfo, long sizeBytes)
{
this.Filename = filename;
this.SourceInfo = sourceInfo;
this.SizeBytes = sizeBytes;

//在硬盘上创建一个文件流 使得我们可以接收大文件
data = new FileStream(filename, FileMode.Create, FileAccess.ReadWrite, FileShare.Read, 8 * 1024, FileOptions.None);
}

/// <summary>
/// 把接收到的字节数据添加到当前文件流种
/// </summary>

public void AddData(long dataStart, int bufferStart, int bufferLength, byte[] buffer)
{
lock (SyncRoot)
{
data.Seek(dataStart, SeekOrigin.Begin);
data.Write(buffer, (int)bufferStart, (int)bufferLength);

ReceivedBytes += (int)(bufferLength - bufferStart);

//收到全部数据,保存文件
if (ReceivedBytes == SizeBytes)
{
data.Flush();
data.Close();

SaveFileToDisk(@"d:\" + Filename);
}
}


}

/// <summary>
/// 保存文件
/// </summary>
/// <param name="saveLocation">Location to save file</param>
public void SaveFileToDisk(string saveLocation)
{
if (ReceivedBytes != SizeBytes)
throw new Exception("Attempted to save out file before data is complete.");

if (!File.Exists(Filename))
throw new Exception("The transfered file should have been created within the local application directory. Where has it gone?");

File.Delete(saveLocation);

File.Move(Filename, saveLocation);
}

/// <summary>
///关闭
/// </summary>
public void Close()
{
try
{
data.Dispose();
}
catch (Exception) { }

try
{
data.Close();
}
catch (Exception) { }
}



}
}

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;
}
}
}

110,499

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • Web++
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧