Linux网络:序列化与反序列化
Linux网络:序列化与反序列化
- 序列化与反序列化
- json
- jsoncpp
- Value对象
- 序列化反序列化
- Writer
- Reader
序列化与反序列化
在网络通信中,最重要的就是通过接口,将数据通过网络发送给另一台主机。那么另一台主机收到数据后,就可以对数据进行处理了。
但是网络通信中,数据不是简单的将所有的数据堆砌在一起,然后一起发送出去。相反的,数据应该以特定的形式组织起来,才能让接收方进行合理的解析。
比如以下数据:
你好2024.10.21cool boy
这是一个叫做cool boy
的用户,在2024.10.21
发送了一条"你好"
的消息。对于人来说,这是很好理解的,但是对于计算机来说,这就很难处理了。比如说它也可以被解析为用户名是"你好"
的用户发送了一条叫做cool boy
的消息。用户名、时间、消息内容,这三个字段的分界符在哪里?如何区分这三个区域?
对于UDP
来说,一个消息放在一个报文中一起发送。但是对于TCP
来说,它是面向字节流的,那么就可以能出现多个消息的粘包问题:
你好2024.10.21cool boy我是一个程序员2024.10.21cool boy
比如收到这样一条TCP字节流消息,计算机又要如何区分两个报文的边界?
可以发现,简单的把数据堆在一起,这个数据就会难以解析。因此要把数据按照一定的规律合理组织在一起,并且约定好各个字段的含义,如果是TCP通信,还需要约定好一条消息的定界符,避免粘包问题。
按照一定的规则来组织数据,这种行为称为序列化
。而从组织好的数据中还原出目标信息,称为反序列化
。
当前流行的序列化协议:
- JSON
- XML
- 二进制格式
其中json
全称JavaScript Object Notation
,即JavaScript 对象 表示法
,顾名思义它是由JavaScript
提出的一种序列化规范,这是一种文本形式的协议,以字符串的形式存储与解析数据。
比如刚才的消息可以表示为:
{"message": "你好","time": "2024.10.21","name": "cool boy"
}
这样整个消息就被分为了三个部分,并且每个字段都以键值对的形式表示。这样就可以很明确的表示"你好"
是一条消息,而cool boy
是用户名。而整条报文的最外层,有一个{}
,这就可以起到TCP的定界符的作用。
除去json
还有很多数据序列化格式,比如二进制格式可以使用protobuf
等。接下来博客讲解json
的序列化格式,以及它的C++
接口。
json
json
可以保存标量类型,数组,对象三种形式的数据,规则如下:
- 数据以键值对
key: value
的形式存储 - 多条数据使用
,
隔开 key
的类型必须是字符串value
的类型可以是标量类型,数组,对象- 数组用
[]
表示 - 对象用
{}
表示
对于整个json
消息,它可以是数组或对象。
数组形式:
["hello","world",true,3.14
]
对象形式:
{"name": "cool boy","age": 12,"adult": false
}
首先,数组内部的数据,不需要以键值对的形式存储,而数组最外侧使用[]
表示。在数组中,可以存储任何形式的数据,比如true
是布尔类型,3.14
是浮点型。
而在对象形式中,每个字段都要以key:value
的形式存储,最外侧使用{}
,对象中也可以存储任意标量类型。
另外的,数组与对象之间,还可以进行嵌套:
[{"name": "张三","age": 12,"adult": false},{"name": "李四","age": 20,"adult": true}
]
比如这是一个数组,内部有两个对象,这就是数组内嵌套对象。
{"name": "张三","age": 12,"friend": ["李四","王五","赵六"]
}
这是一个对象,在对象中,friend
存储的是一个数组,数组内又存储了多个字符串。
想要处理json
数据,还是比较麻烦的,比如说要考虑括号匹配问题,数据类型的转化问题。但是不用担心,目前的主流语言,都要专门支持json
格式的库,其中C++
可以使用jsoncpp
库完成数据的序列化与反序列化。
jsoncpp
在Linux
中,下载jsoncpp
非常简单,以Ubuntu
为例:
sudo apt install libjsoncpp-dev
执行完指令,就可以使用该库了,只需包含头文件:
#include <jsoncpp/json/json.h>
Value对象
jsoncpp
的所有操作,都基于Json::Value
类,该类用于保存一个json
格式的对象。
基于C++
的操作符重载特性,Json::Value
类实现了operator[]
,完成对元素的增删改,函数声明如下:
Value& operator[](const char* key);
Value& operator[](const String& key);
这两个接口,是对json对象
的操作,[]
内放key
值,而key
必须是一个字符串,此处支持char*
和std::string
两者格式的字符串。
与std::map
用法一致,如果[]
内的值存在,那么就返回该值,如果不存在,就添加该值:
Json::Value root;
root["name"] = "张三";
root["age"] = 12;std::cout << root["age"] << std::endl;
以上代码中,创建了一个json
对象root
,并设置了“name”
和"age"
属性,最后又获取并输出"gae"
。
如果Json::Value
存储的是一个json数组
,那么使用以下两个接口:
Value& operator[](ArrayIndex index);
Value& operator[](int index);
这两个接口根据下标index
返回元素的引用,下标从0
开始。
如果存储了json数组
,那么就不能通过operator[key] = value
的形式插入值了,因为数组不需要key
值,此时使用以下接口:
void append(const Json::Value& value);
只需要append(value)
即可在数组尾插一个元素。
Json::Value root;
root.append("张三");
root.append("李四");
root.append("王五");
root.append("赵六");std::cout << root[2] << std::endl;
以上代码,往数组中插入了四个值,并输出下标为2
的元素。
- 类型转化
如果仔细观察,你会发现上面所有接口,操作的都是Json::Value
,比如append(const Json::Value& value)
。
其实Json::Value
可以转化为绝大部分的标量类型:
Json::Value& operator=(bool value);
Json::Value& operator=(int value);
Json::Value& operator=(unsigned int value);
Json::Value& operator=(Int64 value);
Json::Value& operator=(UInt64 value);
Json::Value& operator=(double value);
Json::Value& operator=(const char* value);
Json::Value& operator=(const std::string value);
也就是说之前的所有接口,其实Json::Value
这个参数可以表达所有的标量类型,因此不论是数组的appent
还是对象的operator[]
,都可以插入任意标量类型的数据,这些标量数据最后会转化为Json::Value
。
- 数组和对象操作
再补充一些数组和对象的通用接口:
size_t size();
bool empty();
void clear();
size
:返回数组或对象中元素数量empty
:检查数组或对象是否为空clear
:清空数组或对象的所有元素
序列化反序列化
了解如何往Json::Value
中填写数据后,接下来就要考虑如何进行序列化与反序列化了。
Writer
jsoncpp
通过Json::Writer
类进行序列化操作,但是该类是一个抽象类,无法实例化出对象。它有两个派生类StyledWriter
和FastWriter
,这两个类的对象完成具体的序列化操作。
在Json::Writer
类中,有一个writer
函数,声明如下:
virtual String write(const Value& root) = 0;
这是最核心的序列化函数,= 0
表示这个函数由子类重写,也就是StyledWriter
和FastWriter
对这个函数进行了重写。
函数输入一个Json::Value
对象,随后会把这个对象进行序列化,将序列化好的数据作为返回值。
构造并序列化以下json
:
["hello","world",12,false,["abc","def"],{"gender": "boy","age": 12}
]
这是一个数组,内部包含了六个元素,其中第五个元素是另一个数组,第六个元素是一个对象。
构造对象:
Json::Value root;
root.append("hello");
root.append("world");
root.append(12);
root.append(false);Json::Value arr;
arr.append("abc");
arr.append("def");
root.append(arr);Json::Value obj;
obj["gender"] = "boy";
obj["age"] = 12;
root.append(obj);
对于前四个标量数据,直接通过append
添加到root
结尾即可。而对于嵌套的数组和对象,要创建另一个Json::Value
,先进行数据填充,在添加到root
结尾。
序列化:
Json::StyledWriter swriter;
std::string style = swriter.write(root);
std::cout << "StyledWriter:" << std::endl;
std::cout << style << std::endl;Json::FastWriter fwriter;
std::string fast = fwriter.write(root);
std::cout << "FastWriter:" << std::endl;
std::cout << fast << std::endl;
此处的序列化分别实验了StyledWriter
和FastWriter
,它们都可以完成序列化。通过write(root)
接口完成序列化后,将序列化的结果输出,结果:
StyledWriter:
["hello","world",12,false,[ "abc", "def" ],{"age" : 12,"gender" : "boy"}
]FastWriter:
["hello","world",12,false,["abc","def"],{"age":12,"gender":"boy"}]
可以看出,其实两种序列化形式的数据内容是一样的,区别在于格式:
StyledWriter
:会给json
数据添加换行格式,让数据更加可视化FastWriter
:去除所有的换行格式,整个json
就是一行数据
在实际中,选用哪一种都可以,两者区别其实不大。
现在将以上对象序列化到test.json
文件中:
Json::StyledWriter swriter;
std::string str = swriter.write(root);
std::ofstream ofs("test.json");
ofs << str;
ofs.close();
使用std::string
接收到write
返回值后,直接通过文件流对象输出到文件即可。
Reader
当需要反序列化数据时,就要用到Json::Reader
类,核心反序列化函数如下:
bool parse(const std::string& document, Value& root,bool collectComments = true);
bool parse(IStream& is, Value& root, bool collectComments = true);
与Writer
不同,Reader
可以直接使用,无需使用它的派生类。不论是通过StyledWriter
还是FastWriter
序列化的数据,Reader
都可以直接反序列化。
第一个函数接收一个std::string
和一个Json::Value root
对象的引用,parse
直接反序列化字符串内的数据给root
对象,第三个参数不用管,默认为true
即可。
第二个函数则是通过文件流IStream& is
读取数据,然后反序列化到root
中。
当拿到一个未知的Json::Value
,又要如何解析?这就要用到is
和as
系列的接口:
bool isNull() const; // 检查值是否为为空
bool isBool() const; // 检查是否为布尔类型
bool isInt() const; // 检查是否为整型
bool isInt64() const; // 检查是否为64位整型
bool isUInt() const; // 检查是否为无符号整型
bool isUInt64() const; // 检查是否为64位无符号整型
bool isIntegral() const; // 检查是否为整数或者可转化为整数的浮点数
bool isDouble() const; // 检查是否为浮点数
bool isNumeric() const; // 检查是否为数字,整数或浮点数
bool isString() const; // 检查是否为字符串
bool isArray() const; // 检查是否为数组
bool isObject() const; // 检查是否为对象
由于拿到Json::Value
后,无法确定内部的值是对象,数组,还是一些标量。因此提供了以上is
系列接口,来检测数据类型,防止出现接收数据时,类型不匹配导致的错误。
const char* asCString() const;
String asString() const;
Int asInt() const;
UInt asUInt() const;
Int64 asInt64() const;
UInt64 asUInt64() const;
float asFloat() const;
double asDouble() const;
bool asBool() const;
当确定了类型后,就可以通过以上的as
系列接口,直接把Json::Value
转化为对应的标量类型,进行后续操作。
而对于数组可能不知道它有多少个元素,可以通过size
接口获取下标范围。而对于对象,可能不知道他有哪些key
,此时可以通过以下接口:
Members getMemberNames() const;
如果Json::Value
内部存储的是对象,该函数用于获取一个Json::Value
的所有key
,并存储在Json::Value::Members
中,Json::Value::Members
是一个数组,可以直接通过下标访问。
对象常用以下格式解析:
Json::Value::Members keys = root.getMemberNames();
for (int j = 0; j < keys.size(); j++)
{Json::Value item = root[keys[j]]// 解析item
}
确定root
是一个对象后,先root.getMemberNames()
拿到所有的key
。随后通过一个循环遍历所有的key
,root[keys[j]]
就是对象root
的一个成员,随后在对该对象做解析操作即可。
由于不论是标量类型,还是数组或者对象,都是使用Json::Value
表示的,所以常用递归的形式来解析:
void print(Json::Value root)
{if (root.isInt())std::cout << root.asInt() << std::endl;else if (root.isBool())std::cout << root.asBool() << std::endl;else if (root.isString())std::cout << root.asString() << std::endl;else if (root.isArray()){std::cout << "[" << std::endl;for (int i = 0; i < root.size(); i++)print(root[i]);std::cout << "]" << std::endl;}else if (root.isObject()){std::cout << "{" << std::endl;Json::Value::Members keys = root.getMemberNames();for (int j = 0; j < keys.size(); j++){std::cout << keys[j] << ": ";print(root[keys[j]]);}std::cout << "}" << std::endl;}
}
当该函数接收到一个Json::Value root
后,先判断他是不是基本的标量类型,如果是标量类型,那么直接输出即可。
如果是数组,那么for (int i = 0; i < root.size(); i++)
遍历它的所有下标,每个元素root[i]
有可能是标量,嵌套数组,嵌套对象。但是没关系,这些类型都被统一为了Json::Value
,直接递归print(root[i])
。
对象同理,先通过root.getMemberNames()
拿到所有的key
,所有root[keys[j]]
就是对象内的元素,这个元素同样有可能是标量,嵌套数组,嵌套对象,它们被统一为了Json::Value
,直接递归print(root[keys[j]])
。
以递归形式读取刚才的test.json
:
void print(Json::Value root)
{if (root.isInt())std::cout << root.asInt() << std::endl;else if (root.isBool())std::cout << root.asBool() << std::endl;else if (root.isString())std::cout << root.asString() << std::endl;else if (root.isArray()){std::cout << "[" << std::endl;for (int i = 0; i < root.size(); i++)print(root[i]);std::cout << "]" << std::endl;}else if (root.isObject()){std::cout << "{" << std::endl;Json::Value::Members keys = root.getMemberNames();for (int j = 0; j < keys.size(); j++){std::cout << keys[j] << ": ";print(root[keys[j]]);}std::cout << "}" << std::endl;}
}int main()
{std::ifstream ifs("test.json");Json::Reader re;Json::Value root;re.parse(ifs, root);print(root);return 0;
}
在main
函数中,打开文件流ifs("test.json")
,随后re.parse(ifs, root)
直接从文件流中读取数据,解析到root
对象中,最后通过print
函数递归输出该数据。
输出结果:
[
hello
world
12
0
[
abc
def
]
{
age: 12
gender: boy
}
]
这个结果没有控制缩进,所有格式有点丑。
相关文章:
Linux网络:序列化与反序列化
Linux网络:序列化与反序列化 序列化与反序列化jsonjsoncppValue对象序列化反序列化WriterReader 序列化与反序列化 在网络通信中,最重要的就是通过接口,将数据通过网络发送给另一台主机。那么另一台主机收到数据后,就可以对数据进…...
Aloudata BIG 主动元数据平台支持 Oracle/DB2 存储过程算子级血缘解析
Aloudata BIG 算子级血缘主动元数据平台已经支持 Oracle 类型、DB2 类型的存储过程算子级血缘解析,并达到 90% 血缘解析准确率: 能够识别准确的字段级数据加工依赖关系;能够识别多级嵌套调用的存储过程的血缘;能够推断存储过程内…...

