目录
一、什么是序列化和反序列化?
二、利用Jsoncpp实现序列化和反序列化
1.序列化
(1)使用 Json::Value 的 toStyledString 方法
(2)使用 Json::StreamWriterf 方法
(3)使用 Json::FastWriterff 方法
2.反序列化
(1)使用 Json::Reader 方法
三、数据封装(消息定界)——解决TCP通信粘包问题
一、什么是序列化和反序列化?
我们之前进行网络编程时,都是在应用层编写代码,处理的都是一些复杂的数据结构,如结构体、对象等等,再将这些数据通过socket接口传输到网络中。但是前提情况是服务端和客户端都是在相同的平台环境中。实际上,发送这些数据时,还利用序列化协议如Protocol Buffers、MessagePack等将数据进行序列化转换。
为什么不直接将这些数据传输过去呢?因为发送方和接收方环境可能不相同,内存布局不同,例如直接将结构体发送给接收方,可能存在填充字节等问题。另外发送发和接收方的编程语言不同也会有影响,例如一个C++编写的服务端发送数据给一个Python编写的客户端,二者数据结构的定义都不相同,无法正确接收。
发送方将复杂的数据结构统一转换为结构化的数据,再发送给到网络中,这就是序列化;接收方接收到结构化数据后根据序列化与反序列化协议,再将结构化数据转换为复杂的数据结构,这就是反序列化。序列化和反序列化可以解决数据跨系统、跨语言、跨平台传输一致性问题。
二、利用Jsoncpp实现序列化和反序列化
Jsoncpp 是一个用于处理 JSON 数据的 C++ 库。 它提供了将 JSON 数据序列化为字符串以及从字符串反序列化为 C++ 数据结构的功能。 Jsoncpp 是开源的, 广泛用于各种需要处理 JSON 数据的 C++ 项目中。
Linux安装Jsoncpp命令:
sudo apt-get install libjsoncpp-dev #ubuntu
sudo yum install jsoncpp-devel #centos
1.序列化
(1)使用 Json::Value 的 toStyledString 方法
优点:将 Json::Value 对象直接转换为格式化的 JSON 字符串
#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;
} //{
// "name" : "joe",
// "sex" : "男"
//}
(2)使用 Json::StreamWriterf 方法
优点:提供了更多的定制选项, 如缩进、 换行符等
#include <iostream>
#include <string>
#include <sstream>
#include <memory>
#include <jsoncpp/json/json.h>
int main()
{Json::Value root;root["name"] = "joe";root["sex"] = "男";Json::StreamWriterBuilder wbuilder; // StreamWriter 的工厂std::unique_ptr<Json::StreamWriter> writer(wbuilder.newStreamWriter());std::stringstream ss;writer->write(root, &ss);std::cout << ss.str() << std::endl;return 0;
}//{
// "name" : "joe",
// "sex" : "男"
//}
(3)使用 Json::FastWriterff 方法
优点:比 StyledWriter 更快, 因为它不添加额外的空格和换行符
#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;
}//{
// "name" : "joe",
// "sex" : "男"
//}
2.反序列化
(1)使用 Json::Reader 方法
优点:提供详细的错误信息和位置, 方便调试
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>
int main()
{// JSON 字符串std::string json_string = "{\"name\":\"张三\",\"age\":30, \"city\":\"北京\"}";// 解析 JSON 字符串Json::Reader reader;Json::Value root;// 从字符串中读取 JSON 数据bool parsingSuccessful = reader.parse(json_string,root);if (!parsingSuccessful) {// 解析失败, 输出错误信息std::cout << "Failed to parse JSON: " <<reader.getFormattedErrorMessages() << std::endl;return 1;}// 访问 JSON 数据std::string name = root["name"].asString();int age = root["age"].asInt();std::string city = root["city"].asString();// 输出结果std::cout << "Name: " << name << std::endl;std::cout << "Age: " << age << std::endl;std::cout << "City: " << city << std::endl;return 0;
}//Name: 张三
//Age: 30
//City: 北京
三、数据封装(消息定界)——解决TCP通信粘包问题
由于TCP是面向字节流的协议,传输的数据时不会自动维护消息的边界,发送方多次发送的数据可能会被合并接收,或者发送方写入发送缓冲区的数据会被拆分为多次发送,这就会导致接收方从接收缓冲区中读取的数据可能不是一条完整的数据,可能是多条数据组合在一起或者仅仅是一条完整数据的一部分。这个问题就是粘包问题。
为了解决粘包问题,发送方在对发送数据使用Jsoncpp序列化之后,要对Json数据加上固定长度消息头或者分割界定符来为维护数据边界,同样的接收方接收到消息后,要先从消息中提取出一条完整的数据(即去除数据边界),再对数据进行反序列化。
static const std::string sep = "\r\n";
// 设计一下协议的报头和报文的完整格式
// "len"\r\n"{json}"\r\n --- 完整的报文, len 有效载荷的长度!
// \r\n: 区分len 和 json 串
// \r\n: 暂是没有其他用,打印方便,debug
// 添加报头
std::string Encode(const std::string &jsonstr)
{int len = jsonstr.size();std::string lenstr = std::to_string(len);return lenstr + sep + jsonstr + sep;
}
// 不能带const
// "le
// "len"
// "len"\r\n
// "len"\r\n"{json}"\r\n (]
// "len"\r\n"{j
// "len"\r\n"{json}"\r\n"len"\r\n"{
// "len"\r\n"{json}"\r\n
// "len"\r\n"{json}"\r\n"len"\r\n"{json}"\r\n"len"\r\n"{json}"\r\n"len"\r\n"{json}"\r
std::string Decode(std::string &packagestream)
{// 分析auto pos = packagestream.find(sep);if (pos == std::string::npos)return std::string();std::string lenstr = packagestream.substr(0, pos);int len = std::stoi(lenstr);// 计算一个完整的报文应该是多长??int total = lenstr.size() + len + 2 * sep.size();if (packagestream.size() < total)return std::string();// 提取std::string jsonstr = packagestream.substr(pos + sep.size(), len);packagestream.erase(0, total);return jsonstr;
}