应用层自定义协议与序列化一站式指南

目录

  • [1. 概念理解](#1. 概念理解)
    • [1.1 应用层](#1.1 应用层)
    • [1.2 再次理解"协议"](#1.2 再次理解“协议”)
    • [1.3 序列化和反序列化](#1.3 序列化和反序列化)
      • [1.3.1 概念](#1.3.1 概念)
      • [1.3.2 举例](#1.3.2 举例)
      • [1.3.3 为什么序列化&反序列化是应用层协议的关键?](#1.3.3 为什么序列化&反序列化是应用层协议的关键?)
  • [2. 重新理解read、write、recv、send及网络数据传输的本质](#2. 重新理解read、write、recv、send及网络数据传输的本质)
  • [3. tcp为什么支持全双工](#3. tcp为什么支持全双工)
  • [4.Jsoncpp详解 - 用于处理 JSON 数据序列化的 C++ 库](#4.Jsoncpp详解 - 用于处理 JSON 数据序列化的 C++ 库)
    • [4.1 Jsoncpp特性及安装](#4.1 Jsoncpp特性及安装)
    • [4.2 Json:Value类](#4.2 Json:Value类)
    • [4.3 Json序列化方式](#4.3 Json序列化方式)
      • [1. 使用 Json::Value 的toStyledString 方法](#1. 使用 Json::Value 的toStyledString 方法)
      • [2. 使用 Json::StreamWriter](#2. 使用 Json::StreamWriter)
      • [3. 使用 Json::FastWriter](#3. 使用 Json::FastWriter)
      • [4. 使用 Json::StyledWriter writer;](#4. 使用 Json::StyledWriter writer;)
    • [4.4 Json反序列化方式](#4.4 Json反序列化方式)
      • [1. 使用 Json::Reader](#1. 使用 Json::Reader)
      • [2. 使用 Json::CharReader 的派生类(不推荐了,上面的足够了):](#2. 使用 Json::CharReader 的派生类(不推荐了,上面的足够了):)
    • [4.5 总结](#4.5 总结)

1. 概念理解

1.1 应用层

应用层是直接面向用户需求的最上层,它定义了应用程序之间通信的规则和接口,目的是解决具体的业务问题(比如网页浏览、文件传输、邮件收发)。

我们日常写的网络程序(如 HTTP 接口服务、FTP 客户端、即时通讯工具、游戏联机模块),其业务逻辑和用户交互相关的核心代码,都属于应用层的范畴。

1.2 再次理解"协议"

协议是一种 "约定"。socket api的接口,在读写数据时,都是按 "字符串" 的方式来发送接收的。如果我们要传输一些 "结构化的数据" 怎么办呢?

📌 其实,协议:就是双方约定好的结构化的数据

1.3 序列化和反序列化

1.3.1 概念

序列化 & 反序列化的核心概念是:应用层协议中 "结构化数据" 与 "网络传输字节流" 之间的转换操作 ------ 因为网络只能传输二进制字节流,而应用程序要处理的是有业务含义的结构化数据(比如你的聊天消息struct),这两者的转换就靠这两个操作:

  • 序列化 :把应用程序里的结构化数据 (如 C++ 的struct/class、JSON 对象),转换成连续的字节流(或字符串),方便通过网络发送。
  • 反序列化 :把网络接收的字节流 ,还原成应用程序能识别的结构化数据,方便上层业务逻辑处理。

1.3.2 举例

以图中的 "聊天消息" 为例:

  1. 序列化过程(发送端)

    应用程序里的结构化数据是:

    cpp 复制代码
    // 对应你图里的message/time/nickname
    struct ChatMsg {
        string message;   // "你好啊"
        string time;      // "20xx-yy-zz aa:bb:cc"
        string nickname;  // "新时代好青年"
    };

    序列化就是把这个struct转换成连续的字符串(字节流):"你好啊 20xx-yy-zz aa:bb:cc 新时代好青年"------ 这一步完成了 "从多(多个字段)变一(一个字节流)",适配网络传输的要求。

  2. 反序列化过程(接收端)

    接收端拿到字节流"你好啊 20xx-yy-zz aa:bb:cc 新时代好青年"后,按照约定的格式拆分字段,还原成ChatMsg结构体 ------ 这一步完成了 "从一(字节流)变多(多个业务字段)",方便上层程序读取消息内容、显示到聊天界面。

1.3.3 为什么序列化&反序列化是应用层协议的关键?

你说 "应用层协议是两端能解析的约定",而序列化 / 反序列化就是这个 "约定" 的具体实现方式

  • 协议约定了 "聊天消息要包含 message/time/nickname 三个字段";
  • 序列化 / 反序列化约定了 "这三个字段怎么打包成字节流、怎么拆包"------ 两者结合,才实现了两端的正确通信。

2. 重新理解read、write、recv、send及网络数据传输的本质

1.write/send、read/recv 的本质:用户态与内核态的内存拷贝

  • write()/send()(发送):将应用程序 "用户态内存" 中的数据,拷贝到操作系统内核的 "TCP 发送缓冲区"(并非直接发送到网络)。
  • read()/recv()(接收):将操作系统内核 "TCP 接收缓冲区" 中的数据,拷贝到应用程序的 "用户态内存"(并非直接从网络读取)。
  • 核心:这两个系统调用的本质是用户态与内核态之间的内存数据拷贝,是应用程序与内核协议栈之间的 "数据交接" 操作。

2.两台主机的网络通信:"内核缓冲区 + 协议栈处理" 的完整流程

两台主机的通信并非 "内核缓冲区直接互传",而是结合协议栈的分层处理:

  1. 主机 A:应用层序列化后的数据 → write()拷贝到内核发送缓冲区 → TCP 将数据分段(按 MSS 大小)、封装 TCP 头 → 网络层封装 IP 头 → 链路层封装帧头 → 通过物理网络发送。
  2. 主机 B:链路层接收帧、解包 → 网络层解 IP 头 → TCP 验证报文合法性、重组分段数据 → 将完整数据存入内核接收缓冲区 → 应用层通过read()拷贝到用户态内存 → 反序列化处理。

3.TCP 对内核缓冲区数据的控制逻辑

内核缓冲区中数据的 "发送时机""分段大小" 由 TCP 协议主导,但 "内容总大小" 由应用层决定:

  • TCP 决定:
    • 发送时机(如 Nagle 算法合并小数据、拥塞控制动态调整发送速率);
    • 数据分段大小(基于 MSS <最大报文段长度>,避免 IP 分片);
  • 应用层决定:要发送的业务数据的总长度(如你例子中聊天消息的字符串长度)。

4.TCP 的 "传输控制":出错后的纠错机制

TCP 被称为 "传输控制协议",核心是通过以下机制解决传输错误:

  • 差错校验:通过 TCP 头的校验和,检测报文是否损坏;
  • 超时重传:发送端未收到确认时,重传发送缓冲区中的数据;
  • 快速重传:接收端收到乱序报文时,主动反馈,触发发送端重传;
  • 流量控制:通过滑动窗口,避免接收端缓冲区溢出导致数据丢失。

5.TCP 全双工的本质:"单连接 + 独立双缓冲对"

TCP 通信是全双工 (双向同时通信),核心原因是:单个 TCP 连接的两端,都各自拥有 "独立的发送缓冲区 + 接收缓冲区"------ 即主机 A 的 "发送缓冲 + 接收缓冲",与主机 B 的 "发送缓冲 + 接收缓冲" 是双向独立的数据流通道,支持两端同时发送、同时接收数据。

6.网络数据传输的本质:"多轮拷贝 + 分层协议处理"

网络传输的完整本质是:

应用层数据 → 「用户态→内核态」拷贝(write) → 内核协议栈分层封装(TCP→IP→链路层) → 物理网络传输 → 对端分层解包 → 「内核态→用户态」拷贝(read) → 应用层处理。

核心是 "用户态 - 内核态拷贝"+"协议栈的封装 / 解包"+"物理层的信号传输" 的组合流程。

3. tcp为什么支持全双工

TCP 通信是全双工 (双向同时通信),核心原因是:单个 TCP 连接的两端,都各自拥有 "独立的发送缓冲区 + 接收缓冲区"------ 即主机 A 的 "发送缓冲 + 接收缓冲",与主机 B 的 "发送缓冲 + 接收缓冲" 是双向独立的数据流通道,支持两端同时发送、同时接收数据。

4.Jsoncpp详解 - 用于处理 JSON 数据序列化的 C++ 库

4.1 Jsoncpp特性及安装

Jsoncpp 是一个用于处理 JSON 数据的 C++ 库。它提供了将 JSON 数据序列化为字符串以及从字符串反序列化为 C++ 数据结构的功能。Jsoncpp 是开源的,广泛用于各种需要处理 JSON 数据的 C++ 项目中。

特性:

  1. 简单易用:Jsoncpp 提供了直观的 API,使得处理 JSON 数据变得简单。
  2. 高性能:Jsoncpp 的性能经过优化,能够高效地处理大量 JSON 数据。
  3. 全面支持:支持 JSON 标准中的所有数据类型,包括对象、数组、字符串、数字、布尔值和 null。
  4. 错误处理:在解析 JSON 数据时,Jsoncpp 提供了详细的错误信息和位置,方便开发者调试。

安装:

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

4.2 Json:Value类

Json::Value 是 Jsoncpp 库中的一个重要类,用于表示和操作 JSON 数据结构。以下是一些常用的 Json::Value 操作列表:

  1. 构造函数
  • Json::Value() :默认构造函数,创建一个空的 Json::Value 对象。

  • Json::Value(ValueType type, bool allocated = false) :根据给定的ValueType (如 nullValue , intValue , stringValue 等)创建一个Json::Value 对象。

  1. 访问元素
  • Json::Value& operator[](const char* key) :通过键(字符串)访问对象中的元素。如果键不存在,则创建一个新的元素。

  • Json::Value& operator[](const std::string& key) :同上,但使用std::string 类型的键。

  • Json::Value& operator[](ArrayIndex index) :通过索引访问数组中的元素。如果索引超出范围,则创建一个新的元素。

  • Json::Value& at(const char* key) :通过键访问对象中的元素,如果键不存在则抛出异常。

  • Json::Value& at(const std::string& key) :同上,但使用 std::string 类型的键。

  1. 类型检查
  • bool isNull() :检查值是否为 null。

  • bool isBool() :检查值是否为布尔类型。

  • bool isInt() :检查值是否为整数类型。

  • bool isInt64() :检查值是否为 64 位整数类型。

  • bool isUInt() :检查值是否为无符号整数类型。

  • bool isUInt64() :检查值是否为 64 位无符号整数类型。

  • bool isIntegral() :检查值是否为整数或可转换为整数的浮点数。

  • bool isDouble() :检查值是否为双精度浮点数。

  • bool isNumeric() :检查值是否为数字(整数或浮点数)。

  • bool isString() :检查值是否为字符串。

  • bool isArray() :检查值是否为数组。

  • bool isObject() :检查值是否为对象(即键值对的集合)。

  1. 赋值
  • Json::Value& operator=(bool value) :将布尔值赋给 Json::Value 对象。

  • Json::Value& operator=(int value) :将整数赋给 Json::Value 对象。

  • Json::Value& operator=(unsigned int value) :将无符号整数赋给Json::Value 对象。

  • Json::Value& operator=(Int64 value) :将 64 位整数赋给 Json::Value 对象。

  • Json::Value& operator=(UInt64 value) :将 64 位无符号整数赋给 Json::Value对象。

  • Json::Value& operator=(double value) :将双精度浮点数赋给 Json::Value 对象。

  • Json::Value& operator=(const char* value) :将 C 字符串赋给 Json::Value对象。

  • Json::Value& operator=(const std::string& value) :将 std::string 赋给 Json::Value 对象。

  1. 类型转换
  • bool asBool() :将值转换为布尔类型(如果可能)。

  • int asInt() :将值转换为整数类型(如果可能)。

  • Int64 asInt64() :将值转换为 64 位整数类型(如果可能)。

  • unsigned int asUInt() :将值转换为无符号整数类型(如果可能)。

  • UInt64 asUInt64() :将值转换为 64 位无符号整数类型(如果可能)。

  • double asDouble() :将值转换为双精度浮点数类型(如果可能)。

  • std::string asString() :将值转换为字符串类型(如果可能)。

  1. 数组和对象操作
  • size_t size() :返回数组或对象中的元素数量。

  • bool empty() :检查数组或对象是否为空。

  • void resize(ArrayIndex newSize) :调整数组的大小。

  • void clear() :删除数组或对象中的所有元素。

  • void append(const Json::Value& value) :在数组末尾添加一个新元素。

  • Json::Value& operator[](const char* key, const Json::Value&defaultValue = Json::nullValue) :在对象中插入或访问一个元素,如果键不存在则使用默认值。

  • Json::Value& operator[](const std::string& key, const Json::Value&defaultValue = Json::nullValue) :同上,但使用 std::string 类型的。

4.3 Json序列化方式

1. 使用 Json::Value 的toStyledString 方法

  • 优点:将Json::Value 对象直接转换为格式化的JSON字符串。示例如下:
cpp 复制代码
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>
// 引入 jsoncpp 库的核心头文件,该库用于 C++ 中 JSON 数据的构建与解析

int main()
{
    // 创建 Json::Value 类型的根对象,这是 jsoncpp 库的核心数据结构
    // 可理解为一个空的 JSON 容器,用于存储键值对形式的 JSON 数据
    Json::Value root;

    // 向 JSON 根对象中添加键值对
    root["name"] = "joe";
    root["sex"] = "男";
    
    // 调用 Json::Value 的 toStyledString() 方法
    // 将 JSON 对象转换为带缩进、换行的格式化字符串(易读形式),而非紧凑的单行字符串
    std::string s = root.toStyledString();
    
    // 将格式化后的 JSON 字符串输出到控制台
    std::cout << s << std::endl;
    
    return 0;
}
bash 复制代码
$ ./test.exe
{
    "name" : "joe",
    "sex" : "男"
}

2. 使用 Json::StreamWriter

  • 优点:提供了更多的定制选项,如缩进、换行符等。示例如下:
cpp 复制代码
#include <iostream>
#include <string>
// 引入字符串流头文件,用于在内存中构建/存储JSON字符串(替代物理文件流)
#include <sstream>
// 引入智能指针头文件,用于安全管理StreamWriter对象的内存(自动释放)
#include <memory>
// 引入jsoncpp库核心头文件,提供JSON构建/序列化相关类
#include <jsoncpp/json/json.h>

int main()
{
    // 1. 构建JSON数据对象(核心数据载体)
    // 创建Json::Value根对象,用于存储键值对形式的JSON数据
    Json::Value root;
    // 向JSON根对象添加键值对
    root["name"] = "joe";
    root["sex"] = "男";
    
    // 2. 构建JSON序列化器(StreamWriter)
    // 创建StreamWriterBuilder对象:它是jsoncpp中生成StreamWriter的"工厂类"
    // 相比toStyledString(),StreamWriterBuilder更灵活,可自定义序列化格式(如缩进、换行)
    Json::StreamWriterBuilder wbuilder;
    
    // 通过工厂类创建StreamWriter对象,并使用unique_ptr智能指针管理
    // unique_ptr会自动释放内存,避免手动delete导致的内存泄漏,是C++现代内存管理的最佳实践
    std::unique_ptr<Json::StreamWriter> writer(wbuilder.newStreamWriter());
    
    // 3. 内存中序列化JSON(将JSON对象写入字符串流)
    // 创建stringstream字符串流对象:在内存中存储序列化后的JSON字符串(无需写入文件)
    std::stringstream ss;
    
    // 调用StreamWriter的write方法:将Json::Value对象序列化为JSON字符串,写入stringstream
    // 第一个参数:要序列化的JSON数据对象;第二个参数:输出流(这里是内存字符串流ss)
    writer->write(root, &ss);
    
    // 4. 输出结果
    // 调用stringstream的str()方法,获取内存中的JSON字符串并打印到控制台
    std::cout << ss.str() << std::endl;
    
    return 0;
}
bash 复制代码
$ ./test.exe
{
    "name" : "joe",
    "sex" : "男"
}

3. 使用 Json::FastWriter

  • 优点:比 StyledWriter 更快,因为它不添加额外的空格和换行符。示例如下:
cpp 复制代码
#include <iostream>
#include <string>
// 引入jsoncpp库核心头文件,提供JSON构建和序列化相关的类
#include <jsoncpp/json/json.h>

int main()
{
    // 1. 构建JSON数据对象
    // 创建Json::Value根对象,作为JSON数据的载体,存储键值对形式的JSON数据
    Json::Value root;
    // 向JSON根对象添加键值对
    root["name"] = "joe";
    root["sex"] = "男";

    // 2. 创建FastWriter序列化器对象
    // Json::FastWriter是jsoncpp中专门用于生成"紧凑版"JSON字符串的类
    // 相比toStyledString()的美化版,FastWriter生成的字符串无缩进、无换行,体积更小,适合网络传输/数据存储
    Json::FastWriter writer;

    // 3. 序列化JSON对象为字符串
    // 调用FastWriter的write()方法,将Json::Value对象转换为紧凑的JSON字符串
    std::string s = writer.write(root);

    // 4. 输出序列化后的JSON字符串到控制台
    std::cout << s << std::endl;

    return 0;
}
bash 复制代码
$ ./test.exe
{"name":"joe","sex":"男"}

4. 使用 Json::StyledWriter writer;

cpp 复制代码
#include <iostream>
#include <string>
// 引入jsoncpp库核心头文件,提供JSON数据构建、序列化的核心类(如Json::Value、StyledWriter)
#include <jsoncpp/json/json.h>

int main()
{
    // 1. 构建JSON数据载体(核心对象)
    // 创建Json::Value类型的根对象,可理解为一个空的JSON容器,用于存储键值对形式的JSON数据
    Json::Value root;
    // 向JSON根对象添加第一个键值对
    root["name"] = "joe";
    root["sex"] = "男";

    // 2. 创建StyledWriter序列化器对象
    // Json::StyledWriter是jsoncpp中专门用于生成"美化版"JSON字符串的类
    // 相比FastWriter的紧凑版,StyledWriter会自动添加缩进、换行,让JSON字符串更易读,适合调试/日志输出
    Json::StyledWriter writer;

    // 3. 将JSON对象序列化为美化版字符串
    // 调用StyledWriter的write()方法,把Json::Value对象转换为带缩进/换行的JSON字符串
    std::string s = writer.write(root);

    // 4. 输出序列化后的JSON字符串到控制台
    std::cout << s << std::endl;

    return 0;
}
bash 复制代码
$ ./test.exe
{
    "name" : "joe",
    "sex" : "男"
}

4.4 Json反序列化方式

反序列化指的是将序列化后的数据重新转换为原来的数据结构或对象。Jsoncpp提供了以下方法进行反序列化:

1. 使用 Json::Reader

  • 优点:提供详细的错误信息和位置,方便调试。示例如下:
cpp 复制代码
#include <iostream>
#include <string>
// 引入jsoncpp库核心头文件,提供JSON解析(Reader)和数据存储(Value)的核心类
#include <jsoncpp/json/json.h>

int main() {
    // 1. 定义待解析的JSON格式字符串
    // 注意:C++中双引号需要用反斜杠转义(\"),该字符串包含name(字符串)、age(整数)、city(字符串)三个键值对
    std::string json_string = "{\"name\":\"张三\", \"age\":30,\"city\":\"北京\"}";

    // 2. 创建JSON解析器对象
    // Json::Reader是jsoncpp中专门用于解析JSON字符串/文件的类,负责将JSON格式的文本转换为Json::Value对象
    Json::Reader reader;

    // 3. 创建Json::Value对象,作为解析后的数据载体
    // Json::Value可以理解为一个"万能容器",能存储字符串、整数、数组、对象等各种JSON数据类型
    Json::Value root;

    // 4. 执行JSON解析操作
    // 调用Reader的parse()方法:将JSON字符串解析到root对象中
    // 返回值为bool类型:true表示解析成功,false表示解析失败(如JSON格式错误)
    bool parsingSuccessful = reader.parse(json_string, root);

    // 5. 解析错误处理(核心:避免非法访问解析失败的空数据)
    if (!parsingSuccessful) {
        // 解析失败时,输出格式化的错误信息(包含错误位置、原因,便于调试)
        std::cout << "Failed to parse JSON: " << reader.getFormattedErrorMessages() << std::endl;
        return 1; // 返回非0值表示程序异常退出
    }

    // 6. 从解析后的Json::Value对象中提取数据
    
    std::string name = root["name"].asString();// asString():将Json::Value中存储的字符串类型数据转换为C++ std::string
    int age = root["age"].asInt();// asInt():将Json::Value中存储的整数类型数据转换为C++ int
    std::string city = root["city"].asString();// asString():提取city字段的字符串值

    // 7. 输出解析后的结果,验证数据正确性
    std::cout << "Name: " << name << std::endl;
    std::cout << "Age: " << age << std::endl;
    std::cout << "City: " << city << std::endl;

    return 0;
}
bash 复制代码
$ ./test.exe
Name: 张三
Age: 30
City: 北京

2. 使用 Json::CharReader 的派生类(不推荐了,上面的足够了):

  • 在某些情况下,你可能需要更精细地控制解析过程,可以直接使用 Json::CharReader 的派生类。
  • 但通常情况下,使用 Json::parseFromStream 或Json::Reader 的parse 方法就足够了。

4.5 总结

  • toStyledString 、StreamWriter 和FastWriter 提供了不同的序列化选项,你可以根据具体需求选择使用。

  • Json::Reader 和parseFromStream 函数是Jsoncpp中主要的反序列化工具,它们提供了强大的错误处理机制。

  • 在进行序列化和反序列化时,请确保处理所有可能的错误情况,并验证输入和输出的有效性。

相关推荐
北京耐用通信18 小时前
耐达讯自动化CANopen转Profibus 网关:实现光伏逆变器无缝接入工业以太网的技术解析
网络·人工智能·物联网·网络协议·自动化·信息与通信
Wadli19 小时前
项目5 |HTTP服务框架
网络·网络协议·http
yuanmenghao19 小时前
CAN系列 — (8) 为什么 Radar Object List 不适合“直接走 CAN 信号”
网络·数据结构·单片机·嵌入式硬件·自动驾驶·信息与通信
CCPC不拿奖不改名19 小时前
网络与API:HTTP基础+面试习题
网络·python·网络协议·学习·http·面试·职场和发展
乾元19 小时前
无线定位与链路质量预测——从“知道你在哪”,到“提前知道你会不会掉线”的网络服务化实践
运维·开发语言·人工智能·网络协议·重构·信息与通信
切糕师学AI19 小时前
SSL是什么?
网络协议
Tao____19 小时前
企业级物联网平台
java·网络·物联网·mqtt·网络协议
eggcode19 小时前
C#读写Bson格式的文件
c#·json·bson
ipooipoo118819 小时前
跨境电商IP选型指南:静态IP vs 动态IP 的区别
网络·网络协议·tcp/ip