Java 解决阿里云OSS服务器私有权限图片通过URL无法预览的问题
简单描述一下此场景的业务: 由于系统中需要将上传的图片在系统中展示(private私有权限不能直接通过url直接展示),不想通过先下载下来然后以流的形式返回给前台展示这种方法很不友好,毕竟现在前台展示方式都是通过图片URL进行展示,所以就上官网查看API文档,果然找到了解决…...

HarmonyOS 5.0应用开发——应用打包HAP、HAR、HSP
【高心星出品】 目录 应用打包HAP、HAR、HSPModule类型HAPHAR创建HAR建立依赖HAR共享内容 HSP创建HSP建立依赖同上HSP共享内容同上 HAR VS HSP 应用打包HAP、HAR、HSP 一个应用通常会包含多种功能,将不同的功能特性按模块来划分和管理是一种良好的设计方式。在开发…...
Android demo文件内容记录
<style name"Theme.Demo1" parent"Theme.MaterialComponents.DayNight.DarkActionBar"><!-- Primary brand color. --><item name"colorPrimary">color/purple_500</item>//状态栏的背景色,优先级小于androi…...
掌握SQL高阶技巧,助你提高数据处理的效率和查询性能
高级 SQL 技巧 窗口函数(Window Functions) 窗口函数允许你对数据集的特定行执行计算,而不会聚合结果。常见的窗口函数包括: ROW_NUMBER():为每一行分配一个唯一的序号。RANK():为每一行分配一个排名&am…...

