序列化问题和网络字节序

发布于:2025-06-14 ⋅ 阅读:(18) ⋅ 点赞:(0)

目录

序言

序列化是什么?

为什么要序列化?

-->什么是大小端存储?

内存地址与字节分布

什么?什么是网络字节序?

拓展:

一、JSON 序列化为何天然规避字节序?

核心原理:字符流无多字节序问题

二、二进制序列化的大小端困境与网络字节序

1. 二进制序列化的 "端序隐患"

2. 网络字节序:二进制数据的 "统一语言"

三、htonl 与序列化的本质区别:简单数据 vs 结构化数据

1. 简单数据(如端口号)的处理

2. 结构体的序列化需求

四、序列化的两大核心目的总结

1. 消除平台差异(大小端、数据类型)

2. 结构化标识(字段解析规则)

五、类比理解:序列化如同 "数据的快递单"

六、实践建议:不同场景的序列化策略


序言

我们之前已经学过了序列化,例如Json,而且我们知道TCP面向字节流的,数据在网络中传输需要进行序列化,以及了解到了网络字节序等关键词,但是他们之间究竟有什么关系呢?似乎我们都有些许疑惑;我们来梳理一下

序列化是什么?

序列化其实就是将我们代码中的结构化的数据转化成另外一种格式,比如Json序列化,我们知道转化到Json格式的key-value格式的数据结构,其类型其实是字符串。

当然不止这一种序列化方式,我们还可以序列化成其他的格式,例如二进制、XML格式等,而不同的格式其特点也是不同的,后序在场景中需要的操作也是不同的。(网络字节序/大小端存储的问题)

好,现在我们知道了什么是序列化,序列化就是将结构化的对象,转化为另外一种特殊的格式,比如Json串,二进制,XML;

但其实这里还可以分类,其中Json和XML都属于是文本类型的数据格式;

这是AI给出的几种序列化目标对象,总结的很好。

那么接下来我们就要探讨为什么要进行序列化了?也就是序列化要解决的是什么问题?

为什么要序列化?

--->解决发送方和接收方在网络中对数据存储顺序的认识不一致的问题。

也就是大小端存储的问题。

-->什么是大小端存储?

我们先来看一下常见的几种数据类型,比如char,int,double等;其中char字符是只有一个字节,而其他的类型是多字节类型,数据的存储顺序可分为两种,一个是小端存储,一个是大端存储。

内存地址与字节分布

假设整数存储在内存地址 0x1000 开始的 4 字节空间:

地址 大端序存储(高位在前) 小端序存储(低位在前)
0x1000 0x12(最高位字节) 0x78(最低位字节)
0x1001 0x34 0x56
0x1002 0x56 0x34
0x1003 0x78(最低位字节) 0x12(最高位字节)

如果网络通信中,发送方数据是大端存储,而接收方是小端存储,那么就会造成双方解析错误;

所以序列化的第一个目的就是解决不同平台的大小端存储的差异

如果我们将数据序列化成Json字符串,那么是不是就不存在字节序的问题了,因为字符串是字符组成的,每一个字符一个字节存储一个数据,没有字节顺序。

但是如果序列化成二进制格式,而二进制格式的数据仍然有大小端存储的区别,所以序列化后还需要进行转化成网络字节序。

什么?什么是网络字节序?

咬文嚼字,网络字节序就是在数据在网络中的存储顺序,其实还是大小端存储的问题,只不过网络字节序指的是规定好的字节序,TCP协议是面向字节流的,其规定的网络字节序就是大端存储,也就是如果使用TCP协议进行通信,那么就必须要将数据转化为网络字节序,才可以正确的在网络中传输。


好!回到刚才的问题,我们说到序列化成二进制格式的数据依旧存在大小端存储的问题,那么就需要将序列化的数据转化成网络字节序。这没问题。

但是我有个问题,我们知道转化成网络字节序可以使用接口htonl,但是我们之前写socket服务端的时候,端口号也需要转化为网络字节序,可是确实直接将变量作为实参传递,这不是没有进行序列化也可以吗?

NO! 这里的区别在于端口号要么是int要么是char[],没有结构化的数据,何谈序列化!

但是如果对象是一个结构体,那个是包含了几个多个字节的类型对象的,在场景中,序列化的目的不再是解决大小端存储的差异性问题了,而是保证接收方可以正确的识别那几个字节的数据是哪个对象的!

所以!这就是序列化的第二个目的--->保证接收方正确识别和解析结构化的对象;

如果传递的是结构体类型的数据时,如果不是序列化成文本类型(字符串),

拓展:


一、JSON 序列化为何天然规避字节序?

