当前位置:编程学习 > C#/ASP.NET >>

TCPclient传输文件,丢包,不得不使用sleep,请求解决方案!

public void SendData( string sFilePath,string sAddr,int nPort)
        {
            FileStream streamLocalFile = File.OpenRead(sFilePath);   //打开本地文件,获得流对象
            int sNameLength = sFilePath.Length;                      //获得文件名长度

            //备置缓冲区对象
            int nBufferLength = 5120;
            Byte[] buffer = new byte [ nBufferLength ];            //建立指定大小的缓存

            //备置头数据中的原型
            //int nCommandType = 0;
            string sFileName = Path.GetFileName(sFilePath );                 //从路径中提取文件名字串
            int nFileNameLength = sFileName .Length ;
            int nStation = 0;
            int nFileLength = (int)streamLocalFile.Length;

            //将头原型压入缓存
            Byte[] byteCmdType = System.BitConverter.GetBytes(nCommand_Head);
            byteCmdType.CopyTo(buffer, sizeof(int) * 0);                                         //压入数据类型标记

            Byte[] byteNameLength = System.BitConverter.GetBytes(nFileNameLength);  
            byteNameLength.CopyTo(buffer, sizeof(int) * 1);                    //压入文件名长度值

            Byte[] byteStation = System.BitConverter.GetBytes(nStation);
            byteStation.CopyTo(buffer, sizeof(int) * 2);                                   //压入文件位置值

            Byte[] byteFileLength = System.BitConverter.GetBytes(nFileLength );              //压入文件总长度值
            byteFileLength.CopyTo(buffer, sizeof(int) * 3);

            Byte[] byteFileName = System.Text.Encoding.Default.GetBytes(sFileName);          //压入文件名称字串
            byteFileName.CopyTo(buffer, sizeof(int) * 4);

            ///////////////////////////准备好网络流
            
            IPEndPoint iepRemote = new IPEndPoint (IPAddress .Parse (sAddr ),nPort );
            TcpClient clientSend = new TcpClient ();
            clientSend.NoDelay = true;
            clientSend.SendTimeout = 30000;
            clientSend.Connect(iepRemote);            //阻塞并执行远程连接,如失败,则继续阻塞,只有连接成功才能过关
            NetworkStream nwStream = clientSend.GetStream();

            //向流中写入文件头数据包,此处阻塞(实际上没有立即挂起等请求)
            nwStream.Write(buffer, 0, buffer.Length);

            //循环读取文件内容块并向网络流中写入数据块
            int nResidualLength = nFileLength ;                      //剩余文件长度

            int nOffSet = sizeof(int) *2;                    //确定缓存中读入文件的偏移超始位置
            int nDefaultDataLength = buffer .Length - nOffSet  ;

            int nReadLength = 0;

            int nSendedLength = 0;

            while (( nReadLength = streamLocalFile.Read(buffer, nOffSet, nDefaultDataLength )) > 0)  //直到读到数据的结尾
            {                
                ////向buffer中补写命令类型标签
                if (nResidualLength > nDefaultDataLength)
                {
                    System.BitConverter.GetBytes(nCommand_Body).CopyTo(buffer, 0);     //补写包中的命令标签为数据体:nCommand_Body
                    nResidualLength -= nReadLength ;                                //重设文件剩余长度
                }
                else
                    System.BitConverter.GetBytes(nCommand_End).CopyTo(buffer, 0);      //补写包中的命令标签为数据结尾:nCommand_End

                System.BitConverter.GetBytes(nReadLength).CopyTo(buffer, 4);           //向buffer中补写本包中实用数据的长度值(实际读到的数据长度)

                //Thread.Sleep(200);/////此处只有加此句或设断点,发送接收都完全成功,但若非如此,总会有几个包收不到,请求能够替代sleep的方案,因为这种方法低效且不可控!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

                    nwStream.BeginWrite(buffer, 0, buffer.Length, asyncCellback, nwStream);
                    //nwStream.Write(buffer, 0, buffer.Length);                              //向网络中写入数据包,将阻塞并等待远程的请求,也就是说这并不是一个单向抛出动作,而是来回互动

                nSendedLength += nReadLength ;
            }

            streamLocalFile.Close();   //关闭本地文件流
            nwStream.Close();          //关闭网络文件流
            clientSend.Close();
        }   


