C# Socket Networkstream接收数据不全

holymaple 2015-01-13 12:10:25
最近在做c/s,客户端用C#的Socket进行通信。接收数据开单独的接收线程使用networkstream进行接收
接收的方法大体上跟网上流传的一样
do
{
int readSize = mNetworkstream.Read(bytes, 0, 1024);
if (readSize > 0)
{
//do sth
}
}while(mNetworkstream.DataAvailable)

这样接收局内网或者单机没问题,但是放到外网测试网络不稳定就接收不全。
搜集很多资料,也试过在发送Socket包之前加个包大小,然后在while中增加判断是否读到包的大小,可是一样接不全,中间好像掺杂了一些没用的数据?

我一个包很容易超过byte[10240],而且要整个包接收完才能处理数据。
请问C# Socket使用networkstream怎样才能接收完全呢?

ps:也有听说过异步networkstream.beginread接收。鉴于网上一大抄,各种人贴各种代码片段根本就没用。有些有用但只能接收一次。。。

如果有beginread相关代码也希望大牛们贴出来参考一下,不要贴代码片段。。。因为按照代码片段以及很多资料、MSDN示例来写,我就没成功过。
我每次接收都是1024读的,一个包上万,按照网上的资料,都分成好几份1024的数据了。。有什么办法可以让我接完上完的包呢?

谢谢了~
...全文
4275 27 打赏 收藏 转发到动态 举报
写回复
用AI写文章
27 条回复
切换为时间正序
请发表友善的回复…
发表回复
qq_36726957 2018-08-11
  • 打赏
  • 举报
回复
默默回一句,听不太懂你们说啥,新人一枚,自觉可用:
internal byte[] ReceiveByteArray(NetworkStream stream)
{


int bufferlen = GetSize(stream);
byte[] resultbyte = new byte[bufferlen];

for (int i = 0; i < bufferlen; i++)
{
resultbyte.SetValue((byte)stream.ReadByte(), i);//每次顺序读取一个字节
}
return resultbyte;
}
130mb 53秒 还能接受
最后说一句 无脑复制就不要发帖了 对新人很不友好
liangliming 2015-09-14
  • 打赏
  • 举报
回复
stream可用属性和Read是微软存在Bug的。调什么函数检验来着,我也忘了,正在找...
holymaple 2015-01-17
  • 打赏
  • 举报
回复
问题已自行解决。 在11楼我读取的代码中
                                    while (m_Reader.DataAvailable || currentSize < packSize)
                                    {
                                        readSize = m_Reader.Read(bytes, 0, (int)m_ConnectSocket.ReceiveBufferSize);
                                        if (readSize > 0)
                                        {
                                            currentSize += readSize;//读取成功,进行叠加
                                            totalBytes = totalBytes.Concat(bytes).ToArray();//将读取的内容添加到容器后面
                                        }
                                    }
其中这一段是有问题的,我将数据串起来采用的是 byte.Concat方法,而该方法是将bytes添加到totalBytes后面。 bytes长度为1024。这代码在单机或网络环境好的情况是可以使用的,也是网上搜索给出的代码标准答案。 但是如果在网络没那么好的情况下,是有问题的。 Networkstream的Read方法是尽可能的往stream里面读取数据。如果网络不好的情况下,真实有用的数据可能只有512,但是Read中强行读取1024,导致bytes前512个数据是有用数据,而后面的512~1024数据则是之前的数据。 而我的代码中直接把1024的bytes添加到结果后面,这样是不正确的。 解决办法是类似下面的代码一样

                                        if (readSize > 0)
                                        {
                                            LogRecord.WriteLog(readSize);
                                            currentSize += readSize;
                                            byte[] temp = new byte[readSize];
                                            Array.Copy(bytes, 0, temp, 0, readSize);
                                            totalBytes = totalBytes.Concat(temp).ToArray();//C#神器,byte[]会自动延长
                                        }
多谢xian_wwq指出应该打印到日志内。项目中引用Log4Net将结果打到文件里面比对一下就发现问题了。 至于zanfeng指出Networkstream并不可靠,但我并不知道改用什么其他方法接收。 以及chengbin0602指出的要异步接收。 结贴给分,感谢各位!
编程有钱人了 2015-01-16
  • 打赏
  • 举报
