拒绝粘包!基于 C++ 手把手带你实现一个自定义 LV 协议
目录为什么需要这个协议解决粘包拆解“快递面单” 协议结构1. Length总长度—— 4字节固定2. MType消息类型—— 4字节固定3. IDLengthID长度—— 4字节固定4. MID消息ID—— 长度可变5. Body消息体—— 长度可变实战演练 —— 接收方如何拆包裹代码实战 —— 怎么打包怎么拆包第一步明确图片里的协议结构第二步发送方打包拼字符串第三步接收方拆包切字符串第四步解决粘包和半包核心中的核心第五步完整测试把前面的 pack 和 unpack 都拼起来首先我们把这个过程想象成“寄快递”。网络通信就像是一条传送带发送方要把东西传给接收方但传送带是连续不断的接收方怎么知道哪一堆东西是一个完整的包裹呢这个协议就是为了解决这个问题而设计的“快递面单规范”为什么需要这个协议解决粘包假设你在网上买了两件商品商家用两个纸箱装好发给你。但因为快递公司传送带的原因你收到的时候可能是粘包两个纸箱被胶带死死缠在一起变成了一个巨大的箱子。半包一个箱子在路上破了你先收到了半个箱子第二天才收到另外半个。计算机里的TCP协议就是这条“传送带”。它只负责把数据源源不断地送过去但不保证边界。所以我们需要一个约定在每个纸箱的最外面贴一张固定格式的“快递面单”。只要看到面单就知道这个箱子有多大里面装的是什么拆解“快递面单” 协议结构图片中间那个长条图就是一个完整的“快递面单”。我们从左到右看1. Length总长度—— 4字节固定这是什么这是面单的第一行写着“这个包裹总共有多重多少字节”。为什么固定4字节因为4个字节可以表示很大的数字最大能表示40多亿足够描述绝大多数包裹的大小了。怎么用接收方只要先读这4个字节就知道“哦原来后面还有100个字节的数据那我就死等直到收满100个字节我才算收完这一个包裹。”2. MType消息类型—— 4字节固定这是什么这是面单的第二行写着“这个包裹是干什么用的”。为什么需要因为网络通信很复杂有的是请求别人办事RPC调用有的是订阅新闻发布/订阅有的是注册服务。接收方拿到包裹后得先看类型才知道要交给哪个部门去处理。例子比如MType1代表RPC请求MType2代表RPC响应。3. IDLengthID长度—— 4字节固定这是什么这是面单的第三行写着“我的快递单号有多长”。为什么需要因为后面的“快递单号MID”长度是不固定的。有的单号长有的单号短。所以必须先用一个固定的4字节告诉接收方“单号占了多少个字节”。4. MID消息ID—— 长度可变这是什么这是“快递单号”。作用用来唯一标识这一个包裹。比如你发了3个请求给服务器服务器回了3个响应你怎么知道哪个响应对应哪个请求就是靠这个MID来匹配的。长度由前面的IDLength决定。比如IDLength8那这里就占8个字节。5. Body消息体—— 长度可变这是什么这是“包裹里真正装的东西”。作用也就是实际要传输的业务数据。比如你调用一个“计算加法”的函数Body里就装着11这个参数。长度它的长度 总长度Length - 面单上其他所有字段的长度实战演练 —— 接收方如何拆包裹假设你是一个接收方收到了这串数据你该怎么解析先读前4个字节Length你一看值是100。你的心里活动好的这个包裹总共有100个字节。后面还有96个字节因为我已经读了4个了。接着读4个字节MType你一看值是1。你的心里活动哦这是一个RPC请求我得准备去处理业务逻辑。再读4个字节IDLength你一看值是8。你的心里活动好的这个包裹的单号有8个字节长。接着读8个字节MID你读到了具体的单号比如10086。你的心里活动记住这个单号等下回复的时候要带上。最后读剩下的所有字节Body算一下总共100字节我已经用了 444820 字节还剩80字节。你就把这80个字节全读出来。你的心里活动这就是真正的数据内容了。完美你成功地从连续不断的网络数据流中精准地切分出了完整的一条消息代码实战 —— 怎么打包怎么拆包第一步明确图片里的协议结构根据图片我们的数据包长这样[Length 4字符][MType 4字符][IDLength 4字符][MID 变长][Body 变长]Length后面所有东西的总长度固定 4 字符MType消息类型固定 4 字符IDLengthMID 的长度固定 4 字符MID消息 ID长度由 IDLength 决定Body正文剩下的都是它第二步发送方打包拼字符串#include iostream #include string using namespace std; // 辅助函数把数字变成 4 位字符串。比如 5 变成 0005 string to4(int num) { string s to_string(num); while (s.length() 4) s 0 s; return s; } // 打包函数 string pack(int mtype, string mid, string body) { // 1. 算 IDLengthMID 的长度 int idLen mid.length(); // 2. 算 Length后面所有东西的总长度 // 后面有MType(4) IDLength(4) MID(idLen) Body(body.length()) int length 4 4 idLen body.length(); // 3. 按顺序拼接这就是打包 string packet to4(length) to4(mtype) to4(idLen) mid body; return packet; } int main() { // 发送一条消息类型1ID是1001正文是Hello string packet pack(1, 1001, Hello); cout 打包结果: packet endl; return 0; }运行结果我们来拆解一下这个结果0017→ Length后面总共 17 个字符0001→ MType类型 10004→ IDLengthMID 有 4 个字符1001→ MIDHello→ Body第三步接收方拆包切字符串/* 模拟Lv协议 打包拆包 */ /* 数据包长这样子 [Length 4字符][MType 4字符][IDLength 4字符][MID 变长][Body 变长] */ #include iostream #include string using namespace std; // 辅助函数把数字转化为4为字符串4转为字符串0004 string to4(int num){ // 转换字符串 string s to_string(num); while(s.length() 4){ // 左边补0 s 0 s; } return s; } // 打包函数 string pack(int mtype, string mid, string body){ // 1、算IDLength长度(MID 的长度) int idLen mid.size(); // 2、算length后面所有东西的总长度 int length 4 4 idLen body.size(); // 3、按顺序拼接这就是打包 string packet to4(length) to4(mtype) to4(idLen) mid body; return packet; } void unpack(string packet){ // 1、切除前4个字符这是Length string lenStr packet.substr(0,4); int length stoi(lenStr); // 2、切除前4个字符这是MType string mtypeStr packet.substr(4,4); int mtype stoi(mtypeStr); // 3、切除前4个字符这是LDLength string idLenStr packet.substr(8,4); int idLen stoi(idLenStr); // 4、从12个字符开始切idLen个字符这是MID string mid packet.substr(12,idLen); // 5. 剩下的全是 Body string body packet.substr(12 idLen); // 打印结果 cout 解析结果 endl; cout Length length endl; cout MType mtype endl; cout IDLength idLen endl; cout MID mid endl; cout Body body endl; } int main() { // 1、发送方打包 string packet pack(1, 1001, hello); cout 打包结果 packet endl; // 2、解包 unpack(packet); return 0; }运行结果第四步解决粘包和半包核心中的核心现实中网络不会乖乖地一条一条发。我们用一个大口袋buffer来装数据。string buffer ; // 大口袋 // 处理口袋里的数据 void processBuffer() { while (true) { // 1. 口袋里连 4 个字符Length都不够等下次吧 if (buffer.length() 4) break; // 2. 偷看前 4 个字符算出整个包应该有多长 int length stoi(buffer.substr(0, 4)); // 3. 整个包的实际总长度 4Length自己 length后面的东西 int totalLen 4 length; // 4. 口袋里够不够一个完整的包 if (buffer.length() totalLen) { // 够了切出完整的包 string packet buffer.substr(0, totalLen); // 从口袋里删掉已经处理的部分 buffer.erase(0, totalLen); // 解析这个包用上面的 unpack 逻辑 cout ✅ 解析出一条完整消息: packet endl; } else { // 不够半包等下次数据来 cout ⏳ 数据不完整等待更多数据... endl; break; } } } // 模拟网络数据到达 void onDataReceived(string data) { cout 收到数据: data endl; buffer data; // 丢进口袋 processBuffer(); // 尝试处理 }第五步完整测试把前面的 pack 和 unpack 都拼起来/* 模拟Lv协议 打包拆包 */ /* 数据包长这样子 [Length 4字符][MType 4字符][IDLength 4字符][MID 变长][Body 变长] */ #include iostream #include string using namespace std; // 辅助函数把数字转化为4为字符串4转为字符串0004 string to4(int num){ // 转换字符串 string s to_string(num); while(s.length() 4){ // 左边补0 s 0 s; } return s; } // 打包函数 string pack(int mtype, string mid, string body){ // 1、算IDLength长度(MID 的长度) int idLen mid.size(); // 2、算length后面所有东西的总长度 int length 4 4 idLen body.size(); // 3、按顺序拼接这就是打包 string packet to4(length) to4(mtype) to4(idLen) mid body; return packet; } void unpack(string packet){ // 1、切除前4个字符这是Length string lenStr packet.substr(0,4); int length stoi(lenStr); // 2、切除前4个字符这是MType string mtypeStr packet.substr(4,4); int mtype stoi(mtypeStr); // 3、切除前4个字符这是LDLength string idLenStr packet.substr(8,4); int idLen stoi(idLenStr); // 4、从12个字符开始切idLen个字符这是MID string mid packet.substr(12,idLen); // 5. 剩下的全是 Body string body packet.substr(12 idLen); // 打印结果 cout 解析结果 endl; cout Length length endl; cout MType mtype endl; cout IDLength idLen endl; cout MID mid endl; cout Body body endl; } // 解决粘包和半包 string buffer ; // 处理口袋里的数据 void processBuffer(){ while(true){ // 1、口袋里4个字段都不够等下次 if(buffer.length() 4) break; // 2、偷看一下4个字符算出这个包有多长 int length stoi(buffer.substr(0,4)); // 3、整个包的实际总长度 int totalLen 4 length; // 4、口袋能不能装一个完整的包 if(buffer.length() totalLen){ // 够了切出完整的包 string packet buffer.substr(0,totalLen); // 口袋里删掉已经处理的部分 buffer.erase(0, totalLen); // 解析这个包 cout 解析出一条完整消息 endl; unpack(packet); }else { // 解决半包 // 不够,半包等下次数据来 cout 数据不完整等待更多数据 endl; break; } } } // 模拟网络数据到达 void on(string data){ cout 收到数据 data endl; buffer data; // 丢进口袋 processBuffer(); //尝试处理 } int main() { // 粘包和贴包 string packet1 pack(1, 1001, hello); string packet2 pack(1, 1002, ok); cout 场景一正常处理 endl; on(packet1); cout 场景二粘包一起发 endl; on(packet1 packet2); cout endl 场景二粘包一起发 endl; on(packet1.substr(0,10)); on(packet1.substr(10)); return 0; }总结三句话再次深刻理解LV协议打包Length MType IDLength MID Body用拼起来。拆包先读前 4 个字符拿到Length就知道后面还有多少字符。处理粘包/半包数据先丢进buffer循环检查buffer够不够一个完整的包4 Length够了就切出来不够就等核心公式完整包长度 4 Length

