我们之前已经学过了序列化,例如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[],没有结构化的数据,何谈序列化!
但是如果对象是一个结构体,那个是包含了几个多个字节的类型对象的,在场景中,序列化的目的不再是解决大小端存储的差异性问题了,而是保证接收方可以正确的识别那几个字节的数据是哪个对象的!
所以!这就是序列化的第二个目的--->保证接收方正确识别和解析结构化的对象;
如果传递的是结构体类型的数据时,如果不是序列化成文本类型(字符串),
1234
→"1234"
),每个字符按编码(如 UTF-8)转为单字节或多字节字符编码。
1234
在 JSON 中是字符串"1234"
,存储为 UTF-8 字符流0x31 0x32 0x33 0x34
,每个字节独立表示字符,无多字节数据的顺序问题。
int32 0x12345678
为字节流0x78 0x56 0x34 0x12
,大端平台解析为0x78563412
,与原值完全不同。
htonl()
/htons()
转大端;
ntohl()
/ntohs()
转回本地字节序。
必须序列化的原因
结构体包含多个字段,需解决:
int age
和int score
的字节流;
age
还是score
;
int
和float
的字节流。
示例:结构体序列化流程
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字节序转换)
}
htonl
转网络字节序,或文本序列化规避端序。
name
后age
);
Tag
);
场景 | 序列化策略 | 示例代码 |
---|---|---|
简单数值传输 | 直接转网络字节序(极简序列化) | net_port = htons(local_port) |
结构体跨平台通信 | 二进制序列化 + 网络字节序 | Protobuf 生成代码 +htonl转换 |
异构系统集成 | 文本序列化(JSON/XML) | json.dumps(obj) → 字符串传输 |
通过将技术概念与生活场景类比,可以更直观地理解序列化的本质 —— 它不仅是字节序的转换,更是数据结构的跨平台 "翻译规则"。在实际开发中,根据数据复杂度和性能需求选择合适的序列化方式,是保障系统稳定性的关键。