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
版权声明:本文为博主原创文章,转载请附上博文链接!

[c#源码分享]TCP通信中的大文件传送的更多相关文章

  1. TCP通信中的大文件传送

    TCP通信中的大文件传送 源码   (为节省空间,不包含通信框架源码,通信框架源码请另行下载) 文件传送在TCP通信中是经常用到的,本文针对文件传送进行探讨 经过测试,可以发送比较大的文件,比如1个G ...

  2. 【腾讯Bugly干货分享】深入源码探索 ReactNative 通信机制

    Bugly 技术干货系列内容主要涉及移动开发方向,是由 Bugly 邀请腾讯内部各位技术大咖,通过日常工作经验的总结以及感悟撰写而成,内容均属原创,转载请标明出处. 本文从源码角度剖析 RNA 中 J ...

  3. TCP/IP以及Socket聊天室带类库源码分享

    TCP/IP以及Socket聊天室带类库源码分享 最近遇到个设备,需要去和客户的软件做一个网络通信交互,一般的我们的上位机都是作为客户端来和设备通信的,这次要作为服务端来监听客户端,在这个背景下,我查 ...

  4. 3D语音天气球(源码分享)——在Unity中使用Android语音服务

    转载请注明本文出自大苞米的博客(http://blog.csdn.net/a396901990),谢谢支持! 开篇废话: 这个项目准备分四部分介绍: 一:创建可旋转的"3D球":3 ...

  5. TCP/IP源码(59)——TCP中的三个接收队列

    http://blog.chinaunix.net/uid-23629988-id-3482647.html TCP/IP源码(59)——TCP中的三个接收队列  作者:gfree.wind@gmai ...

  6. 3D语音天气球(源码分享)——完结篇

    转载请注明本文出自大苞米的博客(http://blog.csdn.net/a396901990),谢谢支持! 开篇废话: 由于这篇文章是本系列最后一篇,有必要进行简单的回顾和思路整理. 这个程序是由两 ...

  7. [DeviceOne开发]-土地销售项目源码分享

    一.简介 这个是一个真实项目开源,虽然不是很花哨,但是中规中矩,小细节处理的也很好,非常值得参考和借鉴.里面的数据都缓存到本地,可以离线运行,但是调整一下代码,马上就可以和服务端完全对接.后续会有详细 ...

  8. angular源码分析:angular中的依赖注入式如何实现的

    一.准备 angular的源码一份,我这里使用的是v1.4.7.源码的获取,请参考我另一篇博文:angular源码分析:angular源代码的获取与编译环境安装 二.什么是依赖注入 据我所知,依赖注入 ...

  9. WP8.1&Win10幸运大转盘源码分享

    先AD一下我的群:Win10开发者群:53078485 最近在写一个APP,其中需要一个转盘动画的源码,找了很多但是都没有找到,无奈只好自己来写,写完效果自己还是比较满意的,分享出来,有需要的童鞋可以 ...

随机推荐

  1. Get The Treasury【HDU-3642】【扫描线】

    题目链接 题目给出的是N个体积块,问的是有多少体积重叠了3次及以上? 那么就是怎么处理体积这样子的问题了,看到Z的种类不多的时候,就想着从Z离散化的角度去考虑这个问题了,然后就是怎样子去处理面积了,这 ...

  2. 2019 SCUT SE 新生训练第四波 L - Boxes in a Line——双向链表

    先上一波题目 https://vjudge.net/contest/338760#problem/L 这道题我们维护一个双向链表 操作1 2 3 都是双向链表的基本操作 4操作考虑到手动将链表反转时间 ...

  3. python基本数据类型集合set操作

    转:https://www.cnblogs.com/tina-python/p/5468495.html 一.集合的定义 set集合,是一个无序且不重复的元素集合. 集合对象是一组无序排列的可哈希的值 ...

  4. C++中的面向对象(一)

    1,本节课开始进入 C++ 中的面向对象,面向对象是 C++ 中最核心也是体现 C++ 价   值的一个部分: 2,日常生活当中我们都习惯对事物进行分类,那么这种分类的思想是否可以引入到 程序设计中? ...

  5. ELK-7.3安装部署

    原文 ELK-7.3安装部署 前沿 1.什么是ELK? ELK是由Elasticsearch.Logstash.Kibana 三个开源软件的组成的一个组合体 不懂自行查阅 https://www.el ...

  6. Purfer序列

    我们经常干的一件事是把数变为关于图的问题来解决,那么久了未免不会有这个疑问:能不能把图变成数来解决问题? 所以有了这个purfer数列. 介绍一下这个数列有什么用(或者说有什么性质): 能够将一棵无根 ...

  7. PascalCase & camelCase & kebabCase

    帕斯卡拼写法( 也叫大骆驼拼写法),一种计算机编程中的变量命名方法.它主要的特点是将描述变量作用所有单词的首字母大写,然后直接连接起来,单词之间没有连接符.比如: Age LastName Winte ...

  8. 关于 ioctl 函数

    ioctl函数是用于控制的设备的接口 1.底层: long (*unlocked_ioctl) (struct file *filp, unsigned int cmd, unsigned long ...

  9. go语言从例子开始之Example11.range遍历

    range 迭代各种各样的数据结构.让我们来看看如何在我们已经学过的数据结构上使用 rang 吧. package main import "fmt" func main() { ...

  10. Vue-cli使用prerender-spa-plugin插件预渲染和配置cdn

    参考:https://www.jianshu.com/p/6a4c0b281e7f 使用vue-cli打包项目一般为spa项目,众所周知单页面应用不利于SEO,有ssr和预渲染两种解决方案,这里我们只 ...