使用TCP分块传输文件时是否也要接收端对每块数据的确认

iloli 2014-01-28 11:23:24
由于本人第一次做这种大文件1M以上的网络传输,在实践中发现发送端将一个4M的文件分块(64K)发送时,接收端如果以8K以下的块接收是正常的,而以大于8K的块来接收就会造成接收到的文件的最后会有部分数据缺失的情况。而且接收的缓存块越大于8K 则最后的数据缺失得越严重。

不是太了解NetworkStream的传输机制,但我知道有一个NetworkStream的接收或发送缓存, 这个是不是会影响到接收代码的逻辑?比如当接收端接收不过来的时候 会造成NetworkStream缓存被发送端填满,而如果没有握手机制,发送端不管这个 会继续发送,而接收端的缓存满了以后 ,就会把这些新发过来的数据直接丢掉呢?

如果是这样,那是不是即使是使用TCP协议传输文件,分块的时候也必须在接收端的代码中手动发回对每个块的确认信息给发送端,然后发送端再发下一个块的数据呢?


有经验做过这方面东西的朋友能不能给一个比较健壮的发送接收的具体详细点的过程逻辑给我。。
...全文
639 27 打赏 收藏 转发到动态 举报
写回复
用AI写文章
27 条回复
切换为时间正序
请发表友善的回复…
发表回复
qldsrx 2014-02-08
  • 打赏
  • 举报
回复
pt是什么,从头到尾没看到定义,pt.ReceiveLength做了什么?ReceiveByte的存在有何意义?你给的代码都没有连续性可言,残缺不可分析。 就你给的现象分析,外加ReceiveByte函数的实现,可以猜想,你接收数据是一次性而不是循环接收,接收了一次8K字节后,就认为结束当前的数据传输了,客户端主动断开了数据的接收,导致接收不完整。
qldsrx 2014-02-08
  • 打赏
  • 举报
回复
你倒底有没有认真看MSDN上有关Read方法的说明了?写的明明白白的,返回值才是实际接受的字节数,而不是看你给定的数组大小。要不你最后一次接收数据,只发送了4个字节,但是缓冲区是64K,难道4字节后面的内容都是有效的?如果你这么设计框架的话,早被人骂死了,缓冲区和实际接收数是两个不同的概念。
iloli 2014-02-08
  • 打赏
  • 举报
回复
引用 23 楼 ysd_xwl 的回复:
一方面分块,另一方面要定义一个数据传输格式,比如:|标识|包长度|压缩标志|数据类型|数据
这个跟格式没关系吧。我是在TCP传数据的时候缺少数据。
iloli 2014-02-08
  • 打赏
  • 举报
回复
引用 22 楼 qldsrx 的回复:
你这个封装类有问题: 一、滥用异步方法,如果使用异步方法,就不该轮询那个IsCompleted属性进行等待,而是直接在回调函数中做相关处理,此处你是要封装一个同步方法,因此应该直接用Read而不是BeginRead去接收数据,而且在同步方法中,可以设置ReadTimeout来控制接收超时,那个属性对异步方法无效。 二、同步方法的Read和异步方法的EndRead都会返回实际接收的字节数,你却无视了那个返回值,以为一定能接收你给定数组大小的字节内容,这是不可能的。也许你设置的缓冲区是64K,但是调用EndRead完成接收时才填充了8K字节,那也算是一次完整的接收,需要按照实际接收到的字节数处理。
我全改为同步方法发送和接收了,问题依旧。
iloli 2014-02-08
  • 打赏
  • 举报
