Linux网络:序列化和反序列化


一,序列化和反序列化的概念理解

  • 序列化就是你打包的过程

    • 你有一个复杂的物件,比如一辆自行车。你不能直接把整辆自行车塞进箱子。你需要把它拆解成车把、轮子、车架等部件,然后用泡沫纸包好,整齐地、扁平地放进一个纸箱里,最后在箱子上贴好标签“自行车-零件”。

    • 目的: 为了便于运输或储存。打包后的箱子规整、安全,方便卡车运送。

  • 反序列化就是你在新家拆包组装的过程

    • 你收到这个写着“自行车-零件”的箱子,然后根据之前的拆卸方法(或者箱子里的说明书),把零件重新组装起来,恢复成一辆可以骑的完整自行车

    • 目的: 将运输来的东西恢复原状,重新投入使用。


1-1 在编程世界里的具体含义

  • 在计算机中,程序运行时,数据(比如一个游戏角色的信息:名字、等级、血量、坐标)是以一种复杂的、存在于内存中的结构形式存在的。

  • 比如在C++里,它可能是一个结构体(struct)或一个对象(object),它们在内存中有特定的布局和指针关系。

  • 内存中的游戏角色对象 (复杂结构)

name: "Alice" (指向一个字符串的地址)
level: 99
health: 100.5
position: {x: 10.3, y: 20.8} (一个嵌套的结构)

现在,你想把这个角色的数据通过网络发送给另一个玩家,或者保存到硬盘上的一个文件里

问题来了:
你不能直接把内存里这一块地址(比如0x7ffeea6b8c10)通过网络发送出去,或者原样写入文件。因为:

  • 内存地址对其他机器或程序毫无意义。

  • 数据中可能包含指针指针的值在另一台机器上指向的可能是无效甚至危险的内容。

  • 不同的电脑可能有不同的字节序大端/小端,对数据的解释方式不同。

解决方案就是:序列化和反序列化


1-2 序列化 (打包)

过程: 将内存中这个结构化的对象,转换成一个平坦的、连续的字节序列(通常是一个字节流或一个字符串)。

这个字节序列是自描述的,可以被安全地传输或存储。

常用的“打包规则”(序列化格式)有:

  • JSON: {"name": "Alice", "level": 99, "health": 100.5, "position": {"x": 10.3, "y": 20.8}} 人类可读,非常流行。

  • XML: 类似HTML标签,稍微冗长。

  • Protocol Buffers (protobuf): Google发明的,二进制格式,体积小,速度快。

  • MessagePack: 二进制的JSON,比JSON体积小。

形象化: 你把那辆自行车(对象)拆解,按照JSON的说明书,打包成了一个贴有“JSON格式”标签的纸箱(一串字节流)。


1-3 反序列化 (拆包组装)

过程: 当另一端收到这个字节流(或从文件读取后),它需要根据相同的规则,将这个字节序列重新构建成内存中一个全新的、和原始对象一模一样的结构化对象

形象化: 对方收到“JSON格式”的纸箱,按照JSON的说明书,把零件重新组装成了那辆完全可以正常骑行的“自行车”(对象)。


1-4 序列化和反序列化有什么意义

在我们之前学习socket编程中中:

  • 客户端发送数据前: 需要先将欲发送的数据结构序列化成 char 字节数组(buffer)。

  • 客户端调用 send(): 发送的其实是这个序列化后的字节数组。

  • 服务端调用 recv(): 接收到的是一个原始的字节数组。

  • 服务端处理数据前: 必须根据事先约定好的格式(如JSON),将这个字节数组反序列化,才能得到有意义的、可以操作的数据结构。

如果没有这个过程,收发双方根本无法理解彼此发送的原始字节到底代表什么


二,序列化和反序列化

现在开始我们将序列化和反序列化带到应用中来,接下来我会用比较简单的代码演示序列化和反序列化的流程用到的是C++jsoncpp


2-1 简单的序列化示例

Ubuntu 20.04 上,你可以通过包管理器轻松安装 jsoncpp

sudo apt install libjsoncpp-dev

简单的序列化示例

  • 创建 JSON 对象
Json::Value root;

  • 以键值对的形式,类似map一样存储数据
root["name"] = "Alice";
root["age"] = 25;
root["isStudent"] = false;

Json格式的序列化对象中,可以是键值对的格式,也可以是数组的格式,也可以是数组键值对混合的模式


  • 创建数组
Json::Value languages;
languages.append("C++");
languages.append("Python");
languages.append("JavaScript");
root["languages"] = languages;

  • 创建嵌套对象
Json::Value address;
address["city"] = "New York";
address["country"] = "USA";
root["address"] = address;

这里创建了一个keyaddress对应着一个键值对对象


  • 序列化为字符串并打印
Json::StyledWriter styledWriter;
std::string pretty_json = styledWriter.write(root); // 美化格式

std::cout << "Serialized JSON:" << std::endl;
std::cout <<  pretty_json << std::endl;

完整代码

#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>

int main() {
    // 创建 JSON 对象
    Json::Value root;
    root["name"] = "Alice";
    root["age"] = 25;
    root["isStudent"] = false;
    
    // 创建数组
    Json::Value languages;
    languages.append("C++");
    languages.append("Python");
    languages.append("JavaScript");
    root["languages"] = languages;
    
    // 创建嵌套对象
    Json::Value address;
    address["city"] = "New York";
    address["country"] = "USA";
    root["address"] = address;
    
    // 序列化为字符串
    Json::StreamWriterBuilder writer;
    std::string json_str = Json::writeString(writer, root);
    
    std::cout << "Serialized JSON:" << std::endl;
    std::cout << json_str << std::endl;
    
    return 0;
}

