Fork me on GitHub
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");
            }
        }
复制代码

 

 临时存储文件数据用到的字典类
 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;
        }
    }
}
复制代码

 

 
posted on 2015-03-06 23:30  HackerVirus  阅读(1642)  评论(0编辑  收藏  举报