回复
引用 22 楼 qldsrx 的回复:
你这个封装类有问题: 一、滥用异步方法,如果使用异步方法,就不该轮询那个IsCompleted属性进行等待,而是直接在回调函数中做相关处理,此处你是要封装一个同步方法,因此应该直接用Read而不是BeginRead去接收数据,而且在同步方法中,可以设置ReadTimeout来控制接收超时,那个属性对异步方法无效。 二、同步方法的Read和异步方法的EndRead都会返回实际接收的字节数,你却无视了那个返回值,以为一定能接收你给定数组大小的字节内容,这是不可能的。也许你设置的缓冲区是64K,但是调用EndRead完成接收时才填充了8K字节,那也算是一次完整的接收,需要按照实际接收到的字节数处理。
对,的确是在接收的时候会出现接收到的数据长度没有填满接收缓存数组的情况。这个问题不是说在TCP协议中不存在吗? 还有,我参照很多示例和书,都是使用IsCompleted属性进行轮询等待异步完成的,我在小文件传输的时候一切都是好的,只有在大文件传输的时候,且接收缓存设得比较大,如大于8K才会有数据缺少的情况。这个问题主要是由于什么造成的呢?还有就是我怎么样在不改变异步接收方法的前提下实现完整,准确接收所有数据? 还有一点:他传大文件时 如果缓存数组一但设置得过大就出会问题,而且仅仅是文件尾部数据缺失,缺失数据的数量长度不固定,并不存在中间缺失数据的情况。
ysd_xwl 2014-02-08
  • 打赏
  • 举报
回复
一方面分块,另一方面要定义一个数据传输格式,比如:|标识|包长度|压缩标志|数据类型|数据
qldsrx 2014-02-08
  • 打赏
  • 举报
回复
你这个封装类有问题: 一、滥用异步方法,如果使用异步方法,就不该轮询那个IsCompleted属性进行等待,而是直接在回调函数中做相关处理,此处你是要封装一个同步方法,因此应该直接用Read而不是BeginRead去接收数据,而且在同步方法中,可以设置ReadTimeout来控制接收超时,那个属性对异步方法无效。 二、同步方法的Read和异步方法的EndRead都会返回实际接收的字节数,你却无视了那个返回值,以为一定能接收你给定数组大小的字节内容,这是不可能的。也许你设置的缓冲区是64K,但是调用EndRead完成接收时才填充了8K字节,那也算是一次完整的接收,需要按照实际接收到的字节数处理。
iloli 2014-02-08
  • 打赏
  • 举报
回复
引用 20 楼 qldsrx 的回复:
pt是什么,从头到尾没看到定义,pt.ReceiveLength做了什么?ReceiveByte的存在有何意义?你给的代码都没有连续性可言,残缺不可分析。 就你给的现象分析,外加ReceiveByte函数的实现,可以猜想,你接收数据是一次性而不是循环接收,接收了一次8K字节后,就认为结束当前的数据传输了,客户端主动断开了数据的接收,导致接收不完整。
pt 是对接收的一个封装类。循环是在外面类来进行的,pt要做的只是完成接收或发送一次数据。 PT类文件如下:

using System;
using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;
using System.Threading;
using System.IO;

namespace CheckUpAccount
{
    /// <summary>
    /// 功    能:
    ///            数据报文收发器 
    /// 描    述:
    ///            只对数据报文提供接收,发送功能
    ///             
    ///
    /// </summary>
    public class PacketTransmitter
    {
        private NetworkStream ns;
        private int sleepMilliseconds;
        private int timeOutSeconds;
        private int sleepCount;
        private TcpClient client;
        private Encoding encoding;

        public PacketTransmitter(TcpClient client, Encoding encoding, int sleepMilliseconds, int timeOutSeconds)
        {
            this.encoding = encoding;
            this.ns = client.GetStream();
            this.client = client;
            this.sleepMilliseconds = sleepMilliseconds;
            this.timeOutSeconds = timeOutSeconds;
            this.sleepCount = timeOutSeconds * 1000 / sleepMilliseconds;

            
        }


        #region 数据的接收方法Receive

        /// <summary>
        /// 异步接收一个完整数据包  包格式为头部指定字节指示报文全长。一般为4
        /// </summary>
        /// <param name="headLength">包头的长度</param>
        /// <returns></returns>
        public byte[] ReceiveOnePacket(int headLength)
        {
            byte[] headByte = new byte[headLength]; //包头
            byte[] bodyByte = null;                 //包体
            byte[] packetByte = null;               //全包

            try
            {
                ReceiveByte(headByte);          //1、接收包头
                bodyByte = new byte[int.Parse(encoding.GetString(headByte)) - headLength];
                ReceiveByte(bodyByte);          //2、接收包体
                if (headByte == null || bodyByte == null)
                {
                    return null;
                }

                packetByte = new byte[headByte.Length + bodyByte.Length];   //3、还原全包
                headByte.CopyTo(packetByte, 0);
                bodyByte.CopyTo(packetByte, headByte.Length);

            }
            catch (Exception)
            {
                return null;
            }

            return packetByte;


        }