【AI服务器】全国产PCIe 5.0 Switch SerDes 测试和分析,以11槽PCIe GPU底板(PCIe 4.0/5.0)为例(二)
3 PCIe 4.0 SerDes 和 5.0 SerDes 要求比较 表 2 总结 PCIe 4.0 和 5.0 SerDes 要求之间的差 异。PCIe 标准包含三个相互依赖的规范,这些规范 旨在确保不同供应商的 SerDes 和通道的互操作性: ● PCIe BASE 规范定义了整个协议栈的芯片 级性能,是一…...

#数据结构(二)--栈和队列
栈和队列 一栈 1.栈的顺序存储结构 特点:先进后出 栈是一种只能在一端进行插入或删除操作的线性表。 表中允许插入删除操作的一端为栈顶(top),表的另一端为栈底(bottom), 1 结构体的定义 …...

react18中的函数组件底层渲染原理分析
react 中的函数组件底层渲染原理 react组件没有局部与全局之分,它是一个整体。这点跟vue的组件化是不同的。要实现 react 中的全局组件,可以将组件挂在react上,这样只要引入了react,就可以直接使用该组件。 函数式组件的创建 …...

提升产品竞争力之--IPD产品成本篇
在汉捷的咨询过程中,很多企业老总交流时都会提起这个抱怨:“现在产品竞争太激烈了,客户买产品首先看价格,你价格高一点就买别家的啦……” 汉捷咨询在前文谈到“通过定义产品包需求,来提升产品竞争力。差异化开发&…...

