TCP:协议、序列化与反序列化、JSON 数据和jsoncpp

协议

概念:

在 Linux 网络 的语境下,协议 指的是一套标准化的规则和约定,它规定了网络中不同的计算机、设备或程序之间如何进行可靠、高效的数据通信。简单来说,协议就是设备之间通信的"共同语言"和"行为准则"。没有协议,网络设备就像说着不同语言、没有交通规则的人在街上乱走,无法有效沟通。

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

序列化与反序列化

在Linux网络编程中,序列化 和 反序列化 是将数据转换为适合网络传输的格式(和反向转换)的关键过程。下面详细解释这两个概念及其在Linux网络中的应用:

什么是序列化与反序列化

• 定义结构体来表示我们需要交互的信息;

• 发送数据时将这个结构体按照一个规则转换成字符串, 接收到数据的时候再按照相同的规则把字符串转化回结构体;

• 这个过程叫做"序列化" 和"反序列化"

序列化(Serialization)

将数据结构或对象状态转换为字节流的过程,以便:

  • 网络传输
  • 持久化存储
  • 进程间通信

反序列化(Deserialization)

将字节流还原为原始数据结构或对象的过程。

为什么需要序列化

  1. 方便网络发送
  2. 方便协议的可扩展性和可维护性
cpp 复制代码
// 示例:直接发送结构体会有问题
struct Person {
    char name[50];
    int age;
    float salary;
};

// 问题:
// 1. 内存对齐差异
// 2. 字节序差异(大端/小端)
// 3. 结构体填充差异
// 4. 指针无效(指向的地址在接收端无意义)

下面这幅图详细展示了序列化与反序列化

一幅图理解tcp 为什么支持全双工

• 在任何一台主机上,TCP 连接既有发送缓冲区,又有接受缓冲区,所以,在内核中,可以在发消息的同时,也可以收消息,即全双工

• 这就是为什么一个tcp sockfd 读写都是它的原因

• 实际数据什么时候发,发多少,出错了怎么办,由TCP 控制,所以TCP 叫做传输控制协议

由此,我们可以得到几个结论

  1. TCP网络发送数据本质是把数据从发送缓冲区通过网络拷贝到对端的接受缓冲区
  2. TCP支持全双工本质是有一对接受和发送缓冲区
  3. 我们认为在每一个发送单元都是一个cp问题,是用户和内核之间进行生产和消费
  4. 发送和接收数据时数据有没有发完整、收完整,TCP并不关心,而是由用户自己控制维护(如数据包粘包问题)

什么是粘包问题?

粘包(Packet Sticking/Concatenation) 是指在网络通信中,接收方一次性接收到的数据包含了多个独立的应用层消息,或者一个完整的消息被拆分到多个数据包中接收的现象。

根本原因

TCP协议的特性(主要原因):

○ TCP是面向字节流的协议,没有消息边界概念

○ 数据在传输层被分割成TCP段,在接收端重新组装

○ 发送方多次write()的数据可能被合并成一个TCP段发送

○ 接收方一次read()可能读取到多个应用层消息

内核缓冲区机制:

○ 发送/接收缓冲区可能导致数据积累和合并

UDP vs TCP

  • UDP:不会出现粘包,因为UDP是面向数据报的,每个sendto()对应一个独立的数据报
  • TCP:必然需要考虑粘包问题,是流式协议的特性决定的

总结 :粘包不是TCP的缺陷,而是其流式特性的必然结果。解决粘包问题的核心在于设计明确的应用层消息格式,确保接收方能正确识别每个独立的消息边界。

JSON 数据

JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式,它使用人类可读的文本来表示结构化数据。JSON 已成为现代 Web 应用和 API 中最常用的数据格式之一。

基本特点

  1. 轻量级:相比 XML 更简洁,占用空间小
  2. 易读性:文本格式,人类可直接阅读和理解
  3. 语言独立:几乎所有编程语言都支持 JSON
  4. 自描述性:数据结构清晰,容易理解

JSON 的六种数据类型

字符串(String)

cpp 复制代码
"这是一个字符串"
"name": "张三"
"email": "zhangsan@example.com"

数字(Number)

cpp 复制代码
42          // 整数
3.14        // 浮点数
-10         // 负数
2.5e4       // 科学计数法(25000)

布尔值(Boolean)

cpp 复制代码
true
false

空值(Null)

cpp 复制代码
null

对象(Object)

cpp 复制代码
{
  "name": "张三",
  "age": 25,
  "isStudent": true,
  "address": {
    "city": "北京",
    "street": "中关村"
  }
}

数组(Array)

cpp 复制代码
["苹果", "香蕉", "橙子"]
[1, 2, 3, 4, 5]
[{"id": 1}, {"id": 2}, {"id": 3}]

JSON 语法规则

  1. 键值对结构:"键":值
  2. 逗号分隔:键值对之间用逗号分隔
  3. 花括号包裹对象:{ }
  4. 方括号包裹数组:
  5. 键必须是字符串:必须用双引号包裹
  6. 值可以是任意类型:字符串、数字、对象、数组等

JSON 的局限性

  1. 不支持注释:JSON 规范不支持注释
  2. 二进制数据:不适合存储二进制数据(需要使用 Base64 编码)
  3. 日期格式:没有内置的日期类型(通常用 ISO 8601 字符串表示)
  4. 文件大小:对于非常大的数据,可能不如二进制格式高效