        /// <summary>
        /// 根据指定长度,从网络流中接收相应的数据。
        /// </summary>
        /// <param name="ns">要读取的网络流</param>
        /// <param name="length">要读取的字节长度</param>
        /// <returns></returns>
        public byte[] ReceiveLength(int length)
        {
            byte[] recByte = new byte[length];
            ReceiveByte(recByte);
            return recByte;
        }

        /// <summary>
        /// 这是收数据的基方式,不对外公开。根据接收字节数组的元素长度从网络流中读取相应长度的数据到数组中。如果发生错误接收数组为NULL
        /// </summary>
        /// <param name="ns">要读取的网络流</param>
        /// <param name="receiveByte">待接收的字节数组</param>
        private void ReceiveByte(byte[] receiveByte)
        {
            IAsyncResult iar = ns.BeginRead(receiveByte, 0, receiveByte.Length, null, null);
            int count = sleepCount;
            while (!iar.IsCompleted)
            {
                //if (count <= 0)
                //{
                //    receiveByte = null;
                //    return;
                //}
                Thread.Sleep(sleepMilliseconds);
                //count--;
            }
            try
            {
                ns.EndRead(iar);
                return;
            }
            catch (Exception)
            {
                receiveByte = null;
                return;
            }



        }

        //public byte[] ReceiveByteFragment(int bufferSize)
        //{



        //    return null;
 
        //}
       
        #endregion

        #region 数据的发送方法Send
        /// <summary>
        /// 发送数据
        /// </summary>
        /// <param name="sendByte">要发送的数据字节数组</param>
        /// <returns></returns>
        public bool Send(byte[] sendByte)
        {
            IAsyncResult iar = ns.BeginWrite(sendByte, 0, sendByte.Length, null, null);
            int count = sleepCount;
            while (!iar.IsCompleted)
            {
                //if (count <= 0)
                //{
                //    return false;
                //}
                Thread.Sleep(sleepMilliseconds);
                //count--;
            }
            try
            {
                ns.Flush();
                ns.EndWrite(iar);
                
                return true;
            }
            catch (Exception)
            {
                return false;
            }

   
        }
        
        /// <summary>
        /// 发送数据
        /// </summary>
        /// <param name="sendByte">要发送的数据字节数组</param>
        /// <param name="offset">数据字节数组中开始发送的位置</param>
        /// <param name="size">要发送的字节数</param>
        /// <returns></returns>
        public bool Send(byte[] sendByte,int offset,int size )
        {
            IAsyncResult iar = ns.BeginWrite(sendByte, offset,size, null, null);
            int count = sleepCount;
            while (!iar.IsCompleted)
            {
                //if (count <= 0)
                //{
                //    return false;
                //}
                Thread.Sleep(sleepMilliseconds);
                //count--;
            }
            try
            {
                ns.Flush();
                ns.EndWrite(iar);
                
                return true;
            }
            catch (Exception)
            {
                return false;
            }


        }

        /// <summary>
        /// 分片发送数据流
        /// </summary>
        /// <param name="stream">要发送的流</param>
        /// <param name="bufferSize">缓存大小</param>
        /// <returns></returns>
        public bool SendFragment(Stream stream, int bufferSize)
        {
            BinaryReader br = new BinaryReader(stream, encoding);
            int count = sleepCount; //循环次数
            int readCount = 0;  //当前读到的文件流字节数
            int currentPosit = 0; //当前文件流的位置
            byte[] sendBufs = new byte[bufferSize]; //发送缓存
            while ((readCount = br.Read(sendBufs, currentPosit, sendBufs.Length)) != 0)
            {
                if (!Send(sendBufs))
                {
                    return false;
                }
                
                currentPosit += readCount;//手动提升当前流位置 
            }

            return true;
        }

       
        #endregion

        /// <summary>
        /// 关闭收发器。尝试在SOCKET上关闭收发,关闭SOCKET连接,关闭TcpClient对象
        /// </summary>
        public void Close()
        {
            try
            {
                client.Client.Shutdown(SocketShutdown.Both);
            }
            catch (Exception)
            {

            }

            try
            {
                client.Client.Close();
            }
            catch (Exception)
            {

            }

            try
            {
                client.Close();
            }
            catch (Exception)
            {

            }



        }

    }
}

