1063256619 发表于 2015-3-13 14:23:41

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

TCP通信中的大文件传送
NetworkComms网络通信框架序言源码   (为节省空间,不包含通信框架源码,通信框架源码请另行下载)文件传送在TCP通信中是经常用到的,本文针对文件传送进行探讨经过测试,可以发送比较大的文件,比如1个G或者2个G本文只对文件传送做了简单的探讨,示例程序可能也不是很成熟,希望本文起到抛砖引玉的作用,有兴趣的朋友帮忙补充完善首先看一下实现的效果服务器端:http://images.cnitblog.com/blog2015/586310/201503/061208295399410.jpg客户端(一次只能发送一个文件):http://images.cnitblog.com/blog2015/586310/201503/061213408996298.jpg服务器端收到的文件,存放到了D盘根目录下(存放的路径可以根据情况修改)本程序基于开源的networkcomms2.3.1通信框架下面来看一下实现的步骤:1、客户端   (1): 先连接服务器:   http://common.cnblogs.com/images/copycode.gif
    //给连接信息对象赋值            connInfo = new ConnectionInfo(txtIP.Text, int.Parse(txtPort.Text));            //如果不成功,会弹出异常信息            newTcpConnection = TCPConnection.GetConnection(connInfo);            TCPConnection.StartListening(connInfo.LocalEndPoint);            button1.Enabled = false;            button1.Text = "连接成功";http://common.cnblogs.com/images/copycode.gif

(2)发送大文件(分段发送)   http://common.cnblogs.com/images/copycode.gif
   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");                }            }                   }http://common.cnblogs.com/images/copycode.gif

2:服务器端接收文件: (1)开始监听
http://common.cnblogs.com/images/copycode.gif
//服务器开始监听客户端的请求             //开始监听某T端口            IPEndPoint thePoint = new IPEndPoint(IPAddress.Parse(txtIP.Text), int.Parse(txtPort.Text));               TCPConnection.StartListening(thePoint, false);            button1.Text = "监听中";            button1.Enabled = false;            //此方法中包含服务器具体的处理方法。            StartListening();http://common.cnblogs.com/images/copycode.gif

(2)添加接收文件处理方法               //处理收到的文件字节数据            NetworkComms.AppendGlobalIncomingPacketHandler<byte[]>("PartialFileData", IncomingPartialFileData);         //处理收到的文件信息数据            NetworkComms.AppendGlobalIncomingPacketHandler<SendInfo>("PartialFileDataInfo", IncomingPartialFileDataInfo);
http://common.cnblogs.com/images/copycode.gif
//处理收到的文件字节数据               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.ContainsKey(sequenceNumber))                  {                                             //如果已经收到此部分 “文件字节数据” 对应的 “文件信息数据”                        info = incomingDataInfoCache;                        incomingDataInfoCache.Remove(sequenceNumber);                         if (!receivedFilesDict.ContainsKey(connection.ConnectionInfo))                            receivedFilesDict.Add(connection.ConnectionInfo, new Dictionary<string, ReceivedFile>());                        //如果当前收到字节数据,还没有对应的ReceivedFile类,则创建一个                        if (!receivedFilesDict.ContainsKey(info.Filename))                        {                            receivedFilesDict.Add(info.Filename, new ReceivedFile(info.Filename, connection.ConnectionInfo, info.TotalBytes));                                                   }                        file = receivedFilesDict;                  }                  else                  {                                                if (!incomingDataCache.ContainsKey(connection.ConnectionInfo))                            incomingDataCache.Add(connection.ConnectionInfo, new Dictionary<long, byte[]>());                        incomingDataCache.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");            }      }http://common.cnblogs.com/images/copycode.gif

http://common.cnblogs.com/images/copycode.gif
   //处理收到的文件信息数据      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.ContainsKey(sequenceNumber))                  {                        //如果当前文件信息类对应的文件字节部分已经存在                        data = incomingDataCache;                        incomingDataCache.Remove(sequenceNumber);                                                if (!receivedFilesDict.ContainsKey(connection.ConnectionInfo))                            receivedFilesDict.Add(connection.ConnectionInfo, new Dictionary<string, ReceivedFile>());                                             if (!receivedFilesDict.ContainsKey(info.Filename))                        {                            receivedFilesDict.Add(info.Filename, new ReceivedFile(info.Filename, connection.ConnectionInfo, info.TotalBytes));                                                    }                        file = receivedFilesDict;                  }                  else                  {                                                if (!incomingDataInfoCache.ContainsKey(connection.ConnectionInfo))                            incomingDataInfoCache.Add(connection.ConnectionInfo, new Dictionary<long, SendInfo>());                        incomingDataInfoCache.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");            }      }http://common.cnblogs.com/images/copycode.gif


http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif 临时存储文件数据用到的字典类
http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif ReceivedFile方法
3.在MessageContract类库中添加SendInfo契约类方法,此方法用于传递文件信息,客户端和服务器端都需要使用http://images.cnitblog.com/blog2015/586310/201503/061227528053961.jpg
http://common.cnblogs.com/images/copycode.gif
using System;using System.Collections.Generic;using System.Linq;using System.Text;using ProtoBuf;namespace MessageContract{    /// <summary>    /// 文件信息类    /// </summary>    public    class SendInfo    {      /// <summary>      /// 文件名称      /// </summary>      1)]      public string Filename { get; private set; }      /// <summary>      /// 文件发送-开始位置      /// </summary>      2)]      public long BytesStart { get; private set; }      /// <summary>      /// 文件大小      /// </summary>      3)]      public long TotalBytes { get; private set; }      /// <summary>      /// 顺序号      /// </summary>      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;      }    }}http://common.cnblogs.com/images/copycode.gif



caisheng1984 发表于 2015-11-5 23:52:13

谢谢分享

御坂崎 发表于 2015-11-7 09:05:57

楼主,很厉害,虽然还没有学到,顶上

seling 发表于 2016-12-26 23:30:40

这个框架之前研究过 还是很不错的了。 楼主辛苦了

美伦美 发表于 2017-1-5 15:24:58

█美伦美(北京)国际贸易有限公司█【北京朝阳区金盏乡雷庄工业区】美仑美国际家居,专营进口欧式家具,美式家具;北京美式家具,北京欧式家具,北京外贸家具,北京仓储家具,北欧实木家具,国内首家万平仓储式家具展厅,整合国内外70余个家具品牌,上万件单品;固定利润模式,北京性价比家具,全国家具品牌-特色家具:实木家具,北京实木家具,北京家具网上商城,打造北京市高性价比家具品牌。
  主营业务
  家具产品风格包括:经典美式、田园、乡村、现代、欧式、新古典、小件精品家具及大量外贸家具尾单产品等。经营范围为精品出口和知名品牌家具的零售和批发,经销产品整合了国内近七十个家具厂家品牌灯饰,地毯,花艺,画品,等
  成立于1993年,前身为国内资深家具进口贸易公司,以代理国外知名家具品牌进口贸易为主要经营产品.在北京拥有万平美式仓储展厅,国际环保标准,整合国内外几十个家具品牌,上万种单品,风格齐全.仓储式折扣直销,让利客户全外贸家具采购,环保承诺,家具,软装,配饰等,真正的一站式选购家具的验。省时,省力,省心,还省钱。

Margin_Lost 发表于 2017-3-13 17:47:06

楼主,牛哦,我来看看,顺便顶上

zhangxiaoheng 发表于 2017-4-30 18:02:48

链接失效了哦,楼主
页: [1]
查看完整版本: [c#源码分享]TCP通信中的大文件传送