接收部分:

       public void ReceviData()
        {
            int nCommandType = 0;

            int nReceviPort = 7890;
            Byte[] bytes = new byte[5120];
            string sMessage = "";

            TcpListener listener = new TcpListener(IPAddress.Any, nReceviPort);   //建立一个监听对象

            listener.Start();
            TcpClient clientRecevi = listener.AcceptTcpClient();                  //接受挂起的连接请求,返回一个TcpClient.如果没有,则阻塞
            NetworkStream nwStream = clientRecevi.GetStream();                    //得到刚连接上的网络数据流

            int nNameLength = 0;
            int nFileLength = 0;
            int nBlockLength = 0;
            int nOffSet = 0;
            string sFileName = "";
            FileStream streamLocalFile = null;            

            int i=0;
            while ( (i = nwStream.Read(bytes, 0, bytes.Length)) > 0 )      //读数据到缓存中
            {                
                nCommandType = System.BitConverter.ToInt32 (bytes, 0);     //得到文件类型

                switch ( nCommandType )
                {
                    case 0:       //命令:打开或创建文件和文件流,附文件名称和文件大小                        
                        nNameLength = System.BitConverter.ToInt32(bytes, sizeof(int) * 1);       //得到文件名长度
                        nFileLength = System.BitConverter.ToInt32(bytes, sizeof(int) * 2);       //得到文件总长度
                        nOffSet = System .BitConverter .ToInt32 (bytes ,sizeof(int) * 3);      //得到开始传输的位置                       
                        sFileName = System.Text.Encoding.Default.GetString(bytes, sizeof(int) * 4, nNameLength);   //得到文件名字串                        
                        string sFilePath = Directory.GetCurrentDirectory() + @"\" + sFileName ;                //创建打开文件并得到文件流
                        streamLocalFile = File.Create(sFilePath);
                        break;
                    case 1:       //命令:接收文件内容并直接写入本地已打开的文件流中
                        nBlockLength = System.BitConverter.ToInt32(bytes, sizeof(int));    //得到本次次传入的数据的长度
                        streamLocalFile.Write(bytes, sizeof (int)*2, nBlockLength );              //向已打开的本地文件流中写入接收到的数据
                        break;
                    case 2:    //命令:当前文件传输完毕,停此文件接收
                        //得到文件尾的数据长度
                        nBlockLength = System.BitConverter.ToInt32(bytes, sizeof(int));    //得到本次次传入的数据的长度
                        streamLocalFile.Write(bytes, sizeof(int) * 2, nBlockLength);              //向已打开的本地文件流中写入接收到的数据
                        streamLocalFile.Close();
                        break;
                    case 3:    //命令:强行终止当前传输中的文件,并记录终止位置
                        break;
                    default:
                        break;
                }
            }
            clientRecevi.Close();
        }

有此经验的朋友帮忙了!!! --------------------编程问答-------------------- 分几部分发啊,谁叫你一次性发出去,数据量大了肯定会有延时的 --------------------编程问答-------------------- 我是分次发送的呀.每次5210字节(实际上减去8个字节的包头),测试用的文件kangjia.mpeg文件大小为2.642388m.
如果我在strea.write(buffer,0,buffer.length)前设断点,循环几次后取消断点完全放行,则复制过去的文件大小完全正确,可正常播放.但是,如果我从开始时就不用断点,则总是会少四五个包,使得传过去的文件无法播放.
同样.如果我在tread.write(buffer,0,buffer.length)前加上tread.sleep()暂停200毫秒,发送接收也完全正确,收到的文件可以正常播放.搞不明白什么愿因 --------------------编程问答-------------------- 这点我也想知道。收藏,继续关注。 --------------------编程问答-------------------- 那就是硬件的延迟问题了,你每次发收数据+个头和尾,然后收的时候进行判断试试 --------------------编程问答-------------------- 可是,write是阻塞的呀,我原以为只要接收方没有执行stream.read()的话,发送方会一直阻塞在stream.write()处,但根据出现的这种情况来看,这个远程传机制有点不对劲 !
我观察,实际上,当程序运行到nwStream.write()时,即使接收方没有执行streamWrite(),程序也会继续执行nwStream.write()五六次左右,如果接收方还是没有执行read(),发送方才会阻塞挂起..并非直接阻塞.
但不管理怎么说,从write会因为接收方不读而挂起这种现象来看,接收速度是会影响到发送速度的,按此理,发送方不可能在接收方没有读出流中的数据的情况下跳过流中任何数据而发送后面的数据的.
可事实上,为什么在阻塞状态下执行的TCP数据传输,怎么会在前面的包没有被成功接收的情况下继续发送后面的数据,这那里阻塞了呀 --------------------编程问答-------------------- 我也在研究TCP编程,希望和你交流一下.
**********************************************
Mr xing
email&msn:xingtaisen@126.com
QQ:654 1798 16
********************************************** --------------------编程问答-------------------- 同志们,一起努力解决这个问题吧.
难道一定要一问一答,请求一个位置发送一个位置吗?不断向发送端发出请求的数据的序号,例如,请求"1",就发第一个包,请求"2"就发第二个包....,如果请求2,结果没有收到2号包,超时后再次请求包2;如果收到了包3,则继续发送请求包2的信息,总之,由下载方完全控制发送方的发送步骤.
帮我想想,一定要这种做才可能不丢包吗.
可如果这样做了,效率一定高不了.

