Socket TCP协议解决粘包、半包问题的三种解决方案

举报
CoderZ1010 发表于 2022/09/25 06:29:23 2022/09/25
【摘要】 什么是粘包、半包问题:         粘包:例如服务端依次将两条消息发送给客户端,我们暂且简单的将这两条消息举例为"Hello"、"Unity",而客户端一次性读取到的内容却是"HelloUnity",像这种一次性读取到两条消息中数据内容的情况称之为粘包。     &nb...

什么是粘包、半包问题:

        粘包:例如服务端依次将两条消息发送给客户端,我们暂且简单的将这两条消息举例为"Hello"、"Unity",而客户端一次性读取到的内容却是"HelloUnity",像这种一次性读取到两条消息中数据内容的情况称之为粘包。

        半包:例如服务端发送消息"Hello"给客户端,而客户端依次读取到"Hel","lo"两条消息,这种情况称之为半包。

粘包、半包发生的原因:

        粘包:消息发送方发送完完整的消息后,接收方没有及时处理(比如网络开小差,未能及时读取消息),数据滞留于缓冲区,此时发送方又继续发送了其他消息,那么接收方下次在缓冲区读取时,一次性读取到大于一条消息数据造成粘包。

        半包:发送方发送消息数据大小为512字节,而接收方缓冲区剩余已不足512字节,造成半包。

        究其根本原因,TCP为流式协议,消息不存在边界。

解决方案:

        1.固定长度法:服务端和客户端规定固定长度的缓冲区,当消息数据长度不足时,使用规定的填充字符进行填充。弊端:增加了不必要的数据传输,造成网络传输负担,不建议使用。

        2.结束标识法:在包体尾部增加标识符表示一条完整的消息数据已经结束。弊端:若消息体本身包含该标识符需要做转义处理,因此效率依然不高。

        3.长度信息法:将包体分为消息头+消息体,消息头中信息为消息体的长度,接收方通过该长度信息读取后面指定长度的内容,需要注意的是需限制可能的最大长度从而规定长度占用字节数。该方法为处理粘包半包问题的常用方法。核心代码:

发送端:


  
  1. //将发送的内容转化为字节数据
  2. byte[] bytes = Encoding.Default.GetBytes(content);
  3. //消息体长度
  4. Int16 length = (Int16)bytes.Length;
  5. //消息头长度
  6. byte[] lengthBytes = BitConverter.GetBytes(length);
  7. //发送的包体 = 消息头 + 消息体
  8. byte[] sendBytes = lengthBytes.Concat(bytes).ToArray();
  9. //发送
  10. socket.Send(sendBytes);

接收端:


  
  1. //接收数据缓冲区
  2. byte[] readBuffer = new byte[1024];
  3. //接收缓冲区的数据长度
  4. int bufferCount = 0;

bufferCount用于记录缓冲区中的有效数据长度,BeginReceive从缓冲区bufferCount的位置开始写入,缓冲区长度为1024,那么可写入的剩余量为1024 - bufferCount


  
  1. socket.BeginReceive(readBuffer, //接收缓冲区
  2. bufferCount, //开始位置
  3. 1024 - bufferCount, //最多读取的数据长度
  4. 0, //标志位
  5. ReceiveCallback, //接收数据回调函数
  6. socket);

在收到新数据后,需要在回调函数中更新bufferCount,以便在下次接收数据时,写入到缓冲区中有效数据的后面。


  
  1. Socket socket = (Socket)ar.AsyncState;
  2. //接收数据的长度
  3. int count = socket.EndReceive(ar);
  4. bufferCount += count;

因为使用了Int16表示消息长度,所以缓冲区中至少有2个字节以上的数据时才去读取并处理,如果小于2,不足以解析出长度信息,如果大于2但小于消息长度+2,表示不足以读取到完整消息。


  
  1. if (bufferCount <= 2) return;
  2. Int16 length = BitConverter.ToInt16(readBuffer, 0);
  3. if (bufferCount < length + 2) return;
  4. //代码执行到此处表示已经有完整的消息,进行处理
  5. string content = Encoding.UTF8.GetString(readBuffer, 2, length);

完整消息读取后,将缓冲区的后续数据向前移位,更新缓冲区。


  
  1. int startIndex = 2 + length;
  2. int count = bufferCount - startIndex;
  3. Array.Copy(readBuffer, startIndex, readBuffer, 0, count);
  4. bufferCount -= startIndex;

文章来源: coderz.blog.csdn.net,作者:CoderZ1010,版权归原作者所有,如需转载,请联系作者。

原文链接:coderz.blog.csdn.net/article/details/121267426

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。