手写序列化与反序列化
目录
前言
一、核心设计目标
二、协议基础:分隔符与编码解码
1. 关键常量
2. 编码函数 Encode:生成可传输的完整报文
3. 解码函数 Decode:从流数据中提取完整报文
三、结构化数据载体:Request与Response类
1. Request类:请求数据封装
2. Response类:响应数据封装
四、对象创建:Factory工厂类
核心接口
五、两种协议的对比与切换
六、核心作用与应用场景
总结
前言
本章的主要目的是体会具体序列化和反序列化的过程。
- 本质:就是对字符串的处理
- 实际情况肯定会更复杂,但是序列化与反序列化已经有很多的现成解决方案了。我们只是为了方便理解有一次手写的经历就够了。。
C++ #pragma once #include <iostream> #include <memory> #include <jsoncpp/json/json.h> // #define SelfDefine 1 namespace Protocol { // 问题 // 1. 结构化数据的序列和反序列化 // 2. 还要解决用户区分报文边界 --- 数据包粘报问题 // 讲法 // 1. 自定义协议 // 2. 成熟方案序列和反序列化 // 总结: // 我们今天定义了几组协议呢??我们可以同时存在多个协议吗???可以// "protocol_code\r\nlen\r\nx op y\r\n" : \r\n 不属于报文的一部分,约定 const std::string ProtSep = " "; const std::string LineBreakSep = "\r\n"; // "len\r\nx op y\r\n" : \r\n 不属于报文的一部分,约定 std::string Encode(const std::string &message) { std::string len = std::to_string(message.size()); std::string package = len + LineBreakSep + message + LineBreakSep; return package; } // "len\r\nx op y\r\n" : \r\n 不属于报文的一部分,约定 // 我无法保证 package 就是一个独立的完整的报文 // "l // "len // "len\r\n // "len\r\nx // "len\r\nx op // "len\r\nx op y // "len\r\nx op y\r\n" // "len\r\nx op y\r\n""len // "len\r\nx op y\r\n""len\n // "len\r\nx op // "len\r\nx op y\r\n""len\nx op y\r\n" // "len\r\nresult code\r\n""len\nresult code\r\n" bool Decode(std::string &package, std::string *message) { // 除了解包,我还想判断报文的完整性, 能否正确处理具有"边界"的报文 auto pos = package.find(LineBreakSep); if (pos == std::string::npos) return false; std::string lens = package.substr(0, pos); int messagelen = std::stoi(lens); int total = lens.size() + messagelen + 2 * LineBreakSep.size(); if (package.size() < total) return false; // 至少 package 内部一定有一个完整的报文了! *message = package.substr(pos + LineBreakSep.size(), messagelen); package.erase(0, total); return true; } class Request { public: Request() : _data_x(0), _data_y(0), _oper(0) { } Request(int x, int y, char op) : _data_x(x), _data_y(y),_oper(op) { } void Debug() { std::cout << "_data_x: " << _data_x << std::endl; std::cout << "_data_y: " << _data_y << std::endl; std::cout << "_oper: " << _oper << std::endl; } void Inc() { _data_x++; _data_y++; } // 结构化数据->字符串 bool Serialize(std::string *out) { #ifdef SelfDefine // 条件编译 *out = std::to_string(_data_x) + ProtSep + _oper + ProtSep + std::to_string(_data_y); return true; #else Json::Value root; root["datax"] = _data_x; root["datay"] = _data_y; root["oper"] = _oper; Json::FastWriter writer; *out = writer.write(root); return true; #endif } bool Deserialize(std::string &in) // "x op y" [) { #ifdef SelfDefine auto left = in.find(ProtSep); if (left == std::string::npos) return false; auto right = in.rfind(ProtSep); if (right == std::string::npos) return false; _data_x = std::stoi(in.substr(0, left)); _data_y = std::stoi(in.substr(right + ProtSep.size())); std::string oper = in.substr(left + ProtSep.size(),right - (left + ProtSep.size())); if (oper.size() != 1) return false; _oper = oper[0]; return true; #else Json::Value root; Json::Reader reader; bool res = reader.parse(in, root); if(res) { _data_x = root["datax"].asInt(); _data_y = root["datay"].asInt(); _oper = root["oper"].asInt(); } return res; #endif } int GetX() { return _data_x; } int GetY() { return _data_y; } char GetOper() { return _oper; } private: // _data_x _oper _data_y // 报文的自描述字段 // "len\r\nx op y\r\n" : \r\n 不属于报文的一部分,约定 // 很多工作都是在做字符串处理! int _data_x; // 第一个参数 int _data_y; // 第二个参数 char _oper; // + - * / % }; class Response { public: Response() : _result(0), _code(0) { } Response(int result, int code) : _result(result), _code(code) { } bool Serialize(std::string *out) { #ifdef SelfDefine *out = std::to_string(_result) + ProtSep + std::to_string(_code); return true; #else Json::Value root; root["result"] = _result; root["code"] = _code; Json::FastWriter writer; *out = writer.write(root); return true; #endif } bool Deserialize(std::string &in) // "_result _code" [){ #ifdef SelfDefine auto pos = in.find(ProtSep); if (pos == std::string::npos) return false; _result = std::stoi(in.substr(0, pos)); _code = std::stoi(in.substr(pos + ProtSep.size()));return true; #else Json::Value root; Json::Reader reader; bool res = reader.parse(in, root); if(res) { _result = root["result"].asInt(); _code = root["code"].asInt(); } return res; #endif } void SetResult(int res) { _result = res; } void SetCode(int code) { _code = code; } int GetResult() { return _result; } int GetCode() { return _code; } private: // "len\r\n_result _code\r\n" int _result; // 运算结果 int _code; // 运算状态 }; // 简单的工厂模式,建造类设计模式 class Factory { public: std::shared_ptr<Request> BuildRequest() { std::shared_ptr<Request> req = std::make_shared<Request>(); return req; } std::shared_ptr<Request> BuildRequest(int x, int y, charop) { std::shared_ptr<Request> req = std::make_shared<Request>(x, y, op); return req; } std::shared_ptr<Response> BuildResponse() { std::shared_ptr<Response> resp = std::make_shared<Response>(); return resp; } std::shared_ptr<Response> BuildResponse(int result, intcode) { std::shared_ptr<Response> req = std::make_shared<Response>(result, code); return req; } }; }一、核心设计目标
- 解决结构化数据传输:将
Request(请求)、Response(响应)这类内存中的结构化数据,转换为可网络传输的字符串(序列化),反之亦然(反序列化)。- 解决粘包 / 拆包问题:通过自定义协议格式,让接收方准确识别每个报文的边界,避免数据混淆。
- 支持多协议灵活切换:通过条件编译(
SelfDefine宏),可在 “自定义简单协议” 和 “JSON 协议” 之间切换。二、协议基础:分隔符与编码解码
这是解决粘包 / 拆包的核心,定义了报文的统一格式。
1. 关键常量
ProtSep = " ":结构化数据内部的字段分隔符(仅自定义协议用)。LineBreakSep = "\r\n":报文边界分隔符,用于区分 “长度字段” 和 “有效载荷”,且不属于有效数据。2. 编码函数
Encode:生成可传输的完整报文
- 功能:将业务数据(
message,如序列化后的请求 / 响应字符串)包装成符合协议的完整报文。- 格式:
长度字符串 + \r\n + 业务数据 + \r\n
- 示例:业务数据为
"3 + 5"(长度 8),编码后为"8\r\n3 + 5\r\n"。- 核心作用:给业务数据添加 “长度头” 和 “边界标识”,让接收方知道何时读完一个完整报文。
3. 解码函数
Decode:从流数据中提取完整报文
- 功能:从接收的流式数据(
package,可能包含多个报文或不完整报文)中,提取出一个完整的业务数据(message)。- 流程:
- 查找第一个
\r\n,提取前面的 “长度字符串” 并转换为整数messagelen。- 计算完整报文的总长度(长度字符串长度 + 业务数据长度 + 2 个
\r\n长度)。- 若当前流数据长度不足总长度,返回
false(报文不完整,等待后续数据)。- 若长度足够,提取业务数据,从流数据中删除已提取的完整报文,返回
true。- 核心作用:准确拆分流式数据中的完整报文,彻底解决粘包 / 拆包问题。
三、结构化数据载体:
Request与Response类封装通信的业务数据,提供序列化(内存→字符串)和反序列化(字符串→内存)接口。
1.
Request类:请求数据封装
- 用途:客户端向服务端发送的计算请求,包含两个运算数和运算符。
- 成员变量:
_data_x:第一个运算数(如 3)。_data_y:第二个运算数(如 5)。_oper:运算符(如+)。- 核心接口:
Serialize:将成员变量转换为字符串(两种协议可选)。
- 自定义协议:
"3 + 5"(用ProtSep分隔字段)。- JSON 协议:
{"datax":3,"datay":5,"oper":"+"}(可读性强,支持复杂结构)。Deserialize:将字符串解析为成员变量,与序列化格式对应。GetX/GetY/GetOper:获取成员变量的接口(封装性)。2.
Response类:响应数据封装
- 用途:服务端向客户端返回的处理结果,包含计算结果和状态码。
- 成员变量:
_result:运算结果(如 8)。_code:状态码(0 = 成功,非 0 = 错误,如 1 = 除零错误)。- 核心接口:
Serialize:将结果和状态码转换为字符串(自定义协议:"8 0";JSON 协议:{"result":8,"code":0})。Deserialize:将字符串解析为成员变量。SetResult/SetCode:设置结果和状态码(服务端用)。四、对象创建:
Factory工厂类采用简单工厂模式,统一管理
Request和Response对象的创建,避免直接new导致的耦合。核心接口
BuildRequest():创建默认Request对象(运算数和运算符为空)。BuildRequest(int x, int y, char op):创建带初始值的Request对象。BuildResponse():创建默认Response对象(结果和状态码为 0)。BuildResponse(int result, int code):创建带初始结果和状态码的Response对象。- 返回值:
std::shared_ptr智能指针,自动管理对象生命周期,避免内存泄漏。五、两种协议的对比与切换
通过
#define SelfDefine 1宏控制协议类型,灵活适配不同场景:
特性 自定义协议( SelfDefine=1)JSON 协议(默认) 数据格式 简洁字符串(如 "3 + 5")标准 JSON 字符串 可读性 低 高 扩展能力(如加字段) 差(需修改字符串解析逻辑) 强(JSON 自动兼容) 传输效率 高(字节少) 略低(带 JSON 格式字符) 适用场景 简单数据、对效率要求高 复杂数据、需调试 六、核心作用与应用场景
这套模块是网络通信的 “数据传输标准”,在之前的 TCP 计算器项目中:
- 客户端:
Request序列化→Encode编码→发送给服务端。- 服务端:接收数据→
Decode解码→Request反序列化→处理计算→Response序列化→Encode编码→返回客户端。- 客户端:接收响应→
Decode解码→Response反序列化→打印结果。总结
该模块通过 “协议编码解码 + 结构化数据序列化 + 工厂模式”,完整解决了网络通信中 “数据怎么传”“边界怎么分”“对象怎么创建” 三个核心问题,代码解耦性强、灵活可扩展,可直接复用在各类 C++ TCP/UDP 通信项目中。