相关新闻

卫浴工厂MES整体解决方案:从需求分析到生产排产落地

卫浴工厂MES整体解决方案:从需求分析到生产排产落地

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/6 11:53:20 阅读更多 →
DRV8818PWPR+TM4C129XNCZAD工业步进驱动硬核实践

DRV8818PWPR+TM4C129XNCZAD工业步进驱动硬核实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/4 1:24:12 阅读更多 →
C++语言原理与实践(十):vector类的底层实现

C++语言原理与实践(十):vector类的底层实现

本篇目标&#xff1a; 掌握vector部分接口的简单实现一.vector类的底层实现1.三个指针首先通过对前面vector的使用&#xff0c;我们就已经知道了vector可以实例化出int&#xff0c;vector<int>&#xff0c;string等各种类型&#xff0c;无论是内置类型&#xff0c;还是自…

2026/11/6 22:37:21 阅读更多 →

最新新闻

Electron桌面应用开发实战:从环境搭建到打包发布全流程

Electron桌面应用开发实战:从环境搭建到打包发布全流程

做前端这些年&#xff0c;我大部分时间都在跟浏览器标签页打交道&#xff0c;直到第一次把桌面应用跑起来&#xff0c;才发现原来自己一直被限制在地址栏和刷新按钮围起来的框框里。Electron这个词&#xff0c;最开始是在某段时间频繁出现在同事的讨论里&#xff0c;一个用HTML…

