本帖最后由 iloli 于 2014-01-28 11:24:38 编辑

解决方案 »

  1.   

    TCP是可靠的协议,就不要麻烦分块传输了。如果不想一次性传个整个文件,可以借鉴Http Range的方式:
    客户请求某个文件的某个范围,比如a.bmp的10000到19999字节,然后专心接收10000字节。
      

  2.   


    分块传输要点:
    1.在传输前先取文件的长度,MD5,
    2.计算块数=长度/每个块大小
    3.发送文件信息 长度 ,MD5,块数量,每块大小(此时等待接收方信息返回)
    4.接收方收到文件信息(长度 ,MD5,块数量,每块大小)开始创建临时文件,回返回状态。
    5.发送方收到接收方的返回状态后,开始发送第一块文件信息 数据包格式应为 第几块,数据内容
    6.接收方收到块数据后,把数据写到临时文件中利用FileStream.Postation这个字节偏移就可以然后收到每个块的信息后对比一下是不是所有的块全收完了。最后再把临时文件MD5与收到的文件信息MD5比交后如果一至说明全收完成。如果不一至说明数据接收错误。
    http://www.csharpwin.com/csharpresource/1135.shtml这是例子是UDP的,TCP跟这个原理差不多
      

  3.   

    TCP 也要用这些步骤吗 这不是跟UDP一样了。TCP的优势感觉都没体现啊
      

  4.   

    我怎么感觉这个过程是针对UDP的。
      

  5.   

    tcp保证数据的完整,保证顺序,你收数据的时候,肯定有问题
      

  6.   

    你应该将读取缓冲区设置为64KB或更小,4M实在太大。但是读取缓冲区本身不是分块,只是在读取一定字节后发送的意思。对于读取缓冲区的数据,发送后不要做任何重发记录,因为TCP保证了发送的准确性,一旦发送出现问题,会抛出异常的,记录异常时读取的断点即可做到重发。
      

  7.   

    你说的我都试过,当发送端为64K 的缓冲 时 只要接收端大于8K 就会在文件尾部丢失数据。
    我现在就是在怀疑是不是有可能接收端大于8K时就会处理不过来(因为它还要写入文件嘛)而造成的丢失文件尾部数据的现象
      

  8.   

    TCP是可靠传输协议,如果包有丢失则会重传,如果缓冲区不够则会控制流量传输速率,你可以通过wireshark抓一下包,看看是否全部接收了,还是中间RST没有正常FIN,或者你的程序写法有问题。
      

  9.   

    发送端的缓冲是多大?这个跟tcp应该没有多大关系,tcp是可靠传输,也可以用wireshark抓包看一下
      

  10.   

    我现在仔细测试了一下。发送的时候发送缓存字节可以任意设,,从K到M 级别都行。但是接收缓存字节不能超过8K ,8K 以下含8K 接收正常,一但超过8K 就出问题,接收的文件会缺失尾部一些数据,而且接收缓存设得越大,文件尾部数据缺失得越严重。。
      

  11.   

    我现在仔细测试了一下。发送的时候发送缓存字节可以任意设,,从K到M 级别都行。但是接收缓存字节不能超过8K ,8K 以下含8K 接收正常,一但超过8K 就出问题,接收的文件会缺失尾部一些数据,而且接收缓存设得越大,文件尾部数据缺失得越严重。。
      

  12.   

    附上部分实现代码:
    不知道是不是代码当中有问题?
     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();
            }
      

  13.   

    pt是什么,从头到尾没看到定义,pt.ReceiveLength做了什么?ReceiveByte的存在有何意义?你给的代码都没有连续性可言,残缺不可分析。
    就你给的现象分析,外加ReceiveByte函数的实现,可以猜想,你接收数据是一次性而不是循环接收,接收了一次8K字节后,就认为结束当前的数据传输了,客户端主动断开了数据的接收,导致接收不完整。
      

  14.   


    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)
                {            }        }    }
    }
      

  15.   

    你这个封装类有问题:
    一、滥用异步方法,如果使用异步方法,就不该轮询那个IsCompleted属性进行等待,而是直接在回调函数中做相关处理,此处你是要封装一个同步方法,因此应该直接用Read而不是BeginRead去接收数据,而且在同步方法中,可以设置ReadTimeout来控制接收超时,那个属性对异步方法无效。
    二、同步方法的Read和异步方法的EndRead都会返回实际接收的字节数,你却无视了那个返回值,以为一定能接收你给定数组大小的字节内容,这是不可能的。也许你设置的缓冲区是64K,但是调用EndRead完成接收时才填充了8K字节,那也算是一次完整的接收,需要按照实际接收到的字节数处理。
      

  16.   


    对,的确是在接收的时候会出现接收到的数据长度没有填满接收缓存数组的情况。这个问题不是说在TCP协议中不存在吗?
    还有,我参照很多示例和书,都是使用IsCompleted属性进行轮询等待异步完成的,我在小文件传输的时候一切都是好的,只有在大文件传输的时候,且接收缓存设得比较大,如大于8K才会有数据缺少的情况。这个问题主要是由于什么造成的呢?还有就是我怎么样在不改变异步接收方法的前提下实现完整,准确接收所有数据?还有一点:他传大文件时 如果缓存数组一但设置得过大就出会问题,而且仅仅是文件尾部数据缺失,缺失数据的数量长度不固定,并不存在中间缺失数据的情况。
      

  17.   

    这个跟格式没关系吧。我是在TCP传数据的时候缺少数据。
      

  18.   

    你倒底有没有认真看MSDN上有关Read方法的说明了?写的明明白白的,返回值才是实际接受的字节数,而不是看你给定的数组大小。要不你最后一次接收数据,只发送了4个字节,但是缓冲区是64K,难道4字节后面的内容都是有效的?如果你这么设计框架的话,早被人骂死了,缓冲区和实际接收数是两个不同的概念。