回复
去博客园搜索,一大推代码 http://www.cnblogs.com/dragon-L/p/3682967.html 里面有代码
holymaple 2015-01-16
  • 打赏
  • 举报
回复
引用 23 楼 wangjun8868 的回复:
去博客园搜索,一大推代码 http://www.cnblogs.com/dragon-L/p/3682967.html 里面有代码
花时间看了,没有帮助+1 11楼开始我就回复贴代码,并阐述我的问题
holymaple 2015-01-15
  • 打赏
  • 举报
回复
没人回么,自己Up~
holymaple 2015-01-15
  • 打赏
  • 举报
回复
引用 19 楼 zanfeng 的回复:
mNetworkstream 这个东西本来封装的就不靠谱没有必要用这个。
。。。NetworkStream不靠谱吗。。。 那应该用什么方法接收数据?
holymaple 2015-01-15
  • 打赏
  • 举报
回复
引用 18 楼 xian_wwq 的回复:
谢谢指出问题,不过逻辑完善后,这代码在外网的情况下依然不能接收全数据。。。数据读到的长度是跟我想的一样,但是数据内容不一样
首先说c#提供的类没有这个bug。 要找到问题,建议你先不处理数据,Server侧只接收数据。 调试通讯程序,加断点的方法不靠谱,原因我不解释了 在client发送数据和Server接收数据的地方打印日志。 通过日志帮助找问题点。 [/quote] 原来调试不靠谱的啊。。。多谢提醒,那我打日志找问题吧,谢谢
xian_wwq 2015-01-14
  • 打赏
  • 举报
回复
引用 12 楼 holymaple 的回复:
[quote=引用 2 楼 xian_wwq 的回复:] 1. 数据包长度应该放在发送数据的头上, 接收数据后,首先进行数据校验,读取长度, 根据长度进行后续接收处理。 lz 提到接收中出现多余数据,就只发送的数据发送过来的 数据长度不定,而接收按定长处理导致。 再差的网络,包头几个字节还是能收到的。 如果包头校验失败,那继续接收也无意义, 可以断开该socket,让客户端重新连接。 2.接收数据后,不要直接在该线程中进行数据解析处理。 需要考虑将数据放入队列或者其他容器,让接收动作立即返回。 否则一旦数据量较大,就会出现接收被阻塞的情况。
你好,我以补全代码在11楼。我也是发送标记作为包头+数据长度+数据形式去获取数据的。 代码在网络环境好的情况下如单机、局内网都没问题,但是放置外网(如我的阿里云)就读不全了。数据是读全了,但是里面的数据不是原数据,像是随机的。[/quote] 有一点显然需要完善, 如果接收时,一次收到的包少于36字节,下面的校验和长度处理就无法进行了。
足球中国 2015-01-14
  • 打赏
  • 举报
回复
mNetworkstream 这个东西本来封装的就不靠谱没有必要用这个。
xian_wwq 2015-01-14
  • 打赏
  • 举报
回复
谢谢指出问题,不过逻辑完善后,这代码在外网的情况下依然不能接收全数据。。。数据读到的长度是跟我想的一样,但是数据内容不一样[/quote] 首先说c#提供的类没有这个bug。 要找到问题,建议你先不处理数据,Server侧只接收数据。 调试通讯程序,加断点的方法不靠谱,原因我不解释了 在client发送数据和Server接收数据的地方打印日志。 通过日志帮助找问题点。
holymaple 2015-01-14
  • 打赏
  • 举报
回复
引用 10 楼 sp1234 的回复:
关于是同步 Read 还是异步 BeginRead,不是这个问题的重点。它不会影响你收到的消息并进行解析的“对错”问题。所以这里可以不提。
你好,之前忘记CSDN自己最多只能连续三楼,所以代码传上去之后发现已经连盖三楼了。。 此前搜C# Socket方面,很多CSDN都有看到你的回答,先谢谢了。 你说的我不太懂,最坏情况下我也有想过将整个Socket包拆分,每个小包里面都有固定的开头或者结束标记,然后接受判断是我的包才将数据串在一起最后处理。但感觉这样太麻烦。 同步Read感觉只有把整个Socket包拆分(数据很大的情况下)才满足需求。 我服务端C++只是直接把整个Socket Send过来,想偷懒不写那么复杂。。。觉得只有异步BeginRead才能满足我的需求。 请问能提供异步BeginRead的示例参考吗?关于BeginRead我自己之前也有根据收集资料写了几次,但总不能得到我想要的结果。 代码在13楼。您能帮我理清BeginRead的思路吗?万分感谢!
holymaple 2015-01-14
  • 打赏
  • 举报