演示结果

Serialized JSON:
{
   "address" : {
      "city" : "New York",
      "country" : "USA"
   },
   "age" : 25,
   "isStudent" : false,
   "languages" : [ "C++", "Python", "JavaScript" ],
   "name" : "Alice"
}


2-1 简单的反序列化示例

  • 创建JSON 字符串
std::string json_str = R "({
"name": "Bob",
"age": 30,
"isStudent": true,
"languages": ["Java", "C#", "Go"],
"address": {
    "city": "San Francisco",
    "country": "USA"
}
})
";

  • 解析 JSON
    Json::CharReaderBuilder reader;
    Json::Value root;
    std::string errors;
    
    std::istringstream json_stream(json_str);
    bool parsingSuccessful = Json::parseFromStream(reader, json_stream, &root, &errors);

Json::CharReaderBuilder reader;创建一个 CharReaderBuilder 对象,用于配置 JSON 解析器的行CharReaderBuilder 是一个工厂类,用于创建 CharReader 对象,它允许你定制化 JSON 解析的行为,默认情况下,它会创建一个遵循 JSON 标准的解析器,比如添加如下代码

Json::CharReaderBuilder reader;

// 允许注释(JSON 标准不允许注释,但很多JSON文件包含注释)
reader.settings_["allowComments"] = true;

// 允许尾随逗号(如 [1, 2, 3,])
reader.settings_["allowTrailingCommas"] = true;

// 允许非引号键(如 {name: "John"},不符合JSON标准但常见)
reader.settings_["allowUnquotedKeys"] = false; // 默认false,保持严格

// 允许单引号字符串(如 {'name': 'John'})
reader.settings_["allowSingleQuotes"] = false; // 默认false

Json::Value root;不过多讲解了,就是一个存放数据的对象

std::string errors;创建一个字符串变量,用于存储解析过程中可能出现的错误信息。这是一个输出参数,如果解析失败,这里会包含详细的错误信息,如果解析成功,这个字符串通常是空的,对于调试非常有用,可以告诉你是哪个字符位置出现了问题

std::istringstream json_stream(json_str),将 JSON 字符串包装成一个输入流std::istringstreamC++ 标准库中的字符串流类,它将字符串 json_str 转换为一个可以按顺序读取的流 Json::parseFromStream 需要一个流对象作为输入,而不是直接的字符串

使用流可以处理大型 JSON 数据(流式处理,不需要一次性加载到内存),更灵活,可以从文件、网络等多种源读取,符合 C++IO 流设计哲学


  • 提取数据

在学习提取数据之前,先学习一下Value的一些接口

类型检查接口

// 检查值的类型
bool isNull() const;      // 是否为 null
bool isBool() const;      // 是否为布尔值
bool isInt() const;       // 是否为 int
bool isUInt() const;      // 是否为 unsigned int
bool isIntegral() const;  // 是否为整数(包括有符号和无符号)
bool isDouble() const;    // 是否为 double
bool isNumeric() const;   // 是否为数字(整数或浮点数)
bool isString() const;    // 是否为字符串
bool isArray() const;     // 是否为数组
bool isObject() const;    // 是否为对象
bool isMember(const char* key) const; // 检查对象是否包含指定键

类型转换接口

bool asBool() const;          // 转换为 bool
int asInt() const;            // 转换为 int
unsigned int asUInt() const;  // 转换为 unsigned int
int64_t asInt64() const;      // 转换为 int64_t
uint64_t asUInt64() const;    // 转换为 uint64_t
double asDouble() const;      // 转换为 double
float asFloat() const;        // 转换为 float
const char* asCString() const;// 转换为 C 风格字符串
std::string asString() const; // 转换为 std::string

提取数据代码

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

其它的没有什么很难的了,直接展示完整代码

#include <jsoncpp/json/json.h>
#include <iostream>
#include <string>
using namespace std;
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>
#include <sstream>

int main() {
    // JSON 字符串
    std::string json_str = R"({
        "name": "Bob",
        "age": 30,
        "isStudent": true,
        "languages": ["Java", "C#", "Go"],
        "address": {
            "city": "San Francisco",
            "country": "USA"
        }
    })";
    
    // 解析 JSON
    Json::CharReaderBuilder reader;
    Json::Value root;
    std::string errors;
    
    std::istringstream json_stream(json_str);
    bool parsingSuccessful = Json::parseFromStream(reader, json_stream, &root, &errors);
    
    if (!parsingSuccessful) {
        std::cout << "Failed to parse JSON: " << errors << std::endl;
        return 1;
    }
    
    // 提取数据
    std::string name = root["name"].asString();
    int age = root["age"].asInt();
    bool isStudent = root["isStudent"].asBool();
    
    std::cout << "Name: " << name << std::endl;
    std::cout << "Age: " << age << std::endl;
    std::cout << "Is Student: " << (isStudent ? "Yes" : "No") << std::endl;
    
    // 处理数组
    std::cout << "Languages: ";
    const Json::Value languages = root["languages"];
    for (const auto& lang : languages) {
        std::cout << lang.asString() << " ";
    }
    std::cout << std::endl;
    
    // 处理嵌套对象
    std::string city = root["address"]["city"].asString();
    std::string country = root["address"]["country"].asString();
    std::cout << "Address: " << city << ", " << country << std::endl;
    
    return 0;
}

演示结果

root@hcss-ecs-f59a:/gch/code/HaoHao/learn3/day7# ./exe
Name: Bob
Age: 30
Is Student: Yes
Languages: Java C# Go 
Address: San Francisco, USA
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值