核心原理:字符流无多字节序问题
  • 数据表示形式: JSON 将数值转为字符串(如1234"1234"),每个字符按编码(如 UTF-8)转为单字节或多字节字符编码。

    • 例:数字1234在 JSON 中是字符串"1234",存储为 UTF-8 字符流0x31 0x32 0x33 0x34,每个字节独立表示字符,无多字节数据的顺序问题。

  • 跨平台一致性: 接收方按字符串解析后再转为数值,无论发送方是大端还是小端平台,结果一致。

二、二进制序列化的大小端困境与网络字节序

1. 二进制序列化的 "端序隐患"
  • 问题本质

    二进制序列化直接存储数据的内存二进制表示,不同平台的字节序差异会导致解析错误。

    • 例:小端平台序列化int32 0x12345678为字节流0x78 0x56 0x34 0x12,大端平台解析为0x78563412,与原值完全不同。

2. 网络字节序:二进制数据的 "统一语言"
  • 定义: 网络字节序即大端序,是 TCP/IP 协议强制要求的传输格式,要求所有多字节数据在网络传输时转为大端。

  • 与序列化的配合

    1. 发送方序列化二进制数据时,对每个多字节整数调用htonl()/htons()转大端;

    2. 接收方用ntohl()/ntohs()转回本地字节序。

三、htonl 与序列化的本质区别:简单数据 vs 结构化数据

1. 简单数据(如端口号)的处理
  • 为何无需序列化: 端口号(2 字节 int)是单一数据类型,通信双方提前约定 "这是一个端口号,需用大端解析",本质是 "极简序列化"(隐含结构编码:"单一 int 类型,无其他字段")。

  • 代码示例

     uint16_t port = 8080;  // 本地端口(小端存储为0x80 0x20)
     uint16_t net_port = htons(port);  // 转大端为0x20 0x80(0x2080=8320)
     send(sockfd, &net_port, 2, 0);
2. 结构体的序列化需求
  • 必须序列化的原因

    结构体包含多个字段,需解决:

    1. 字段身份绑定:如何区分int ageint score的字节流;

    2. 解析顺序:先读age还是score

    3. 类型标识:如何区分intfloat的字节流。

  • 示例:结构体序列化流程

     struct User {
         char name[20];  // 字符串
         int age;        // 年龄
         float height;   // 身高
     };
     ​
     // 序列化(伪代码)
     void serialize(User* u, uint8_t* buf) {
         // 1. 写入name长度(解决字段长度问题)
         uint32_t name_len = strlen(u->name);
         uint32_t net_name_len = htonl(name_len);
         memcpy(buf, &net_name_len, 4);
         
         // 2. 写入name内容
         memcpy(buf+4, u->name, name_len);
         
         // 3. 写入age(转大端)
         uint32_t net_age = htonl(u->age);
         memcpy(buf+4+name_len, &net_age, 4);
         
         // 4. 写入height(转大端,需按float格式处理)
         // ...(省略float字节序转换)
     }

四、序列化的两大核心目的总结

1. 消除平台差异(大小端、数据类型)
  • 处理对象:多字节数值(int、float 等)的字节序统一。

  • 技术手段:通过htonl转网络字节序,或文本序列化规避端序。

2. 结构化标识(字段解析规则)
  • 处理对象:结构体、对象等复杂数据结构。

  • 技术手段

    • 约定字段顺序(如先nameage);

    • 添加字段标签(如 Protobuf 的Tag);

    • 声明字段类型(如 "4 字节表示 int,8 字节表示 double")。

五、类比理解:序列化如同 "数据的快递单"

  • 大小端处理:相当于要求快递包裹内的多件物品(多字节数据)必须按从大到小排列(大端);

  • 结构化标识:快递单上的 "收件人姓名"" 地址 ""电话" 等字段,确保快递员(接收方)能按规则分拣包裹(解析数据)。

  • 关键:若没有快递单(序列化规则),即使包裹内物品排列整齐(处理了大小端),也无法知道该送给谁(字段映射)。

六、实践建议:不同场景的序列化策略

场景 序列化策略 示例代码
简单数值传输 直接转网络字节序(极简序列化) net_port = htons(local_port)
结构体跨平台通信 二进制序列化 + 网络字节序 Protobuf 生成代码 +htonl转换
异构系统集成 文本序列化(JSON/XML) json.dumps(obj) → 字符串传输

通过将技术概念与生活场景类比,可以更直观地理解序列化的本质 —— 它不仅是字节序的转换,更是数据结构的跨平台 "翻译规则"。在实际开发中,根据数据复杂度和性能需求选择合适的序列化方式,是保障系统稳定性的关键。