回复
引用 3 楼 chengbin0602 的回复:
必须要用异步委托递归获取,否则你想一次性获取那是不可能的,要么你把缓冲区设计无限大,代价是会大量占用资源而导致异常。但如果一个包仍然非常大,比如传送文件,你想一次性获取数据还是不可取。
你好,就我个人搜集资料来看,个人是赞同你的说法的,那么请问异步的BeginRead有没有示例呢?因为我根据自己网上搜索的写了运行结果并不是我想要的。代码在13 楼
引用 5 楼 chengbin0602 的回复:
楼主我发现你根本没有理解socket是什么意思,数据包是怎么回事。别怪我说的太难听。socket接收数据没有用while去循环获取的,这种同步获取数据会导致线程阻塞和大量丢包。其他客户端数据会无法接受,并且存在时间同步问题,势必造成由于发送和接受时间和速度不一致导致丢包。你这种设计是个严重的逻辑错误。 为什么socket数据传送必须要用异步委托?因为首先不可能保证同一个时间只会有一个人向你发送数据,所以要使用异步,要解决异步消息队列问题,保证消息间不冲突。 第二,不光要用异步还要用递归,因为你不一定知道发送来的消息的大小,还有一个原因是用递归不用考虑时间问题,什么时候数据接收完成了,什么时候才停止接收。如果你不用递归,你必须有足够的自信你的接收端不会漏掉每一个字节的数据。 从windows消息循环的机制来探讨,但是由于各种原因会使得你服务器的消息循环速度不可能与每个客户端消息循环速度一致,如果你用同步方法,你不可能保证不会漏掉至少一个字节的数据。
这位兄弟所言甚是,那么能不能提供一些异步的示例给小弟参考呢?
holymaple 2015-01-14
  • 打赏
  • 举报
回复
引用 14 楼 xian_wwq 的回复:
[quote=引用 12 楼 holymaple 的回复:] [quote=引用 2 楼 xian_wwq 的回复:] 1. 数据包长度应该放在发送数据的头上, 接收数据后,首先进行数据校验,读取长度, 根据长度进行后续接收处理。 lz 提到接收中出现多余数据,就只发送的数据发送过来的 数据长度不定,而接收按定长处理导致。 再差的网络,包头几个字节还是能收到的。 如果包头校验失败,那继续接收也无意义, 可以断开该socket,让客户端重新连接。 2.接收数据后,不要直接在该线程中进行数据解析处理。 需要考虑将数据放入队列或者其他容器,让接收动作立即返回。 否则一旦数据量较大,就会出现接收被阻塞的情况。
你好,我以补全代码在11楼。我也是发送标记作为包头+数据长度+数据形式去获取数据的。 代码在网络环境好的情况下如单机、局内网都没问题,但是放置外网(如我的阿里云)就读不全了。数据是读全了,但是里面的数据不是原数据,像是随机的。[/quote] 有一点显然需要完善, 如果接收时,一次收到的包少于36字节,下面的校验和长度处理就无法进行了。 [/quote] 谢谢指出问题,不过逻辑完善后,这代码在外网的情况下依然不能接收全数据。。。数据读到的长度是跟我想的一样,但是数据内容不一样
全体起立 2015-01-13
  • 打赏
  • 举报
回复
楼主我发现你根本没有理解socket是什么意思,数据包是怎么回事。别怪我说的太难听。socket接收数据没有用while去循环获取的,这种同步获取数据会导致线程阻塞和大量丢包。其他客户端数据会无法接受,并且存在时间同步问题,势必造成由于发送和接受时间和速度不一致导致丢包。你这种设计是个严重的逻辑错误。 为什么socket数据传送必须要用异步委托?因为首先不可能保证同一个时间只会有一个人向你发送数据,所以要使用异步,要解决异步消息队列问题,保证消息间不冲突。 第二,不光要用异步还要用递归,因为你不一定知道发送来的消息的大小,还有一个原因是用递归不用考虑时间问题,什么时候数据接收完成了,什么时候才停止接收。如果你不用递归,你必须有足够的自信你的接收端不会漏掉每一个字节的数据。 从windows消息循环的机制来探讨,但是由于各种原因会使得你服务器的消息循环速度不可能与每个客户端消息循环速度一致,如果你用同步方法,你不可能保证不会漏掉至少一个字节的数据。
wyx100 2015-01-13
  • 打赏
  • 举报
