当前位置: 代码迷 >> C# >> [c#源码分享]TCP通信中的大文件传递
  详细解决方案

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

热度:94   发布时间:2016-05-05 04:46:48.0
[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;        }    }}

 

7楼乾卦
可以参考飞鸽传书
6楼『大雪无痕』
推荐+1,,改天我也将自己的通讯 框架开源。
5楼luofer
用ZeroMQ(NetMQ ) so easy!
Re: networkcomms通信框架
@luofer,@『大雪无痕』,@猎风,感谢关注
4楼汉卿
你发的好像都是为了推广你的框架,写些框架的原理岂不是更好。
Re: networkcomms通信框架
@汉卿,您好,感谢关注,这个框架是英国的,我对框架原理也还在学习中
3楼wangyongchao880622
学习了,看着还挺复杂。
2楼风来风往风伤
学习啦。。。灰常给力。
1楼猎风
博主好勤奋啊
  相关解决方案