iloli 2014-02-07
  • 打赏
  • 举报
回复
附上部分实现代码: 不知道是不是代码当中有问题?

 private void ReceiveByte(byte[] receiveByte)
        {
            IAsyncResult iar = ns.BeginRead(receiveByte, 0, receiveByte.Length, null, null);
            int count = sleepCount;
            if (!iar.IsCompleted)
            {
                if (count <= 0)
                {
                    receiveByte = null;
                    return;
                }
                Thread.Sleep(sleepMilliseconds);
                count--;
            }
            try
            {
                ns.EndRead(iar);
                return;
            }
            catch (Exception)
            {
                receiveByte = null;
                return;
            }
 
public bool Send(byte[] sendByte,int offset,int size )
        {
            IAsyncResult iar = ns.BeginWrite(sendByte, offset,size, null, null);
            int count = sleepCount;
            if (!iar.IsCompleted)
            {
                if (count <= 0)
                {
                    return false;
                }
                Thread.Sleep(sleepMilliseconds);
                count--;
            }
            try
            {
                ns.EndWrite(iar);
                ns.Flush();
                return true;
            }
            catch (Exception)
            {
                return false;
            }
 
 
        }
 
 
 public bool SendFile(string path, int bufferSize)
        {
            FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read);
            int count = sleepCount; //循环次数
            int readCount = 0;  //当前读到的文件流字节数
            byte[] sendBufs = new byte[bufferSize]; //发送缓存
             
            int fileLength = (int)fs.Length;
            string fileName = Path.GetFileName(path);
            byte[] packetFirst = Joinhead4(string.Format("|{0}|{1}|", fileName, fileLength.ToString()));
 
            if (!pt.Send(packetFirst))  //发送头包
                return false;
 
            #region 关键代码
            while ((readCount = fs.Read(sendBufs,0,sendBufs.Length)) != 0)
            {//以缓存大小分片循环发送文件内容
 
                if (!pt.Send(sendBufs,0,readCount))
                {
                    return false;
                }
            }
 
            fs.Close();
            #endregion
 
            return true;
        }
 
public void ReceiveFile(string directoryPath,int bufferSize)
        { 
            //先接收头包 以得到文件名和文件长度
            byte [] packetFirst = pt.ReceiveOnePacket(4);//根据4字节包头指定长度接收头包
            string rec = DropHead4(packetFirst);//去掉包头得到头包字符串信息
            rec = rec.Remove(0, 1);//去掉最前的分隔线
            rec = rec.Remove(rec.Length - 1, 1);//去掉最后的分隔线
            string[] fileds = rec.Split(new string[] { "|" }, StringSplitOptions.None);//得到数据字段
 
            string fileName = fileds[0];
            string fileFullPath = directoryPath + fileds[0];
            int fileLength = int.Parse(fileds[1]);
 
 
            FileStream fs = new FileStream(fileFullPath, FileMode.Create, FileAccess.ReadWrite);
             
             
            //循环接收文件内容
            int receiveLength = fileLength;   //需要接收的文件总字节数
            byte[] recBufs = new byte[bufferSize]; //接收缓存
 
            while (receiveLength > 0)
            {
                recBufs = pt.ReceiveLength(recBufs.Length);
 
                if (receiveLength > recBufs.Length)
                {
                    fs.Write(recBufs, 0, recBufs.Length);
                }
                else
                {
                    fs.Write(recBufs, 0, receiveLength);
                }
                fs.Flush();
                receiveLength -= recBufs.Length;
            }
             
            fs.Close();
        }
iloli 2014-02-07
  • 打赏
  • 举报
回复
引用 16 楼 jerry_dqh 的回复:
发送端的缓冲是多大?这个跟tcp应该没有多大关系,tcp是可靠传输,也可以用wireshark抓包看一下
我现在仔细测试了一下。发送的时候发送缓存字节可以任意设,,从K到M 级别都行。但是接收缓存字节不能超过8K ,8K 以下含8K 接收正常,一但超过8K 就出问题,接收的文件会缺失尾部一些数据,而且接收缓存设得越大,文件尾部数据缺失得越严重。。
iloli 2014-02-07
  • 打赏
  • 举报
