距离上次更新本文已经过去了 686 天,文章部分内容可能已经过时,请注意甄别

以 tcpServer 的计算器服务为例,实现用 jsoncpp 来进行序列化和反序列化

阅读本文之前,请先阅读 自定义协议 | 序列化和反序列化 | 以 tcpServer 为例

1. 安装 jsoncpp

我所用的系统是 centos7.6,先用下面的命令查找相关的包

plaintext
1
sudo yum list | grep jsoncpp-devel

显示出来的包如下

plaintext
1
2
Repository epel is listed more than once in the configuration
jsoncpp-devel.x86_64 0.10.5-2.el7 @epel

随后安装这个包

plaintext
1
sudo yum install jsoncpp

不知道为什么,安装 jsoncpp-devel.x86_64 显示找不到相关包

或者采用下面的两个命令

plaintext
1
2
sudo yum install epel-release
sudo yum install jsoncpp-devel

如果执行完毕后显示找不到 jsoncpp,尝试重新安装 epel-release

image-20230404123816637

刚开始我显示 epel 已经安装,但是找不到 jsoncpp 这个包,我在重新安装了 epel 后就能正常安装了

plaintext
1
2
3
sudo yum rm epel-release
sudo yum install epel-release
sudo yum install jsoncpp-devel

如下图,正常查找到并安装完毕

image-20230404123834144

在 centos8 下,安装完毕后路径如下

plaintext
1
2
$ ls /usr/include/json
allocator.h assertions.h autolink.h config.h features.h forwards.h json.h reader.h value.h version.h writer.h

1.1 什么是 json?

json 是一个 kv 键值对的序列化方式,每一个 key 都对应了一个 value

json
1
2
3
{
"data": "value"
}

这就有点类似 c++ 中的 map,不过 json 能做的更多

json
1
2
3
4
5
6
7
8
9
{
"data1": "value",
"data2": {
"key1":"value1",
"key2":"value2",
"key3":0,
"key4":true
}
}

json 可以在里面嵌套添加更多的内容,不管是字符串还是整形,还能是 bool 类型的 true/false

因为 json 对反序列化序列化的控制很是不错,可读性也很好,所以被广泛使用!

相比于自己写一个序列化方式,直接用别人的轮子,也不错👻

类似的序列化框架还有 xml

2. 代码示例

2.1 序列化

相比我们自己写的序列化方式,json 的使用简单多了

cpp
1
2
3
4
5
6
7
8
9
10
11
12
void serialize(std::string& out)
{
//使用jsoncpp的代码
Json::Value root;
root["x"] = _x;
root["y"] = _y;
root["op"] = _ops;

Json::FastWriter fw; // 这个是写成一行,对于计算机来说处理的负担小
// Json::StyledWriter fw; // 这个会进行格式化,更好看(但是内容没差距)
out = fw.write(root);
}

对于 fw.write(root),其返回值是一个 string,也方便我们接收

image-20230212135543386

其中 FastWriterStyledWriter 有一点区别,如下

json
1
2
3
4
5
6
//FastWriter
{"data": "value"}
//StyledWriter
{
"data": "value"
}

简单来说,StyledWriter 会对我们的 kv 键值对进行格式化,更方便人类的阅读。而 FastWriter 是直接写成一整行,在传输的时候会方便一点(因为不需要 \n

2.2 反序列化

反序列化需要一个 Reader 来读取字符串,并将其内容根据键值隐射给成员变量

cpp
1
2
3
4
5
6
7
8
9
10
bool deserialize(const std::string &in)
{
//json
Json::Value root;
Json::Reader rd;
rd.parse(in, root);
_x = root["x"].asInt();
_y = root["y"].asInt();
_ops = root["op"].asInt();
}

3. 测试

因为 jsoncpp 是一个第三方库,我们链接的时候需要加上命令 -ljsoncpp

makefile
1
2
tcpServer:tcpServer.cpp
g++ -o $@ $^ -std=c++11 -lpthread -ljsoncpp

发送消息后,可以看到,json 帮我们格式化为如下形式的字符串

json
1
2
{"op":43,"x":1,"y":200}
{"op":43,"x":333,"y":234}

image-20230212140924067

4.gcc 给予宏定义

为了方便对序列化采用的方式进行控制,这里我使用了预处理指令 ifdef/endif 来进行判断,只要我们在文件头定义了 MYPROTOCOL,这里就会采用我们自己的写的序列化方式,否则采用 json

c
1
#define MYPROTOCOL 1  //如果define了这个,那就使用自己的代码

image-20230212182113854

4.1 命令行

但是在文件里面修改 define 还是不太方便,我们可以直接采用 gcc 的命令行参数的方式,进行 define 的插入(这么做之前,要先删除文件中对 MYPROTOCOL 的 define)

bash
1
2
g++ -DMYPROTOCOL tcpServer.cpp -o tcpServer -lpthread -ljsoncpp
g++ -DMYPROTOCOL tcpClient.cpp -o tcpClient -lpthread -ljsoncpp

可以看到,不加编译指令编译出来的服务器,采用的是 json 的方式来序列化

image-20230212182627766

添加了之后,就是用我们自己写的序列化方式来序列化了

image-20230212182919150

4.2 makefile

因此,我们可以修改 makefile 来实现这一点

makefile
1
2
3
4
5
6
7
8
9
10
11
12
.PHONY:all
all:tcpClient tcpServer
MYSELF=-DMYPROTOCOL

tcpClient: tcpClient.cpp
g++ $(MYSELF) -o $@ $^ -std=c++11 -lpthread -ljsoncpp
tcpServer:tcpServer.cpp
g++ $(MYSELF) -o $@ $^ -std=c++11 -lpthread -ljsoncpp

.PHONY:clean
clean:
rm -f tcpClient tcpServer

当我们需要用自己协议的时候,就在最前面加上

plaintext
1
MYSELF=-DMYPROTOCOL

否则直接删除这个定义,或者注释掉后面的内容,就能采用 jsoncpp

makefile
1
MYSELF=#-DMYPROTOCOL

这样就方便一些了

image-20230212183456024