【网络】序列化和反序列化
🥁作者: 华丞臧.
📕专栏:【网络】
各位读者老爷如果觉得博主写的不错,请诸位多多支持(点赞+收藏+关注)。如果有错误的地方,欢迎在评论区指出。
推荐一款刷题网站 👉 LeetCode刷题网站
文章目录
- 一、应用层
- 二、网络版本计算器
- 2.1 协议定制
- 2.2 协议实现
- encode
- decode
- 定制请求 -- Requset
- 定制响应 -- Response
- 输入参数读取
- 测试
- 2.3 使用第三方库
- 测试
一、应用层
- 程序员写的一个个解决实际问题满足日常需求的网络程序都是在应用层的。
- 协议是一种“约定”,socket api的接口在读写数据时,都是按“字符串”的方式来发送接收的。
- 如果我们要传输一些“结构化数据”,能直接传输吗?肯定是不能直接传输的,结构化数据存在一些问题,比如C语言中结构体需要结构体对齐,C++中类同样需要对齐,会浪费网络资源,并且不同操作系统的大小端在不同编译器下可能不同。
所以对于需要传输结构化数据,我们可以进行约定:
- 定义结构体来表示我们需要交互的信息;
- 发送数据时将这个结构体按照约定的规则转换成字符串,接收到数据的时候再按照相同的规则把字符串转回结构体。
- 序列化:将结构体数据转换成字符串的过程。
- 反序列化:将字符串数据转换回结构化数据的过程。
二、网络版本计算器
在进行结构化数据的网络通信时,需要将数据序列化成字符串再发送给对方,但是对方并不知道传输数据的长度,也不知道如何从字符串的数据中读取结构化的数据;因此在进行网络通信之间,通信的双发需要进行约定,比如:约定如何确定序列化数据的长度以及用什么格式来反序列化字符串。
在这里我们先手写一个定制协议,约定如下:
- 计算器格式通常是:1+2、 2*3、9/3;
- 序列化字符串首部几个字节表示长度;
- 有效载荷通过空格隔开;
- 表示长度的报头和有效载荷通过
\r\n隔开; - 报文之间也是用
\r\n隔开
2.1 协议定制

客户端和服务端代码请看👉序列化和反序列化
#pragma once
#include <iostream>
#include <string>
#include <assert.h>#include "Log.hpp"#define CRLF "\r\n"
#define CRLF_LEN strlen(CRLF)
#define SPACE " "
#define SPACE_LEN strlen(SPACE)
#define OPS "+-*/%"// encode,对整个序列化之后的字符串进行添加长度
//"strlen\r\nXXXXXX\r\n"
std::string encode(std::string &in, uint32_t len)
{}// decode,整个序列化之后的字符串进行提取长度
// 1. 必须具有完整的长度
// 2. 必须具有和len相符合的有效载荷
// 我们才返回有效载荷和len
// 否则,我们就是一个检测函数!
// 9\r\n100 + 200\r\n 9\r\n112 / 200\r\n
std::string decode(std::string &in, uint32_t *len)
{}// 定制请求
class Request
{
public:Request(){}~Request(){}int& getX(){ return x_;}int& getY(){ return y_;}char& getOp(){ return op_;}// 序列化void serialization(std::string *out){}// 反序列化bool deserialization(std::string &in){}void debug(){}
private:// 需要计算的数据int x_; int y_;// 需要计算的种类,"+ - * / %"char op_;
};// 定制响应
class Response
{
public:Response():result_(0),exitCode_(0){}~Response(){}// 序列化void serialization(std::string *out){}// 反序列化bool deserialization(std::string &in){}
public:// 退出状态,0标识运算结果合法,非0标识运算结果是非法的int exitCode_; // 运算结果int result_;
};bool makeReuquest(const std::string &str, Request *req)
{}
2.2 协议实现
encode
按照约定需要在序列化之后的字符串首部加上有效载荷的长度并用\r\n隔开,并且结尾也需要加上\r\n。