回复
引用 3 楼 chengbin0602 的回复:
必须要用异步委托递归获取,否则你想一次性获取那是不可能的,要么你把缓冲区设计无限大,代价是会大量占用资源而导致异常。但如果一个包仍然非常大,比如传送文件,你想一次性获取数据还是不可取。
全体起立 2015-01-13
  • 打赏
  • 举报
回复
必须要用异步委托递归获取,否则你想一次性获取那是不可能的,要么你把缓冲区设计无限大,代价是会大量占用资源而导致异常。但如果一个包仍然非常大,比如传送文件,你想一次性获取数据还是不可取。
holymaple 2015-01-13
  • 打赏
  • 举报
回复
使用上面的read方法数据读不全,网上有看到说法是“这是典型的读比发快,既发送端还没发完接收端已经读完了。 http://q.cnblogs.com/q/31971/ 说“TcpClient NetworkStream 是基于流的 ” 要想接全要用BeginRead,可是BeginRead该怎么用?
xian_wwq 2015-01-13
  • 打赏
  • 举报
回复
1. 数据包长度应该放在发送数据的头上, 接收数据后,首先进行数据校验,读取长度, 根据长度进行后续接收处理。 lz 提到接收中出现多余数据,就只发送的数据发送过来的 数据长度不定,而接收按定长处理导致。 再差的网络,包头几个字节还是能收到的。 如果包头校验失败,那继续接收也无意义, 可以断开该socket,让客户端重新连接。 2.接收数据后,不要直接在该线程中进行数据解析处理。 需要考虑将数据放入队列或者其他容器,让接收动作立即返回。 否则一旦数据量较大,就会出现接收被阻塞的情况。
holymaple 2015-01-13
  • 打赏
  • 举报
回复

        public static ManualResetEvent allDone = new ManualResetEvent(false);
        private static byte[] bytes = new byte[m_ConnectSocket.ReceiveBufferSize];
        private static byte[] totalBytes = new byte[0];//C#神器,byte[]会自动延长
        public class State
        {
            public NetworkStream ns;
            public byte[] buffer;
            public byte[] result;
        }
        public static void myReadCallBack(IAsyncResult iar)
        {
            State state = (State)iar.AsyncState;
            NetworkStream myNetworkStream = state.ns;
            int numberOfBytesRead;
            numberOfBytesRead = myNetworkStream.EndRead(iar);
            if (numberOfBytesRead > 0)
            {
                totalBytes = totalBytes.Concat(state.buffer).ToArray();
                //接收到的消息长度可能大于缓冲区总大小,反复循环直到读完为止
                while (myNetworkStream.DataAvailable)
                {
                    myNetworkStream.BeginRead(state.buffer, 0, state.buffer.Length, new AsyncCallback(myReadCallBack), state);
                }
                
            }
            else
            {
                //执行这里我认为数据已经全部接收完毕
                //开始执行如11楼的处理数据代码
                //也就是从 state.buffer 中取出包头判断是否我的包
                //然后提取数据长度,根据数据长度提取数据,将数据交由数据处理线程进行处理
                allDone.Set();
            }
        }

       //线程接收代码如下
                        while (m_Reader.DataAvailable)
                        {
                            State state = new State();
                            state.ns = m_Reader;
                            state.buffer = new byte[m_ConnectSocket.ReceiveBufferSize]; ;
                            state.result = new byte[0];
                            totalBytes = new byte[0];
                            m_Reader.BeginRead(state.buffer, 0, state.buffer.Length, myReadCallBack, state);
                            allDone.WaitOne();
                        }
使用异步的BeginRead,但是代码没有实现我想要的结果。其实对于BeginRead很混乱,希望各位大牛能帮我理清思路。 因为网上关于BeginRead的代码真是惨不忍睹,全是代码片段,全是无脑复制,好一点的在网络环境好的情况下还能接收成功。 但更多的根本没用
加载更多回复(7)

111,077

社区成员

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

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

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