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

源码   (为节省空间,不包含通信框架源码,通信框架源码请另行下载)

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

        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;
        }
    }
}
时间: 2024-11-06 09:33:10

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

[c#源码分享]客户端程序通过TCP通信传送&quot;小文件&quot;到服务器

源码  (不包含通信框架源码,通信框架源码请另行下载) 上一篇文章写了如何通过TCP通信发送图片到客户端,有朋友问如何传送文件,本文将就如何发送文件进行探讨. 对于比较小的文件,可以把文件转化成字节形式,用契约类包装一下,服务器收到后,再把字节转化成文件即可,这也是本文中实现的方式,这种方式的优点是比较简单灵活,缺点是不适合大文件的发送,也不能显示文件发送的进度. 基于TCP的通信机制,对于比较大的文件,这种方式是不可行的,大文件采用分段发送再合成的方式比较好,以后有时间再对如何发送大文件单独探

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

Bugly 技术干货系列内容主要涉及移动开发方向,是由 Bugly 邀请腾讯内部各位技术大咖,通过日常工作经验的总结以及感悟撰写而成,内容均属原创,转载请标明出处. 本文从源码角度剖析 RNA 中 Java <> Js 的通信机制(基于最新的 RNA Release 20). 对于传统 Java<>Js 通信而言,Js 调用 Java 通不外乎 Jsbridge.onprompt.log 及 addjavascriptinterface 四种方式,在 Java 调用 Js 只有 l

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

开篇废话: 由于这篇文章是本系列最后一篇,有必要进行简单的回顾和思路整理. 这个程序是由两部分组成,Android端和Unity端: 1.Unity端负责3D球的创建,显示和旋转:3D语音天气球(源码分享)--创建可旋转的3D球 2.通过天气服务动态创建3D球:3D语音天气球(源码分享)--通过天气服务动态创建3D球 3.Android端使用第三方的语音服务来进行语音识别:3D语音天气球(源码分享)--在Unity中使用Android语音服务 4.Unity中加入Android项目:Unity中

AppCan 移动应用开发项目源码分享:嗡嗡旅游App开发

开发者介绍:老家湖北巴东好山好水,神农溪.巴人河.三里城等都是旅游好去处.中秋节回了趟老家,看到家乡的原生态景色吸引了大批游客,由此萌发了想法:用移动技术开发一个App试水,把家乡景点介绍给更多的人.于是,耗时一个月的<嗡嗡旅游>应运而生,特此将项目源码分享给广大AppCan开发者. 项目实现功能 用户注册.登录,游记查看和发布,查看辖区内景区.酒店.交通.攻略等信息,内容收藏.评论和分享,查看地图,景区门票.酒店电话预定等. 项目使用插件 引导页 引导页3张图片采用的是全屏显示的slider

3D语音天气球(源码分享)——通过天气服务动态创建3D球

转载请注明本文出自大苞米的博客(http://blog.csdn.net/a396901990),谢谢支持! 开篇废话: 这个项目准备分四部分介绍: 一:创建可旋转的"3D球":3D语音天气球(源码分享)--创建可旋转的3D球 二:通过天气服务,从网络获取时实天气信息并动态生成"3D球" 三:Android语音服务和Unity的消息传递 四:Unity3D端和Android端的结合 关于项目的详细介绍和3D球的创建请看上面第一篇文章(重要) 今天主要讲解如何通过获取

Android项目源码分享

http://blog.csdn.net/gao_chun/article/details/47263063 Android项目源码分享 给大家分享几个Android开发项目源码,大部分功能相信可以在实战项目中直接使用,供大家下载学习,大部分项目是基于 Android Studio开发,IDE为Eclipse的童鞋可通过网上教程自行转换,这里就不多说了.有句话说,不贴墙纸的装修都是耍流氓,无源码无效果图的文章也算是耍流氓, ,那就直接上图吧.最近在整理GitHub,打算把一些以前做过的项目中部分

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

一.简介 这个是一个真实项目开源,虽然不是很花哨,但是中规中矩,小细节处理的也很好,非常值得参考和借鉴.里面的数据都缓存到本地,可以离线运行,但是调整一下代码,马上就可以和服务端完全对接.后续会有详细的文档介绍这个App. 二.效果图 三.源码分享 https://github.com/do-project/land_sell 四.讨论地址 http://bbs.deviceone.net/forum.php?mod=viewthread&tid=807&extra=page%3D1 五.

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

先AD一下我的群:Win10开发者群:53078485 最近在写一个APP,其中需要一个转盘动画的源码,找了很多但是都没有找到,无奈只好自己来写,写完效果自己还是比较满意的,分享出来,有需要的童鞋可以拿去用. 先来一张效果图: 功能: 1.五个奖项的中奖几率可以自由调整. 2.用户的金币信息云端存储. 3.每天可以免费抽奖一次. 4.金币信息自由定义. 5.其他,请自行挖掘. 需要准备的: A.图片素材:demo包里有,可以自行提取出来使用,我这个转盘感觉不是特别圆润,也可以自己作图来替换掉.

boost.asio源码剖析(四) ---- asio中的泛型概念(concepts)

* Protocol(通信协议) Protocol,是asio在网络编程方面最重要的一个concept.在第一章中的levelX类图中可以看到,所有提供网络相关功能的服务和I/O对象都需要Protocol来确定一些细节. Protocol的约束摘要如下: 1 class protocol 2 { 3 public: 4 /// Obtain an identifier for the type of the protocol. 5 int type() const; 6 7 /// Obtain