// encode,对整个序列化之后的字符串进行添加长度
//"strlen\r\nXXXXXX\r\n"
std::string encode(std::string &in, uint32_t len)
{// "exitCode_ result_"// "len\r\n" "exitCode_ result_\r\n"std::string encodeStr = std::to_string(len);encodeStr += CRLF;encodeStr += in;encodeStr += CRLF;//std::cout << "debug->encode-> " << encodeStr << std::endl;return encodeStr;
}
decode
- 第一步要先提取有效载荷的长度,再根据长度检查字符串中是否含有一个完整的有效载荷;
- 如果具有完整的有效载荷,可以通过长度来提取有效载荷。
- 注意如果需要进行多次提取,需要将读取出的字符串删除。
// decode,整个序列化之后的字符串进行提取长度
// 1. 必须具有完整的长度
// 2. 必须具有和len相符合的有效载荷
// 我们才返回有效载荷和len
// 否则,我们就是一个检测函数!
// 9\r\n100 + 200\r\n 9\r\n112 / 200\r\n
std::string decode(std::string &in, uint32_t *len)
{assert(len);// 1. 确认是否是一个包含len的有效字符串*len = 0;size_t pos = in.find(CRLF);if(pos == std::string::npos)return "";// 2. 提取长度std::string strLen = in.substr(0, pos);int intLen = atoi(strLen.c_str());// 3. 确认有效载荷也是符合要求的int surplus = in.size() - 2 * CRLF_LEN - pos;if(surplus < intLen){return "";}// 4. 确认有完整的报文结构std::string package = in.substr(pos + CRLF_LEN, intLen);*len = intLen;// 5. 将当前报文完整的从in中全部移除掉int remLen = strLen.size() + 2 *CRLF_LEN + package.size();in.erase(0, remLen);// 6. 正常返回return package;
}
定制请求 – Requset
定制请求,就是给请求方一个存放结构化数据的空间,请求方可以通过定制好的协议进行序列化得到字符串,然后就可以与服务端进行网络通信了;Request中的反序列化通常是给服务端提取请求方的结构化数据,所以服务端可以根据结构化数据向请求方进行响应。
序列化:
- 序列化出的数据属于有效载荷,因此按照协议需要使用空格隔开;
反序列化:
- 按照协议规定,结构化的数据在字符串中是通过空格隔开的,所以我们可以根据字符串中的两个空格将数据结构化提取出来。
#define CRLF "\r\n"
#define CRLF_LEN strlen(CRLF)
#define SPACE " "
#define SPACE_LEN strlen(SPACE)// 定制请求
class Request
{
public:Request(){}~Request(){}int& getX(){ return x_;}int& getY(){ return y_;}char& getOp(){ return op_;}// 序列化void serialization(std::string *out){// 100 + 200 // "100 + 200"std::string dateOne = std::to_string(x_);std::string dateTwo = std::to_string(y_);std::string dateOp = std::to_string(op_);*out = dateOne;*out += SPACE;*out += op_;*out += SPACE;*out += dateTwo;//std::cout << *out << std::endl;}// 反序列化bool deserialization(std::string &in){//std::cout << 0 << std::endl;// 100 + 200 size_t spaceOne = in.find(SPACE);if(spaceOne == std::string::npos) return false;//std::cout << 1 << std::endl;size_t spaceTwo = in.rfind(SPACE);if(spaceTwo == std::string::npos) return false;//std::cout << 2 << std::endl;std::string dateOne = in.substr(0, spaceOne);std::string dateTwo = in.substr(spaceTwo + SPACE_LEN);std::string dateOp = in.substr(spaceOne + SPACE_LEN, spaceTwo - (spaceOne + SPACE_LEN));if(dateOp.size() != 1) {printf("%d:%c\n", dateOp.size(), dateOp.c_str());return false;}//std::cout << 3 << std::endl;// 转成内部成员x_ = atoi(dateOne.c_str());y_ = atoi(dateTwo.c_str());op_ = atoi(dateOp.c_str());return true;}void debug(){std::cout << "x_" << x_ << std::endl;std::cout << "y_" << y_ << std::endl;std::cout << "op_" << op_ << std::endl;}
private:// 需要计算的数据int x_; int y_;// 需要计算的种类,"+ - * / %"char op_;
};
定制响应 – Response
响应是服务端在对客户端的请求提供服务之后给客户端返回的结果,所以Response需要能够存放服务结果以及发生错误时的状态码;而结果和错误码是数据化结构,所以需要序列化之后在传输给客户端,客户端在拿到响应后需要进行反序列化拿到结果和状态码。序列胡和反序列化过程与定制请求类似。
#define CRLF "\r\n"
#define CRLF_LEN strlen(CRLF)
#define SPACE " "
#define SPACE_LEN strlen(SPACE)
// 定制响应
class Response
{
public:Response():result_(0),exitCode_(0){}~Response(){}// 序列化void serialization(std::string *out){// "exitCode_ result_"std::string ec = std::to_string(exitCode_);std::string res = std::to_string(result_);*out = ec;*out += SPACE;*out += res;}// 反序列化bool deserialization(std::string &in){// "nextiCode_ result_"size_t pos = in.find(SPACE);if(pos == std::string::npos) return false;std::string ec = in.substr(0, pos);std::string res = in.substr(pos + SPACE_LEN);exitCode_ = atoi(ec.c_str());result_ = atoi(res.c_str());return true;}public:// 退出状态,0标识运算结果合法,非0标识运算结果是非法的int exitCode_; // 运算结果int result_;
};
输入参数读取
我们在客户端输入时是按照字符串的格式输入的,所以为了能够使用定制协议,需要进行反序列化即将输入的字符串数据按照协议中的结构化数据提取出来。
#define OPS "+-*/%"bool makeReuquest(const std::string &str, Request *req)
{// 123+1 1*1 1/1char strtmp[1024];snprintf(strtmp, sizeof strtmp, "%s", str.c_str());char *left = strtok(strtmp, OPS);if (!left)return false;char *right = strtok(nullptr, OPS);if (!right)return false;char mid = str[strlen(left)];req->getX() = atoi(left);req->getY() = atoi(right);req->getOp() = mid;return true;
}
测试
- IP:127.0.0.1 --》测试