按理说, netWorkStream,接受方应该在每一次执行完read后,向彼岸发送方发出一个成功读取的信号,然后发送方才向网络流中继续写入,如果发送方收不到彼岸的读完信息,则会一直保持阻塞,如果超时,且重发前一个数据等等.
可是,实际上,networkStream似乎在流中的数据没有被读取的情况下,连续发送五六个数据包.

总之 ,我还是没搞明白,为什么在执行writ()函数之前,必须要进行延时处理才能保证数据不丢失?
那几个数据包到底是在那个环节里丢失的呢?是TCPclieng的BUG还是露洞,还是说我采用了错误的方法?
还有就是,DataAvailable 在什么情况下算是有数据状态true,什么情况下会是false?有数据到底是什么意思?意味着什么? --------------------编程问答-------------------- 朋友们帮忙了!!! --------------------编程问答-------------------- 驴哥,你今天怎么不上QQ了?
**********************************************
Mr xing
email&msn:xingtaisen@126.com
QQ:654 1798 16
**********************************************
--------------------编程问答-------------------- 既然你用了循环, 就不要BeginWrite了, 直接Write --------------------编程问答-------------------- 学习,接分 --------------------编程问答-------------------- 你为什么要分多次写呢?
你把数据全部封装好后,一次写入不好吗? --------------------编程问答-------------------- 楼上的,这里讨论的是数据比较大的传送(>1M)。
一定不可以一次搞定的。

从以前的帖子里翻出来的,参考参考。

http://topic.csdn.net/t/20060720/09/4892211.html --------------------编程问答-------------------- 我觉的这个问题根源在接收方,TCP的同步发送返回只是说明数据已经交给底层的Socket缓存,Windows的Socket机制应该能够确保所有的缓存数据都会被发送,但比不表示同步发送返回,该数据块就已经被接受方接收了。当接受方接收数据过满,缓存写满后就会发生后续数据丢失的问题。

