序列化与反序列化

1. 引言

进行网络通信时,很重要的一点,就是要把想传输给其它主机的数据,进行序列化之后,才能调用如write这样的接口,发送到网络中。

对方接收到后,解析所接收到的数据,还原为原始数据,这就是反序列化的过程。

2. 理解

如何理解序列化呢?

主机A与主机B要进行网络通信,主机A有一份数据,想通过网络,发送给B,首先,A要把原始数据,转换成一种更通用,且便于解析的格式的数据。就是要把原始数据转换为另一种格式的数据,这种格式的数据能够很方便地被其它主机,正确地解析为A最初想要发送的数据。这就是序列化。

2.1 序列化的必要性

网络通信前,为什么要序列化?

因为一台主机上的数据,它不能直接以它在当前主机中,在内存存储的格式,去直接传输给其它主机,原因如结构体的对齐规则不同、指针数据在对方主机中无法使用等。

这就使得其它主机如果接收到的是直接传递的原始数据时,无法正确地去把它解析到当前主机,所以序列化的作用就是,把我当前主机想要传输的数据,转换为一种更通用、方便解析的格式的数据,这个转换的过程就是序列化。

其它主机接收到这个转换后的数据,可以进行正确地解析,从而把其它主机真正想要传输的数据,去存放在当前主机的内存、磁盘中。

2.1.1 结构体的内存对齐规则不同

C/C++ 编译器为了提高内存访问效率,会给结构体加填充字节(padding),但填充规则不是统一的:

一、不同编译器(GCC vs MSVC)对齐规则不同;计算结构体的大小时,首先要计算出,每个成员的对齐数 。对齐数 = min(该成员的大小,编译器默认的对齐数)

比如VS下的默认对齐数是8

二、默认对齐数是可以修改的,#pragma pack(4) ,可以把默认对齐数修改为4

代码:

综上,如果不对结构体对象进行处理,而选择直接发送的话,

比如直接使用send接口向网络中发送数据:

cpp 复制代码
struct Student
{
    int    id;       // 4字节
    char   name[7];  // 7字节
    double  score;   // 8字节
};

Student stu = {1001, "zhangs", 95.5};
send(fd, &stu, sizeof(stu), 0);  

主机A的默认对齐数为4,此时sizeof(stu)为20;

主机B的默认对齐数为8,此时sizeof(stu)为24。

A调用recv,接收到B发来的数据,只会对前20个字节进行处理,解析出的数据,一定是错误的!

2.1.2 大、小端字节序问题

小端机:存储数值型数据时,低权值的字节,存放在低地址处。

如图:

大端机:存储数值型数据时,低权值的字节,存放在高地址处。

一些主机是大端机,一些主机是小端机。所以在内存中存储变量时,存储的格式是不同的。

网络通信为了解决这个问题,规定了网络字节序,采用大端字节序。所以向网络中发送数据前,必须要把保证数据转换为大端字节序。

如果两台主机分别是大端机和小端机,此时不加转换地直接发送原始数据,对方解析后,就是完全错误的!

2.1.3 数据类型长度不一致

而且在16位机器上,int类型 通常占用2个字节

2.1.4 传递指针数据

指针是本地内存的地址编号 ,这个编号只在当前主机的内存空间有效,对方主机的内存地址完全独立,所以传输指针地址毫无意义。

A主机传递一个指针数据,这个指针指向的是A主机中的一段合法的、申请成功的内存,但是如果直接传递给B主机,对于B主机来说,这个指针指向的内存数据,

  • 要么是无效地址(访问崩溃);
  • 要么是主机B自己的内存数据,解析出一堆乱码,完全不是A主机原本想传输的数据。

而序列化要做的,就是「抛弃指针地址,传输指针指向的真实数据内容」。

2.1.5 无法适配TCP 面向字节流的特性

TCP 是 "无边界的字节流",直接 send 结构体会遇到两个问题:

一、粘包:如果连续 send 两个 Student 结构体,服务端无法区分 "第一个结构体的末尾" 和 "第二个结构体的开头";

二、拆包:如果结构体数据被 TCP 拆成两段发送,服务端只收到一半数据,直接解析会导致内存越界、程序崩溃。

而序列化时,我们可以给字节流加长度前缀 (比如[总长度(4字节)][id(4)][name(8)][score(4)]),服务端先读长度,再读对应字节数,完美解决粘包 / 拆包 ------ 直接 send 结构体完全做不到这一点。

相关推荐
lay_liu2 小时前
ubuntu 安装 Redis
linux·redis·ubuntu
志栋智能2 小时前
超自动化巡检:应对复杂IT环境的必然选择
运维·网络·安全·web安全·自动化
li星野2 小时前
[特殊字符] Linux/嵌入式Linux面试模拟卷
linux·运维·面试
上海云盾-小余3 小时前
云主机安全加固:从系统、网络到应用的零信任配置
网络·安全·php
JiMoKuangXiangQu3 小时前
Linux 锁 (4) - seqlock
linux·seqlock
xlp666hub3 小时前
如果操作GPIO可能导致休眠,那么同步机制绝不能采用spinlock
linux·面试
RisunJan4 小时前
Linux命令-mkbootdisk(可建立目前系统的启动盘)
linux·运维·服务器
QCzblack4 小时前
见面考复现
网络
朽棘不雕5 小时前
Linux工具(上)
linux·运维·服务器