- IP:119.91.213.117 --》测试

2.3 使用第三方库
大佬也写了第三方库来支持序列化和反序列化,在这里我使用的jsoncpp,安装方式如下:
sudo yum install -y jsoncpp-devel
第三方库的使用较之我们自己实现无疑方便了很多,不用自己造轮子。
#pragma once
#include <iostream>
#include <string>
#include <assert.h>
#include <jsoncpp/json/json.h>#include "Log.hpp"#define CRLF "\r\n"
#define CRLF_LEN strlen(CRLF)
#define SPACE " "
#define SPACE_LEN strlen(SPACE)
#define OPS "+-*/%"// 定制请求
class Request
{
public:Request(){}~Request(){}int& getX(){ return x_;}int& getY(){ return y_;}char& getOp(){ return op_;}// 序列化void serialization(std::string *out){//json// 1. Value对象,万能对象// 2. json是基于KV// 3. json有两套操作方法// 4. 序列化的时候,会将所有的数据内容,转换成为字符串Json::Value root; //,万能对象,任意类型root["x"] = x_;root["y"] = y_;root["op"] = op_;Json::FastWriter fw; //Json::StyledWriter fw;*out = fw.write(root); //序列化}// 反序列化bool deserialization(std::string &in){Json::Value root;Json::Reader rd;rd.parse(in, root);x_ = root["x"].asInt();y_ = root["y"].asInt();op_ = root["op"].asInt();return true;}void debug(){std::cout << "x_" << x_ << std::endl;std::cout << "y_" << y_ << std::endl;std::cout << "op_" << op_ << std::endl;}
private:// 需要计算的数据int x_; int y_;// 需要计算的种类,"+ - * / %"char op_;
};// 定制响应
class Response
{
public:Response():result_(0),exitCode_(0){}~Response(){}// 序列化void serialization(std::string *out){Json::Value root;root["exitCode"] = exitCode_;root["result"] = result_;Json::FastWriter fw;*out = fw.write(root); //序列化}// 反序列化bool deserialization(std::string &in){Json::Value root;Json::Reader rd;rd.parse(in, root);exitCode_ = root["exitCode"].asInt();result_ = root["result"].asInt();return true;}public:// 退出状态,0标识运算结果合法,非0标识运算结果是非法的int exitCode_; // 运算结果int result_;
};
测试
- 使用
Json::FastWriter fw;

