序列与反序列化

目录

TCP的发送和接收过程理解

序列化与反序列化

Jsoncpp

简介

序列化

反序列化

自定义协议格式


TCP的发送和接收过程理解

如下图所示:

之前我们写的EchoServer和DictServer都属于应用层的编写,传输层往上,都属于用户区域,传输层及其往下的部分,都属于内核区域。

通信时,用户可以使用read、write等函数,以TCP通信的一个文件描述符为例,在传输层会为通信双方提供一个发送缓冲区和接收缓冲区。当发送者发送数据时,并不是直接将相关数据直接发送到网络中,而是通过write函数将用户空间拷贝到发送缓冲区中,并返回;而发送缓冲区的数据如何发送、何时发送以及出错的处理方式,都是由传输层TCP协议自己维护;因此,write函数本质是一个拷贝函数,将用户的数据拷贝到对应的发送缓冲区中;随后,TCP协议将发送缓冲区中的数据发送到对方的接收缓冲区中,接收方收到数据会将数据拷贝到自己的接收缓冲区中,当用户调用read接口时,如果接收缓冲区中有数据,read就将数据拷贝到用户空间中,因此,read函数本质上也是一个拷贝函数;如果没有数据,read就会阻塞;如果在发送方发送数据过快,发送缓冲区被写满了,此时write就会阻塞;因此,在通信时,双方使用的函数都是在进行拷贝,真正在网络中通信的,是双方的操作系统。

这也是TCP为什么支持全双工通信的原因,因为通信双方有一对缓冲区,进行发送的时候可以接收,接受的时候也可以发送;同时,用户拷贝数据到内核对应的发送缓冲区,用户拷贝接收缓冲区中的数据到用户空间,是个"生产者-消费者"模型,这也是read和write会什么会阻塞的原因,阻塞的原因是用户在和内核进行同步。操作系统可能会收到很多个报文,也会有还没来得及放入到缓冲区当中的报文,因此,操作系统也要对收到的报文进行管理,管理的方式同样是"先描述,再组织",每个报文都会有对应的数据结构。

但是在这个发送过程中,也会存在一定的问题,例如,当用户发送命令"ls -l -a"到服务器中,TCP可能只会先将"ls"发送,过一段时间才发送"-l -a",这就会导致命令执行出现问题,读数据时,有可能读到的数据只是发送的一部分,读取的过程和写入的过程不一定是完全匹配的,这种读取不完整的报文叫做数据粘包问题。保证完整新的过程是要上层用户自己来实现的,这时就需要定义用户协议了,比如,规定读到'\0'才算结束等规则。

序列化与反序列化

除了传基本数据进行通信,还可以定义结构体来表示我们需要交互的信息,但是在应用层,不推荐直接使用结构体变量的方式进行通信,这是因为客户端和服务端可能在不同的系统行运行,可能存在不同程度的对齐问题以及大小端问题,此时传结构体必须要人为固定大小才能正常通信,同时,通信的双方不一定是同一门编程语言,这时传结构体也不方便通信,最好的实现方式是序列化与反序列化。

序列化与反序列化的大致过程如下所示:

在聊天的过程中,发送消息时,可将不同消息变成一条,将信息由多条变为一条,是序列化的过程,对方收到信息,在应用层将收到的信息转换成多条信息,是反序列化的过程;序列和反序列化可以让我们写出来的代码具有很好的扩展性。

Jsoncpp

简介

Jsoncpp是⼀个用于处理JSON数据的C++库。它提供了将JSON数据序列化为字符串以及从字符串 反序列化为C++数据结构的功能。Jsoncpp是开源的,广泛用于各种需要处理JSON数的C++项目中。可使用如下命令进行安装:

bash 复制代码
ubuntu:sudo apt-get install libjsoncpp-dev
Centos: sudo yum install jsoncpp-devel

序列化

Json中的Value是一个万能对象,通过toStyledString函数可以将对象直接转成格式化的Json字符串。如下所示:

cpp 复制代码
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>

int main()
{
 Json::Value root;
 root["name"] = "joe";
 root["sex"] = "男";
 std::string s = root.toStyledString();

 std::cout << s << std::endl;

 return 0;

}

$ ./test.exe 
{
 "name" : "joe",
 "sex" : "男"
}

也可以使用FastWriter和StyleWriter对象进行序列化,如下面两个程序:

cpp 复制代码
#include <iostream>
#include <string>
#include <sstream>
#include <memory>
#include <jsoncpp/json/json.h>

int main()
{
 Json::Value root;
 root["name"] = "joe";
 root["sex"] = "男";
 Json::FastWriter writer;
 std::string s = writer.write(root);
 std::cout << s << std::endl;

 return 0;

}

$ ./test.exe 
{"name":"joe","sex":"男"}
cpp 复制代码
#include <iostream>
#include <string>
#include <sstream>
#include <memory>
#include <jsoncpp/json/json.h>