如何在Debian操作系统上安装Docker
本章教程,主要介绍如何在Debian 11 系统上安装Docker。主要使用一键安装Docker脚本和一键卸载脚本来完成。 一、安装Docker #!/bin/bashRED\033[0;31m GREEN\033[0;32m YELLOW\033[0;33m BLUE\033[0;34m NC\033[0mCURRENT_DIR$(cd "$(dirname "$0")…...

ArrayList和Array、LinkedList、Vector 间的区别
一、ArrayList 和 Array 的区别 ArrayList 内部基于动态数组实现,比 Array(静态数组) 使用起来更加灵活: ArrayList 会根据实际存储的元素动态地扩容或缩容,而 Array 被创建之后就不能改变它的长度了。ArrayList 允许…...

Linux开发环境配置(下)
✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅ ✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨ 🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿…...

系统开发常用命令合集
本文还会持续更新,大家可以点赞收藏~ ifconfig ifconfigwlan0表示无线网络接口 eth0表示以太网接口(有线) HWaddr是接口的物理地址(MAC地址) inet addr是接口的IPv4地址 Bcast是广播地址,Mask是子网掩码 …...

Termius工具在MAC的使用出现的问题:
Termius工具在MAC的使用出现的问题: 在使用SFTP时,出现不了本地的文件的位置 解决方案: 在Apple store下载的使用不了LOCAL SFTP, 需要在网页上进行下载才可以: 官网下载地址:https://termius.com/down…...
浅析Android中View的绘制流程
前言 在《浅析Android中View的测量布局流程》中分析了VSYNC信号到达App进程之后开启的View布局过程,经过对整个View树进行遍历进行测量和布局,最终确定View的大小以及在屏幕中所处的位置。但是如果用户想在屏幕上看到View的内容还需要经过绘制来生成图形…...