2026/11/10 23:32:01 阅读更多 →
滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

简介&#xff1a;亚太天能T109滑盖智能锁产品说明书是一份PDF格式的官方技术文档&#xff0c;主要面向智能锁安装人员、终端用户及售后维护者&#xff0c;帮助快速掌握产品安装、配置与常见故障处理。全包仅1个PDF文件&#xff0c;大小4.37MB&#xff0c;内容结构清晰&#xff…

2026/11/10 23:32:03 阅读更多 →
PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

上个月我在调试一台高速AGV的驱动系统&#xff0c;整车要求空载跑到8m/s&#xff0c;电机转速已经拉到额定点&#xff0c;驱动器的输出电压也顶到了母线电压极限附近。再往上踩速度指令&#xff0c;转速纹丝不动&#xff0c;三相电流反而开始震荡。那会儿我的第一反应就是&…

2026/11/11 2:31:33 阅读更多 →
C++核心知识点:从内存管理到STL的实战指南

C++核心知识点:从内存管理到STL的实战指南

C 这门语言我前前后后折腾了十几年&#xff0c;用它写过嵌入式驱动、做过游戏服务端&#xff0c;也搭过基础组件库。每次看到新人一头扎进"C 核心知识点"这个搜索词&#xff0c;我其实挺感慨的——这门语言的难点从来不是语法多&#xff0c;而是知识点之间的耦合太深…

2026/11/10 16:14:15 阅读更多 →
T109滑盖智能锁怎么装?安装调试与故障排查全攻略

T109滑盖智能锁怎么装?安装调试与故障排查全攻略

简介&#xff1a;亚太天能T109滑盖智能锁产品说明书以PDF格式发布&#xff0c;适合智能锁用户、安装人员与售后维修人员阅读。内容围绕T109系列指纹密码刷卡锁展开&#xff0c;先介绍前面板、后面板、锁体、机械钥匙、电池盒盖等部件名称与功能&#xff0c;再给出完整安装步骤&…

2026/11/10 12:51:50 阅读更多 →
LLM知识降熵:从信息垄断到理解权下放

LLM知识降熵:从信息垄断到理解权下放

你有没有过这种经历&#xff1a;翻开源码注释、论文摘要或者一份专业报告&#xff0c;每个字都认识&#xff0c;连在一起却像天书。想找人聊聊&#xff0c;身边要么没人懂&#xff0c;要么懂的懒得跟你解释。后来我试着把同一段内容丢给一个大语言模型&#xff0c;几秒钟之后&a…

2026/11/10 23:32:10 阅读更多 →

日新闻

周新闻

月新闻