jsoncpp

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

主要特点

跨平台支持

  • 支持 Windows、Linux、macOS 等主流操作系统
  • 与多种编译器兼容(GCC、Clang、MSVC 等)

核心功能

  • JSON 解析:将 JSON 字符串或文件解析为 C++ 数据结构
  • JSON 序列化:将 C++ 数据结构转换为 JSON 字符串
  • JSON 操作:增删改查 JSON 数据
  • JSON 验证:验证 JSON 数据的有效性

主要组件

Json::Value

  • JSON 值的容器,可表示对象、数组、字符串、数字等
  • 支持类似字典的访问方式

Json::Reader (旧版) / Json::CharReader (新版)

  • 用于解析 JSON 字符串

Json::Writer (旧版) / Json::StreamWriter (新版)

  • 用于将 Json::Value 序列化为字符串

使用

序列化

序列化指的是将数据结构或对象转换为一种格式,以便在网络上传输或存储到文件中。Jsoncpp 提供了多种方式进行序列化:toStyledString、StreamWriter 和FastWriter,这里只介绍StreamWriter(这个最常用也是最佳实践)

使用Json::StreamWriter:

○优点:提供了更多的定制选项,如缩进、换行符等。

○示例:

cpp 复制代码
#include <iostream>
#include <string>
#include <sstream>
#include <memory>
#include <jsoncpp/json/json.h>
using namespace std;
int main()
{
    Json::Value root;
    root["name"] = "joe";
    root["sex"] = "男";
    Json::StreamWriterBuilder wbuilder; // StreamWriter 的工厂
    wbuilder["emitUTF8"] = true;        // 输出会保留 UTF-8 字符的原生形式
    unique_ptr<Json::StreamWriter> writer(wbuilder.newStreamWriter());
    stringstream ss;
    writer->write(root, &ss);
    cout << ss.str() << endl;
    return 0;
}

反序列化

反序列化指的是将序列化后的数据重新转换为原来的数据结构或对象。Jsoncpp 提供了以下方法进行反序列化:Json::Reader和Json::CharReader 的派生类,这里只介绍Json::Reader

** 使用Json::Reader:**

○优点:提供详细的错误信息和位置,方便调试。

○示例:

cpp 复制代码
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>
using namespace std;
int main()
{
    // JSON 字符串
     //在字符串中,如果你想包含一个双引号,但双引号又是字符串的边界符,这时就需要转义。
    string json_string = "{\"name\":\"张三\",\"age\":30, \"city\":\"北京\"}";
    // 解析JSON 字符串
    Json::Reader reader;
    Json::Value root;
    // 从字符串中读取JSON 数据
    bool parsingSuccessful = reader.parse(json_string, root);
    if (!parsingSuccessful)
    {
        // 解析失败,输出错误信息
        cout << "Failed to parse JSON: " << reader.getFormattedErrorMessages() << endl;
        return 1;
    }
    // 访问JSON 数据
    string name = root["name"].asString();
    int age = root["age"].asInt();
    string city = root["city"].asString();
    // 输出结果
    cout << "Name: " << name << endl;
    cout << "Age: " << age << endl;
    cout << "City: " << city << endl;
    return 0;
}

优点

  1. 简单易用:API 设计直观
  2. 纯 C++:不依赖其他语言或运行时
  3. 性能良好:满足大多数应用场景需求
  4. 活跃维护:持续更新和维护
  5. 头部文件单一:#include <jsoncpp/json/json.h>

缺点

  1. 错误处理:错误信息有时不够详细
  2. 性能:对于超大型 JSON 文件,性能可能不如专门的解析器
  3. 内存使用:解析时会一次性加载整个 JSON 到内存

适用场景

  • 配置文件读取/写入
  • API 数据交换
  • 数据持久化
  • 与 Web 服务通信
  • 日志记录

安装

cpp 复制代码
ubuntu:sudo apt-get install libjsoncpp-dev
Centos: sudo yum install jsoncpp-devel
相关推荐
_upupup8 小时前
Linux的调试工具——gdb/cgdb
linux·服务器
JeJe同学8 小时前
Docker镜像导入、容器启动
linux·运维·docker
木子n18 小时前
车载以太网与SOME/IP服务化通信实战-01.从字节到服务:SOME/IP报文解析与四通道开销账
网络·网络协议·tcp/ip·车载以太网·some/ip
Jason_zhao_MR8 小时前
Linux与实时控制如何兼得
linux·嵌入式硬件·机器人·工业控制
闲云野鹤在人间9 小时前
MySQL|从理论、安装、备份到主从复制、MHA高可用详解
linux·运维·数据库·mysql·云计算
Escalating_xu10 小时前
【C 语言】数据在内存中的存储:补码、大小端、整型陷阱与 IEEE 754 全解析
java·c语言·网络
胖头鱼的鱼缸(尹海文)10 小时前
胖头鱼的技术专栏-474 从能跑到能上线:PG SplitJSON 0.2.0 和 0.1.0 差在哪(20261008)
postgresql·json
一个风轻云淡10 小时前
GCC 和 GDB命令简单解读
java·linux·前端
爱跳舞的烤冷面10 小时前
Linux篇——网络编程
网络
H.莓飛10 小时前
【数据结构】二叉树_OJ题
linux·开发语言·数据结构·算法