--------------------编程问答-------------------- to:dionix
看得出来您对soket很有研究,先谢谢!
可是我有一点不理解,如果接收方的soket缓存满而未被读出时,底层soket机制应该停止将数据转移到接受方的缓存才对,否则,就很大程序上失去了数据在传输中的安全性,而tcp的特点就是安全,否则,还不如用udp呢.可如果按您所言,接收方的缓存写满后是丢失数据而不是通知发送方的底层停止发送,那数据发送过程中的安全性何以保障?毕竟两台机器的硬件条件差异是巨大的.比如我的机器 ,一个是P43G,一个是双核1.7G,不知道是不是和这个有关系?
另外,我今天调试时发现,主要是前几个包丢失,后面基本上没发现丢失问题.我在向网络流中写入之前加入了:
            int n = 0;    //用以帮助执行sleep

            while (( nReadLength = streamLocalFile.Read(buffer, nOffSet, nDefaultDataLength )) > 0)  //直到读到数据的结尾
            {                
              .....
                if (n < 4 )
                {
                    Thread.Sleep(200);
                    n++;
                }
                //nwStream.Write(buffer, 0, buffer.Length);  
...然后,就很少写丢包.但如果把这个4改成0,就肯定丢包,改成1,丢包会少一些,希望这个线索能启迪高手给点提示. --------------------编程问答-------------------- 谢谢Ezhuyin给的回复,看了看,很有得学习.多谢! --------------------编程问答-------------------- 经过断点测试,我如果先放行接收方的断点,然后再放行发送方的断点,接收数据成功,数据无丢包.
而如果我先放行发送方,任发送方向流中写入数据,则发送方会连续写入,而此时再放行接收方,第一个数据包肯定丢失.
另一种通过观察数丢的方式来看,如果发送方不设断点,接收方在第一个数据体(算上文件头包的话,这个包应该是第二个包)中接收的数据包中的数据就不对,也就是说,第一个包丢失了.
还有一种现象,就是,当发送方非断点畅通执行,接收放每收到一个包断点,手工放行,则接收方从网络流中读出的数据全是空包,里面的字节全为0.(按理说,如果发送方没有向流中写入空包,不可能从流中读出空包.
由以上现象来看,底层soket的阻塞执行机制可能真的有问题.在接收方数据速度够快的情况下,接收正常,如果接收方读流的数据赶不上发送方向流中写数据的速度,那么,接收方从流中读出的数据就可能出现以下情况:一,在还没有读出B的情况下直接读出了C,且B将一直被勿略,就像发送方从来没有将其放入流一样;二,从流中读出的数据字节全为0

有经验的朋友帮我找找解决方案,
如果我直接用soket而不用tcpclient和networkstream收发的话,有没有可能避免这个问题呢?还望大家帮忙 --------------------编程问答-------------------- 学习 --------------------编程问答-------------------- 我偿试:
直接执行接收端和发送端的exet程序,偿试发送十次同一个2.51m的视频文件,约有2次左右没有丢包,成功传送;5-7次丢1个包,而且是第一个包,因为视频文件如果丢了第一个包就无法播放了,丢后面的还可以播放.还有2次左右丢稍多一些的包
在这个测试中我没有使用thread.sleep(),完全使用tcpclient和networkstream的机制传送.过程大概为:
发送方:
-----读本地文件到缓存中
----写缓存中的数据到网络流中
--循环

接收方:
-----从网络流中读出数据到缓存
-----将缓存中的数据写入本地文件中
-----循环

发送方缓存大小:5120
发送方网络流缓存大小:5120
接收方缓存大小:5120
接收方网络流缓存大小:5120

同志们帮我想想,看到底是TCPclieng\networkstream的问题,还是soket的问题,还是说我程序代码的问题 .此问题不搞清楚,试想谁也把一个每次执行结果都不一样的文件传送模添加到自己的项目中呀.
先谢谢同志们了.

--------------------编程问答-------------------- 再补充一下
我两台电脑在一个局域网hub下.一个P43.0g,一个core2双核1.7G.前者512内存,后者1G内存.不知道有没有可能会是硬件性能差异造成的,如果是,那到底是那个层而设计不合理造成的 --------------------编程问答-------------------- LZ,不知道你在发送方为什么用BeginWrite,这样是异步发送,我觉得应该尝试改成Write,可能就会有改善,这样试下:

//发送方:

nwStream.BeginWrite(buffer, 0, buffer.Length, asyncCellback, nwStream);

改成:

nwStream.Write(buffer, 0, buffer.Length); --------------------编程问答-------------------- to 红金鱼:
不好意思,这是测试忘记改回来了,实际上用的不是begin.谢谢提醒.

终于找到了能解决问题的方法:把缓存降到了2048以下,正常不设断点没出过错,但好像如果只有接收方设断点,而发送方不设断点直接运行,还是会出现.

虽然问题基本解决,但知其然而不知其所以然总也不是回事.为什么一定要降到2048以下才不会问题?丢问到底是那个环节的问题?如果换了其它硬件环境会不会还出现丢包?等.所以说,我目前能确定的一件事是:TCP协议默认方法并不是绝对不丢包的,和硬件有一定关系.最起码,如果把缓存置的太大,就可能会有包不能被接收方读到.真正可靠的数据传输可能只能在应用层上采取措施了.要不然,到时候程序发布出去了,发送视频或其它文件到远程主机,结果有些能播有些不能播,那可麻烦就大了.

有相关经验的朋友提点建议 --------------------编程问答-------------------- mark --------------------编程问答-------------------- 是不是你接受方处理速度太慢,可以做个实验,不向盘上存文件,只存在内存中,看看丢不丢. --------------------编程问答-------------------- 发送基本上没有问题,主要看接收方是怎么处理的。

有时候全部发出的数据,接收时会丢失一部分。如果你用Socket发送一个大于10MB的文件或数据到一个SMTP邮件服务器,则一点问题都没有。目前你收完数据库不能播放,则可能是接收时丢失了数据。

--------------------编程问答-------------------- 学习 --------------------编程问答-------------------- 能告诉我你代码里的nCommand_Head这个变量是什么吗?
我现在也在做tcp上传,但我不知道nCommand_Head这个是什么意思。 --------------------编程问答-------------------- 后来你到底怎么解决的?为啥坚持用tcpclient和networkstream的机制 而不用socket呢?它们优势在哪儿呢? --------------------编程问答-------------------- tcp是不会丢包的。 接收的时候收到的实际大小会不同
补充:.NET技术 ,  C#
CopyRight © 2022 站长资源库 编程知识问答 zzzyk.com All Rights Reserved
部分文章来自网络,