zoukankan      html  css  js  c++  java
  • [c#源码分享]TCP通信中的大文件传送

    NetworkComms网络通信框架序言

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

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

    ---------------------
    作者:networkcomms
    来源:CSDN
    原文:https://blog.csdn.net/networkcomms/article/details/44217851
    版权声明:本文为博主原创文章,转载请附上博文链接!

  • 相关阅读:
    Now Task
    数据库的死锁及解决
    Java小对象的解决之道——对象池(Object Pool)的设计与应用
    Java中的对象池技术
    npm install时报错 npm ERR!Windows_NT 6.1.7601
    Angular 入门学习
    React 之 Hello world
    react webpack.config.js 入门学习
    React/React Native 的ES5 ES6写法对照表
    ES5和ES6中对于继承的实现方法
  • 原文地址:https://www.cnblogs.com/Jeely/p/10972150.html
Copyright © 2011-2022 走看看