pikachu靶场- 文件上传unsafe upfileupload
pikachu靶场- unsafe upfileupload 概述client checkMIME typegetimagesize() 概述 不安全的文件上传漏洞概述 文件上传功能在web应用系统很常见,比如很多网站注册的时候需要上传头像、上传附件等等。当用户点击上传按钮后,后台会对上传的文件进行判断…...
java中this的内存原理是?
在Java中,this关键字是一个特殊的引用,指向当前对象的实例。它在以下几个方面发挥重要作用: 指向当前对象:this可以用来访问当前对象的属性和方法,尤其在参数命名与实例变量重名时,用于区分。 构造函数&a…...

Matlab 车牌识别技术
1.1设计内容及要求: 课题研究的主要内容是对数码相机拍摄的车牌,进行基于数字图像处理技术的车牌定位技术和车牌字符分割技术的研究与开发,涉及到图像预处理、车牌定位、倾斜校正、字符分割等方面的知识,总流程图如图1-1所示。 图1-1系统总…...

CUDA-求最大值最小值atomicMaxatomicMin
作者:翟天保Steven 版权声明:著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处 实现原理 atomicMax和 atomicMin是 CUDA 中的原子操作,用于在并行计算中安全地更新共享变量的最大值和最小值。它们确…...

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式
一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明:假设每台服务器已…...