- 使用
Json::StyledWriter fw;

相关文章:
【网络】序列化和反序列化
🥁作者: 华丞臧. 📕专栏:【网络】 各位读者老爷如果觉得博主写的不错,请诸位多多支持(点赞收藏关注)。如果有错误的地方,欢迎在评论区指出。 推荐一款刷题网站 👉 LeetCode刷题网站 文章…...
【代码随想录训练营】【Day32】第八章|贪心算法|122.买卖股票的最佳时机II |55. 跳跃游戏|45.跳跃游戏II
买卖股票的最佳时机II 题目详细:LeetCode.122 买卖股票的最佳时机,怎么都能够想出来个思路,假如我们每天都能预知明天的股票是涨是降,那么贪心策略就是在涨之前买股票,在降的前一天卖掉,这就是买卖股票的…...
constexpr 和 常量表达式
👀👀常量表达式 常量表达式是指值不会改变并且在编译过程就能得到计算结果的表达式。 字面值属于常量表达式,用常量表达式初始化的const对象也是常量表达式。 那么是什么来就决定是不是常量表达式呢?一个对象是不是常量表达式主要…...
Vue响应式原理————Object.defineProperty()和proxy的用法分享
Vue框架一个比较核心的功能就是我们的数据是响应式的,这样我们在修改数据的时候,页面会自动帮我们更新,那么想要实现这个功能就要实现对一个数据的劫持,即在取值和设置值的同时我们能够检测到即数据劫持。vue2响应式的实现原理所依…...
CSDN 编程竞赛三十四期题解
竞赛总览 CSDN 编程竞赛三十四期:比赛详情 (csdn.net) 本期的题目和第三十一期竞赛的题目竟然高度重合,真不知道该写点什么了。 不过,上次那道测试数据有bug的题已经修复了,答题过程挺顺利的,没有遇到新的问题。 竞…...
C#教程06 运算符
文章目录 一、算术运算符加法运算符(+)减法运算符(-)乘法运算符(*)除法运算符(/)二、逻辑运算符与运算符(&&)或运算符(||)非运算符(!)三、比较运算符等于运算符(==)大于运算符(>)小于运算符(<)大于等于运算符(>=)小于等于运算符(<=…...
软测入门(六)pytest单元测试
pytest pytest是python的一种单元测试框架,同自带的unit test测试框架类似,但pytest更简洁高效。 单元测试: 测试 函数、类、方法能不能正常运行测试的结果是否符合我们的预期结果 安装 pip install -U pytest基本使用 通过pytest包使用…...
经典分类模型回顾5—DenseNet实现图像分类(matlab)
DenseNet,全称为Densely Connected Convolutional Networks,中文名为密集连接卷积网络,是由李沐等人在2017年提出的一种深度神经网络架构。 DenseNet旨在解决深度神经网络中的梯度消失问题和参数数量过多的问题,通过构建密集连接…...
基于flask+bootstrap+echarts+mysql的鱼村小馆订餐后台管理系统
📋 个人简介 💖 作者简介:大家好,我是阿牛,全栈领域优质创作者。😜📝 个人主页:馆主阿牛🔥🎉 支持我:点赞👍收藏⭐️留言Ὅ…...
Spark使用Log4j将日志发送到Kafka
文章目录自定义KafkaAppender修改log4j.properties配置启动命令配置添加参数启动之后可以在Kafka中查询发送数据时区问题-自定义实现JSONLayout解决自定义JSONLayout.java一键应用可能遇到的异常ClassNotFoundException: xxx.KafkaLog4jAppenderUnexpected problem occured dur…...
c++类与对象整理(上)
目录 1.类的引入 2.类的定义 3.类的访问限定符及封装 1)访问限定符 2)封装 4.类的作用域 5.类的实例化 6.类的对象大小的计算 1)类对象的存储方式 2)内存对齐和大小计算 编辑 7.类成员函数的this指针 1)…...
Docker学习(十九)什么是镜像的元数据?
在 Docker 中,镜像的元数据是指与镜像相关的所有信息,包括镜像的名称和标签、作者、描述、创建日期、环境变量、命令等。这些信息都是通过 Dockerfile 或命令行创建和指定的。 镜像的元数据被存储在 Docker Registry 中,并在使用 docker pull…...
Python如何获取弹幕?给你介绍两种方式
前言 弹幕可以给观众一种“实时互动”的错觉,虽然不同弹幕的发送时间有所区别,但是其只会在视频中特定的一个时间点出现,因此在相同时刻发送的弹幕基本上也具有相同的主题,在参与评论时就会有与其他观众同时评论的错觉。 在国内…...
JAVA- AOP 面向切面编程 Aspect切面工具类 记录特定方法执行时的入参、执行时间、返参等内容
背景:JAVA项目,使用AOP对指定函数进行切面。能够记录特定方法执行时的入参、执行时间、返参结果等内容。 文章目录1、自定义注解类1.1 Target1.2 Retention2、Aspect切面工具2.1 JointPoint2.2 Pointcut2.3 切面中的相关注解3、同一个类里调用AOP4、其他…...
「史上最全的 TCG 规范解读」TCG 规范架构概述(下)
可信计算组织(Ttrusted Computing Group,TCG)是一个非盈利的工业标准组织,它的宗旨是加强不同计算机平台上计算环境的安全性。TCG 于 2003 年春成立,并采纳了由可信计算平台联盟(the Trusted Computing Platform Allia…...
GDScript 导出变量 (4.0)
概述 导出变量的功能在3.x版本中也是有的,但是4.0版本对其进行了语法上的改进。 导出变量在日常的游戏制作中提供节点的自定义参数化调节功能时非常有用,除此之外还用于自定义资源。 本文是(Bilibili巽星石)在4.0官方文档《GDScr…...
JAVA知识点全面总结6:泛型反射和注解
六.JAVA知识点全面总结6泛型反射和注解 1.什么是泛型?可以用在哪里? 2.泛型擦除机制是什么?为什么擦除? 3.通配符是什么?作用是什么? 未更新 1.注解是什么?有什么用? 2.注解的自定义和实…...
死代码删除(DCE,Dead Code Elimination)和激进的死代码删除(ADCE,Aggressive DCE)
死代码删除(DCE,Dead Code Elimination)和激进的死代码删除(ADCE,Aggressive DCE)死代码删除(DCE,Dead Code Elimination)DCE简介DCE基本算法激进的死代码删除࿰…...
询问new bing关于android开发的15个问题(前景、未来、发展方向)
前言:new bing是基于chat-gpt的新搜索工具,可以采用对话方式进行问题搜索,经过排队等候终于可以使用new bing,询问了目前我最关心的关于android开发几个问题 文章目录1.如何学好android开发?2.android开发能做什么?3.…...
【C++】初识类和对象
🏖️作者:malloc不出对象 ⛺专栏:C的学习之路 👦个人简介:一名双非本科院校大二在读的科班编程菜鸟,努力编程只为赶上各位大佬的步伐🙈🙈 目录前言一、面向过程和面向对象初步认识二…...
Vue3 + Element Plus + TypeScript中el-transfer穿梭框组件使用详解及示例
使用详解 Element Plus 的 el-transfer 组件是一个强大的穿梭框组件,常用于在两个集合之间进行数据转移,如权限分配、数据选择等场景。下面我将详细介绍其用法并提供一个完整示例。 核心特性与用法 基本属性 v-model:绑定右侧列表的值&…...
JVM垃圾回收机制全解析
Java虚拟机(JVM)中的垃圾收集器(Garbage Collector,简称GC)是用于自动管理内存的机制。它负责识别和清除不再被程序使用的对象,从而释放内存空间,避免内存泄漏和内存溢出等问题。垃圾收集器在Ja…...
SpringBoot+uniapp 的 Champion 俱乐部微信小程序设计与实现,论文初版实现
摘要 本论文旨在设计并实现基于 SpringBoot 和 uniapp 的 Champion 俱乐部微信小程序,以满足俱乐部线上活动推广、会员管理、社交互动等需求。通过 SpringBoot 搭建后端服务,提供稳定高效的数据处理与业务逻辑支持;利用 uniapp 实现跨平台前…...
QT: `long long` 类型转换为 `QString` 2025.6.5
在 Qt 中,将 long long 类型转换为 QString 可以通过以下两种常用方法实现: 方法 1:使用 QString::number() 直接调用 QString 的静态方法 number(),将数值转换为字符串: long long value 1234567890123456789LL; …...
【学习笔记】深入理解Java虚拟机学习笔记——第4章 虚拟机性能监控,故障处理工具
第2章 虚拟机性能监控,故障处理工具 4.1 概述 略 4.2 基础故障处理工具 4.2.1 jps:虚拟机进程状况工具 命令:jps [options] [hostid] 功能:本地虚拟机进程显示进程ID(与ps相同),可同时显示主类&#x…...
关键领域软件测试的突围之路:如何破解安全与效率的平衡难题
在数字化浪潮席卷全球的今天,软件系统已成为国家关键领域的核心战斗力。不同于普通商业软件,这些承载着国家安全使命的软件系统面临着前所未有的质量挑战——如何在确保绝对安全的前提下,实现高效测试与快速迭代?这一命题正考验着…...
Angular微前端架构:Module Federation + ngx-build-plus (Webpack)
以下是一个完整的 Angular 微前端示例,其中使用的是 Module Federation 和 npx-build-plus 实现了主应用(Shell)与子应用(Remote)的集成。 🛠️ 项目结构 angular-mf/ ├── shell-app/ # 主应用&…...
AI+无人机如何守护濒危物种?YOLOv8实现95%精准识别
【导读】 野生动物监测在理解和保护生态系统中发挥着至关重要的作用。然而,传统的野生动物观察方法往往耗时耗力、成本高昂且范围有限。无人机的出现为野生动物监测提供了有前景的替代方案,能够实现大范围覆盖并远程采集数据。尽管具备这些优势…...
uniapp 字符包含的相关方法
在uniapp中,如果你想检查一个字符串是否包含另一个子字符串,你可以使用JavaScript中的includes()方法或者indexOf()方法。这两种方法都可以达到目的,但它们在处理方式和返回值上有所不同。 使用includes()方法 includes()方法用于判断一个字…...
MySQL 部分重点知识篇
一、数据库对象 1. 主键 定义 :主键是用于唯一标识表中每一行记录的字段或字段组合。它具有唯一性和非空性特点。 作用 :确保数据的完整性,便于数据的查询和管理。 示例 :在学生信息表中,学号可以作为主键ÿ…...