int main()
{
 Json::Value root;
 root["name"] = "joe";
 root["sex"] = "男";
 // Json::FastWriter writer;
 Json::StyledWriter writer;
 std::string s = writer.write(root);
 std::cout << s << std::endl;

 return 0;

}


$ ./test.exe 
{
 "name" : "joe",
 "sex" : "男"
}

同时,也可以使用StreamWriter进行序列化,如下程序所示:

cpp 复制代码
#include <iostream>
#include <string>
#include <sstream>
#include <jsoncpp/json/json.h>

int main()
{
    Json::Value root;
    root["name"] = "zhangsan"; //key:value
    root["sex"] = "man";
    root["age"] = 18;

    Json::StreamWriterBuilder wbuilder;
    std::unique_ptr<Json::StreamWriter> swriter(wbuilder.newStreamWriter());

    std::stringstream ss;
    swriter->write(root, &ss);

    std::cout << ss.str() << std::endl;

    return 0;
}

// ./a.out
{
        "age" : 18,
        "name" : "zhangsan",
        "sex" : "man"
}

由于JsonValue串是个万能对象,不仅可以接收基本类型,还可以接收自己,如下程序所示:

cpp 复制代码
#include <iostream>
#include <string>
#include <sstream>
#include <jsoncpp/json/json.h>

int main()
{
    Json::Value sub_root1;
    sub_root1["name"] = "zhangsan";
    sub_root1["sex"] = "man";
    sub_root1["age"] = 18;

    Json::Value sub_root2;
    sub_root2["math"] = 88;
    sub_root2["chinese"] = 98;

    Json::Value root;
    root["who"] = sub_root1;
    root["score"] = sub_root2;
    Json::StreamWriterBuilder wbuilder;
    std::unique_ptr<Json::StreamWriter> swriter(wbuilder.newStreamWriter());

    std::stringstream ss;
    swriter->write(root, &ss);

    std::cout << ss.str() << std::endl;


    return 0;
}

// ./test.exe
{
	"score" : 
	{
		"chinese" : 98,
		"math" : 88
	},
	"who" : 
	{
		"age" : 18,
		"name" : "zhangsan",
		"sex" : "man"
	}
}

反序列化

反序列化的方式也有多种,这里我们介绍Json::Reader,得到序列化的字符串后,可以通过parse函数将JSON串解析成value对象,读取成功后通过key访问数据即可。如下程序所示:

cpp 复制代码
#include <iostream>
#include <string>
#include <sstream>
#include <jsoncpp/json/json.h>

int main()
{
    std::string json_string = "{\"name\":\"张三\", \"age\":30, \"city\":\"北京\"}";

    std::cout << json_string << std::endl;

    Json::Reader reader;
    Json::Value root;
    bool parseok = reader.parse(json_string,root);

    if(!parseok)
    {}

    std::string name = root["name"].asString();
    int age = root["age"].asInt();
    std::string city = root["city"].asString();

    std::cout << name << std::endl;
    std::cout << age << std::endl;
    std::cout << city << std::endl;

    return 0;
}

自定义协议格式

除了解决序列化的问题,还要定制完整的协议格式,以确保接收方接收的信息是完整的,报文格式如下图所示:

报文前面4字节表示简单的报头,代表有效载荷长度,中间的有效载荷为序列化之后的字符串,当接收方读取报文时首先读取有效载荷的长度,并读取对应大小的内容作为有效载荷的内容,以此来解决粘包问题。

相关推荐
数智化管理手记1 天前
精益生产中的TPM管理是什么?一文破解设备零故障的密码
服务器·网络·数据库·低代码·制造·源代码管理·精益工程
Laurence1 天前
C++ 引入第三方库(一):直接引入源文件
开发语言·c++·第三方库·添加·添加库·添加包·源文件
蒸汽求职1 天前
机器人软件工程(Robotics SDE):特斯拉Optimus落地引发的嵌入式C++与感知算法人才抢夺战
大数据·c++·算法·职场和发展·机器人·求职招聘·ai-native
charlee441 天前
最小二乘问题详解17:SFM仿真数据生成
c++·计算机视觉·sfm·数字摄影测量·无人机航测
Tanecious.1 天前
蓝桥杯备赛:Day4-P9749 公路
c++·蓝桥杯
@insist1231 天前
网络工程师-生成树协议(STP/RSTP/MSTP)核心原理与应用
服务器·开发语言·网络工程师·软考·软件水平考试
末日汐1 天前
传输层协议UDP
linux·网络·udp
旖-旎1 天前
分治(库存管理|||)(4)
c++·算法·leetcode·排序算法·快速选择算法
Tanecious.1 天前
蓝桥杯备赛:Day3-P1102 A-B 数对
c++·蓝桥杯
Tanecious.1 天前
蓝桥杯备赛:Day3-P1918 保龄球
c++·蓝桥杯