地震勘探——干扰波识别、井中地震时距曲线特点
目录 干扰波识别反射波地震勘探的干扰波 井中地震时距曲线特点 干扰波识别 有效波:可以用来解决所提出的地质任务的波;干扰波:所有妨碍辨认、追踪有效波的其他波。 地震勘探中,有效波和干扰波是相对的。例如,在反射波…...
【RockeMQ】第2节|RocketMQ快速实战以及核⼼概念详解(二)
升级Dledger高可用集群 一、主从架构的不足与Dledger的定位 主从架构缺陷 数据备份依赖Slave节点,但无自动故障转移能力,Master宕机后需人工切换,期间消息可能无法读取。Slave仅存储数据,无法主动升级为Master响应请求ÿ…...

mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包
文章目录 现象:mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包遇到 rpm 命令找不到已经安装的 MySQL 包时,可能是因为以下几个原因:1.MySQL 不是通过 RPM 包安装的2.RPM 数据库损坏3.使用了不同的包名或路径4.使用其他包…...

dify打造数据可视化图表
一、概述 在日常工作和学习中,我们经常需要和数据打交道。无论是分析报告、项目展示,还是简单的数据洞察,一个清晰直观的图表,往往能胜过千言万语。 一款能让数据可视化变得超级简单的 MCP Server,由蚂蚁集团 AntV 团队…...
rnn判断string中第一次出现a的下标
# coding:utf8 import torch import torch.nn as nn import numpy as np import random import json""" 基于pytorch的网络编写 实现一个RNN网络完成多分类任务 判断字符 a 第一次出现在字符串中的位置 """class TorchModel(nn.Module):def __in…...

html css js网页制作成品——HTML+CSS榴莲商城网页设计(4页)附源码
目录 一、👨🎓网站题目 二、✍️网站描述 三、📚网站介绍 四、🌐网站效果 五、🪓 代码实现 🧱HTML 六、🥇 如何让学习不再盲目 七、🎁更多干货 一、👨…...

推荐 github 项目:GeminiImageApp(图片生成方向,可以做一定的素材)
推荐 github 项目:GeminiImageApp(图片生成方向,可以做一定的素材) 这个项目能干嘛? 使用 gemini 2.0 的 api 和 google 其他的 api 来做衍生处理 简化和优化了文生图和图生图的行为(我的最主要) 并且有一些目标检测和切割(我用不到) 视频和 imagefx 因为没 a…...
Redis:现代应用开发的高效内存数据存储利器
一、Redis的起源与发展 Redis最初由意大利程序员Salvatore Sanfilippo在2009年开发,其初衷是为了满足他自己的一个项目需求,即需要一个高性能的键值存储系统来解决传统数据库在高并发场景下的性能瓶颈。随着项目的开源,Redis凭借其简单易用、…...

Golang——7、包与接口详解
包与接口详解 1、Golang包详解1.1、Golang中包的定义和介绍1.2、Golang包管理工具go mod1.3、Golang中自定义包1.4、Golang中使用第三包1.5、init函数 2、接口详解2.1、接口的定义2.2、空接口2.3、类型断言2.4、结构体值接收者和指针接收者实现接口的区别2.5、一个结构体实现多…...