当前位置: 首页 > news >正文

【Unity开发字典】分包、黏包基本概念和处理逻辑实现

文章目录

    • 什么是分包黏包
    • 如何处理分包黏包
    • 结语

什么是分包黏包

为什么会发生分包黏包
TCP是面向流, 没有边界 ,
一次请求发送的数据量较小,且发送处于同一个TCP等待时间段内, 就会导致黏包.
一次请求过大, 超出了缓存区大小, 就会发生分包, 分几次发送

分包和黏包

  • 正常的理想情况 , 分别发送两个包;
  • 黏包:合并成一个包发送;
  • 分包:拆分成两个或多个包发送;
  • 分包和粘包:消息B进行了分包处理,分包一部分与消息A进行黏包处理。

如何处理分包黏包

这里提供一个简单思路
给所有消息加上一个ID头和信息长度头 . 这里起作用的是信息长度头
信息就会变成[int][int][字节数组] , 长度为 4+4+字节数组长度.

  1. 收到消息后, 直接拼接到,处理分包时缓存的字节数组最后边.
  2. 如果此时分包缓存长度 >= 8 则有完整的头信息, 解析ID ,解析长度
  3. 如果 剩下的字节长度>=信息体长度 解析信息体
  4. 如果已经到了信息末尾, 则重置缓存的字节数组, 结束解读
  5. 如果没有到信息末尾, 继续循环判断, 直到读取所有信息

下面我举个例子, 例如
信息A : 你好世界 ID 为 1001 长度为 12
信息B : 世界你好 ID 为 1001 长度为 12
此时发生了 B分包A黏B包前半部分的情况 ;
1.第一段传来消息为 28个字节
2.第二段传来消息为 12个字节

1.拼接第一个28字节到字节数组后, cacheNum(字节数组长度) = 28 ,nowindex(当前索引位置) = 0
2. 28字节有足够的头文件长度, 则解析头文件, 并且nowindex(当前索引位置) = 8
3. 28字节减去当前索引位置 > 12字节消息体长度, 解读消息体, nowindex(当前索引位置) = 20 , 不等于 28
4. 再次循环 , nowIndex(当前索引位置) = 20; cacheNum(字节数组长度) = 28
5. 判断依旧有足够的头文件 , 解析头文件,nowindex(当前索引位置) = 28
6. 28字节减去当前索引位置 = 0 <12
7. 剩下的信息, 加上头信息, 重新粘贴到分包缓存的第0 位置
8. 等待下次接收消息, 直接拼接到当前数组, 继续循环.

下面是具体的代码, 和相应注释
其中PlayerMsg 是一个自定义,继承了序列化的类, 1001 代表了这个类 , 我会贴上相关代码 , 序列化类的知识在我另一篇文章可以看到

//分包黏包处理的相关代码byte[]cacheBytes=newbyte[1024*1024];//处理分包时缓存的字节数组intcacheNum=0;// 字节数组长度privatevoidReceiveMsg(objectobj){byte[]receiveBytes=newbyte[1024*1024];intreceiveNum=socket.Receive(receiveBytes);HandleReceiveMsg(receiveBytes,receiveNum);}/// <summary>/// 处理接受消息分包黏包问题的方法/// </summary>/// <param name="receiveBytes">接收到的字节数组</param>/// <param name="receiveNum">字节数组长度</param>privatevoidHandleReceiveMsg(byte[]receiveBytes,intreceiveNum){intmsgID=0;//信息ID头intmsgLength=0;//信息长度头intnowIndex=0;//当前index//收到消息后, 应该看看之前有没有缓存的 如果有的话直接拼接到后边receiveBytes.CopyTo(cacheBytes,cacheNum);cacheNum+=receiveNum;while(true){//每次将长度设为-1 避免上一次解析的数据, 影响这一次的判断msgLength=-1;//处理一条消息 , 如果字节数组包含完整的信息头if(cacheNum-nowIndex>=8){//解析IDmsgID=BitConverter.ToInt32(cacheBytes,nowIndex);nowIndex+=4;//解析长度msgLength=BitConverter.ToInt32(cacheBytes,nowIndex);nowIndex+=4;}//如果字节数组长度 - 当前index位置 剩下的信息 大于等于 信息体长度if(cacheNum-nowIndex>=msgLength&&msgLength!=-1){//解析消息体BaseMsgbaseMsg=null;switch(msgID){case1001:PlayerMsgmsg=newPlayerMsg();msg.Reading(cacheBytes,nowIndex);baseMsg=msg;break;}if(baseMsg!=null)receiveQueue.Enqueue(baseMsg);//将接受到的放到接受消息队列nowIndex+=msgLength;//当前index位置 向后移动信息长度if(nowIndex==cacheNum){//如果当前index位置已经到了信息末尾 , 则重置字节数组长度,结束循环cacheNum=0;break;}}else{//如果剩下的信息, 比信息体长度短, 则证明分包if(msgLength!=-1)nowIndex-=8;//如果进行了 id和长度的解析 但是 没有成功解析消息体 那么我们需要减去nowIndex移动的位置//拷贝 到分包缓存 ,从当前位置 到分包缓存 的第0位置 要复制多少Array.Copy(cacheBytes,nowIndex,cacheBytes,0,cacheNum-nowIndex);cacheNum-=nowIndex;//更改分包字节数组长度break;}}}