回复
引用 14 楼 leehark 的回复:
TCP是可靠传输协议,如果包有丢失则会重传,如果缓冲区不够则会控制流量传输速率,你可以通过wireshark抓一下包,看看是否全部接收了,还是中间RST没有正常FIN,或者你的程序写法有问题。
我现在仔细测试了一下。发送的时候发送缓存字节可以任意设,,从K到M 级别都行。但是接收缓存字节不能超过8K ,8K 以下含8K 接收正常,一但超过8K 就出问题,接收的文件会缺失尾部一些数据,而且接收缓存设得越大,文件尾部数据缺失得越严重。。
碼上道 2014-02-05
  • 打赏
  • 举报
回复
发送端的缓冲是多大?这个跟tcp应该没有多大关系,tcp是可靠传输,也可以用wireshark抓包看一下
tcmakebest 2014-02-05
  • 打赏
  • 举报
回复
即使接收数据的时候没有用缓冲区,也不会出现楼主所说的问题,还是检查检查代码质量吧。
leehark 2014-02-05
  • 打赏
  • 举报
回复
TCP是可靠传输协议,如果包有丢失则会重传,如果缓冲区不够则会控制流量传输速率,你可以通过wireshark抓一下包,看看是否全部接收了,还是中间RST没有正常FIN,或者你的程序写法有问题。
iloli 2014-01-30
  • 打赏
  • 举报
回复
引用 12 楼 qldsrx 的回复:
你应该将读取缓冲区设置为64KB或更小,4M实在太大。但是读取缓冲区本身不是分块,只是在读取一定字节后发送的意思。对于读取缓冲区的数据,发送后不要做任何重发记录,因为TCP保证了发送的准确性,一旦发送出现问题,会抛出异常的,记录异常时读取的断点即可做到重发。
你说的我都试过,当发送端为64K 的缓冲 时 只要接收端大于8K 就会在文件尾部丢失数据。 我现在就是在怀疑是不是有可能接收端大于8K时就会处理不过来(因为它还要写入文件嘛)而造成的丢失文件尾部数据的现象
qldsrx 2014-01-30
  • 打赏
  • 举报
回复
你应该将读取缓冲区设置为64KB或更小,4M实在太大。但是读取缓冲区本身不是分块,只是在读取一定字节后发送的意思。对于读取缓冲区的数据,发送后不要做任何重发记录,因为TCP保证了发送的准确性,一旦发送出现问题,会抛出异常的,记录异常时读取的断点即可做到重发。
iloli 2014-01-30
  • 打赏
  • 举报
回复
引用 10 楼 LinuxCard 的回复:
tcp保证数据的完整,保证顺序,你收数据的时候,肯定有问题
但为什么小文件如1K左右的就是正常的呢。我真很郁闷。。。
LinuxCard 2014-01-30
  • 打赏
  • 举报
回复
tcp保证数据的完整,保证顺序,你收数据的时候,肯定有问题
iloli 2014-01-29
  • 打赏
  • 举报
回复
引用 4 楼 xxdddail 的回复:
1.增加数据块编号:将数据分块传输,同时增加一个块号。 2.增加头尾:在每个数据块的头和尾各增加一个字节,以表示该数据的头和尾 3.增加文件长度:为了确保该数据的正确接受,可以在数据据块头后紧接着表示长度的数据,这样在收到数据头之后就可以拿数据的长度,然后按着这个长度收数据。 4.增加校验和:为了确保数据的正确性,可以在数据尾的前一位增加一位作为校验和,这样就可以确保收到的数据是正确的。如果不正确,必须请求重发。 5.组合数据:在收到数据后,按编号顺序组合数据。
我怎么感觉这个过程是针对UDP的。。。。
iloli 2014-01-29
  • 打赏
  • 举报
回复
引用 7 楼 sp1234 的回复:
谁知道你是怎么接收的?
不就是书上教的接收方法。将文件分块 发送端读一块数据立即发出去,循环直到把整个文件读取并发送完。 接收方则是相反的操作
加载更多回复(7)

110,566

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • Web++
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