zoukankan      html  css  js  c++  java
  • 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;
            }
        }
    }
    复制代码
     
  • 相关阅读:
    在winform下实现左右布局多窗口界面的方法(一)
    C# 使用API检查域用户名和密码是否正确
    C#检查网络是否可以连接互联网
    总结:实体类和(XML或二进制)之间相互转(序列化和反序列化)
    XML和实体类之间相互转换(序列化和反序列化)
    C# XML反序列化与序列化举例:XmlSerializer
    XML文件与实体类的互相转换
    Message类的属性Msg所关联的消息ID
    C# Message 消息处理
    在.net中读写config文件的各种方法(自定义config节点)
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/4319517.html
Copyright © 2011-2022 走看看