PlayerMsg 相关代码 (相应知识可以看另一篇文章 :序列化基类)

usingSystem.Collections;usingSystem.Collections.Generic;usingUnityEngine;publicclassPlayerMsg:BaseMsg{publicintplayerID;publicPlayerDataplayerData;publicoverridebyte[]Writing(){intindex=0;byte[]bytes=newbyte[GetBytesNum()];WriteInt(bytes,GetID(),refindex);WriteInt(bytes,GetBytesNum()-8,refindex);//设置消息体长度去掉识别头和长度头WriteInt(bytes,playerID,refindex);WriteData(bytes,playerData,refindex);returnbytes;}publicoverrideintReading(byte[]bytes,intbeginIndex=0){//反序列化不需要解析ID 因为收到消息的那一方收到就已经解析出来 , 才能判断用哪一个类的反序列化intindex=beginIndex;playerID=ReadInt(bytes,refindex);playerData=ReadData<PlayerData>(bytes,refindex);returnindex-beginIndex;}publicoverrideintGetBytesNum(){//识别ID长度+ 信息长度 + ID长度 + 数据长度return4+4+4+playerData.GetBytesNum();}publicoverrideintGetID(){return1001;}}

BaseMsg相关代码 (相应知识可以看另一篇文章 :序列化基类)

usingSystem.Collections;usingSystem.Collections.Generic;usingUnityEngine;publicclassBaseMsg:BaseData{publicoverrideintGetBytesNum(){thrownewSystem.NotImplementedException();}publicoverrideintReading(byte[]bytes,intbeginIndex=0){thrownewSystem.NotImplementedException();}publicoverridebyte[]Writing(){thrownewSystem.NotImplementedException();}publicvirtualintGetID(){return0;}}

结语

分包黏包逻辑不复杂, 这里提供了一个简单的思路, 可以在工作学习中, 进一步优化方法

http://www.gsyq.cn/news/1395735.html

相关文章:

  • 别再为STM32串口打印发愁了!HAL库下三种printf重定向方案实测对比(含MicroLIB配置)
  • 基于Transformer的多粒度序列生成:攻克层次化图像分类两大难题
  • 离散模型解析嵌入式束缚态与法诺共振:从原理到光子器件设计
  • AI提示词大师:安装与配置,反推、扩写、词库管理,告别四处翻找,所有提示词尽在掌握。
  • Realtek r8125 DKMS驱动:Linux 2.5G网卡自动适配终极指南
  • 前沿话题:深度学习、3DGS、语义SLAM与多传感器融合
  • 2026触摸屏PLC一体机品牌市场口碑排行榜深度解析
  • GLM-5.1 高速版:400 tokens/s 刷新全球大模型速度上限
  • 专业Windows 11系统优化:使用Win11Debloat实现高效性能与隐私保护
  • 别再对着空白文档发呆了!书匠策AI让你的毕业论文从“一片空白“到“初稿落地“只需十分钟
  • 绿电直连+微电网+虚拟电厂+源网荷储:未来电力系统的四大支柱
  • 不止于GUI:用Intel MAS命令行在Windows上批量自动化获取多块NVMe SSD信息
  • 支持4K/60fps长时序生成,原生多模态对齐,Sora 2正式版技术白皮书关键参数逐条拆解,不看必踩交付雷区
  • 2026徐州黄金回收深度指南:品类定价全解析+5家靠谱服务商+避坑实操技巧 - 寻茫精选
  • BilibiliDown终极指南:如何免费下载B站高清视频和音频
  • 告别脚本混乱!用Playwright+Pytest+Yaml+Allure搭建可维护的UI自动化框架(附完整源码)
  • 别再手动敲BibTeX了!用Zotero一键搞定IEEE格式参考文献(附期刊/会议/书籍模板)
  • SNK施努卡驱动机构总成半自动装配线:人工与自动化协同解决方案
  • 别再折腾桥接了!用VirtualBox的Microsoft环回适配器搞定虚拟机与宿主机互访(Win10/11实测)
  • 你的第一台无线遥控器选对摇杆了吗?深入对比STM32F103的滑动变阻器摇杆与霍尔摇杆,附实测波形与代码
  • AI大模型不够聪明?别慌!这个“信息补给站“让它在你的工作中大放异彩!
  • Burp Suite HTTPS抓包失败的根源与全平台CA证书配置指南
  • 如何高效获取网盘直链下载地址:完整实战指南
  • 收藏!211本科985硕拿下淘天AI二面,无代码考察,这些是关键!小白程序员必备学习指南
  • 部队营区信息化管理系统:联管联控一体化
  • YOLOv8密集行人识别检测系统(项目源码+YOLO数据集+模型权重+UI界面+python+深度学习+环境配置)
  • m4s-converter:解锁B站缓存视频的终极方案,让珍贵内容永不消失
  • 北京理工大学论文格式终极解决方案:BIThesis LaTeX模板完整指南
  • Uncle小说阅读器:一站式PC端数字图书馆解决方案
  • 华为“韬(τ)定律”深度解读:后摩尔时代芯片设计的新范式