C语言如何使用msgpack进行数据序列化?

99ANYc3cd6
预计阅读时长 33 分钟
位置: 首页 C语言 正文

MessagePack 是一种高效的二进制序列化格式,类似于 JSON,但它更小、更快,非常适合在网络传输、数据存储或进程间通信(IPC)中需要高性能和低带宽的场景。

c语言 msgpack
(图片来源网络,侵删)

下面我将从以下几个方面进行介绍:

  1. 什么是 MessagePack?
  2. 为什么在 C 语言中使用 MessagePack?
  3. 环境搭建(安装)
  4. 核心概念与 API
  5. 完整代码示例(序列化与反序列化)
  6. 高级用法
  7. 与其他格式的对比

什么是 MessagePack?

你可以把它想象成一个“二进制的 JSON”,它将数据结构(如字典、数组、字符串、整数等)转换成紧凑的二进制格式。

JSON 示例:

{
  "name": "Alice",
  "age": 30,
  "isStudent": false,
  "scores": [88, 92, 76]
}

MessagePack 对应的二进制数据 (十六进制表示): 84 a6 6e 61 6d 65 a5 41 6c 69 63 65 a3 61 67 65 1a 1e a5 69 73 53 74 75 64 65 6e 74 c3 a6 73 63 6f 72 65 94 58 56 58 5c 58 34 5a 5c 5c 58 34 5a 5c 58 34 4c

c语言 msgpack
(图片来源网络,侵删)

可以看到,二进制数据比文本形式的 JSON 短得多,解析速度也更快。


为什么在 C 语言中使用 MessagePack?

  • 高性能: C 语言是编译型语言,直接操作内存,MessagePack 的 C 库非常轻量且高效,序列化和反序列化的速度极快。
  • 低内存占用: 生成的二进制数据比 JSON 小,非常适合嵌入式设备或对内存和带宽敏感的应用。
  • 跨平台: MessagePack C 库可以在几乎所有操作系统和架构上编译和运行。
  • 无依赖: 大多数 MessagePack C 库(如官方的 msgpack-c)是纯 C 实现,不依赖其他复杂的库。

环境搭建(安装)

最常用的 C 语言库是官方的 msgpack-c,下面介绍几种常见的安装方法。

使用包管理器(推荐)

  • Ubuntu/Debian:

    sudo apt-get update
    sudo apt-get install libmsgpack-dev
  • macOS (使用 Homebrew):

    c语言 msgpack
    (图片来源网络,侵删)
    brew install msgpack

安装后,编译器就能找到头文件 msgpack.h,链接器也能找到库文件 msgpack

从源码编译

如果你想使用最新版或者需要特定配置,可以从 GitHub 克隆源码编译。

# 克隆仓库
git clone https://github.com/msgpack/msgpack-c.git
cd msgpack-c
# 创建构建目录
mkdir build
cd build
# 配置和编译
cmake ..
make
# 可选:安装到系统(可能需要 sudo)
# sudo make install

编译时需要链接 -lmsgpack


核心概念与 API

msgpack-c 的 API 设计围绕着一个核心结构:msgpack_object

  • msgpack_sbuffer_t: 一个简单的内存缓冲区,用于存储序列化后的二进制数据。
  • msgpack_packer_t: 一个“打包器”,你向它添加数据,它会将数据序列化到 msgpack_sbuffer_t 中。
  • msgpack_unpacker_t: 一个“解包器”,它从二进制数据流中逐个解析出 msgpack_object
  • msgpack_object: 表示一个已解析的 MessagePack 对象,它是一个联合体(union),可以表示整数、浮点数、字符串、数组、字典等所有类型。

主要 API 函数

功能 序列化 (打包) 反序列化 (解包)
创建/销毁 msgpack_sbuffer_new() / msgpack_sbuffer_free() msgpack_unpacker_new() / msgpack_unpacker_free()
初始化 msgpack_packer_init() msgpack_unpacker_reserve_buffer()
添加数据 msgpack_pack_str(), msgpack_pack_int(), msgpack_pack_array(), msgpack_pack_map() msgpack_unpack()
访问数据 (不直接访问,数据写入缓冲区) msgpack_object() 获取 msgpack_object,然后根据其 type 字段访问具体值

完整代码示例(序列化与反序列化)

这个例子演示了如何创建一个 C 结构体,将其序列化为 MessagePack 二进制数据,然后再从二进制数据反序列化回 C 结构体。

代码 (msgpack_example.c)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <msgpack.h>
// 定义我们要序列化的结构体
typedef struct {
    char name[32];
    int age;
    int is_student;
    int scores[3];
} user_data_t;
// 序列化函数
void serialize_user_data(msgpack_sbuffer* sbuf, const user_data_t* data) {
    msgpack_packer pk;
    msgpack_packer_init(&pk, sbuf, msgpack_sbuffer_write);
    // 1. 开始打包一个map(字典),包含4个键值对
    msgpack_pack_map(&pk, 4);
    // 2. 打包 "name" 键和对应的字符串值
    msgpack_pack_str(&pk, strlen(data->name));
    msgpack_pack_str_body(&pk, data->name, strlen(data->name));
    // 3. 打包 "age" 键和对应的整数值
    msgpack_pack_str(&pk, 3);
    msgpack_pack_str_body(&pk, "age", 3);
    msgpack_pack_int(&pk, data->age);
    // 4. 打包 "is_student" 键和对应的布尔值
    msgpack_pack_str(&pk, 10);
    msgpack_pack_str_body(&pk, "is_student", 10);
    msgpack_pack_bool(&pk, data->is_student);
    // 5. 打包 "scores" 键和对应的数组值
    msgpack_pack_str(&pk, 6);
    msgpack_pack_str_body(&pk, "scores", 6);
    msgpack_pack_array(&pk, 3);
    for (int i = 0; i < 3; i++) {
        msgpack_pack_int(&pk, data->scores[i]);
    }
}
// 反序列化函数
void deserialize_user_data(const char* data, size_t len, user_data_t* out_data) {
    msgpack_unpacker unp;
    msgpack_unpacker_init(&unp, MSGPACK_UNPACKER_BUFFER_SIZE);
    msgpack_unpacker_reserve_buffer(&unp, len);
    memcpy(msgpack_unpacker_buffer(&unp), data, len);
    msgpack_object root;
    msgpack_unpack_result result = msgpack_unpack(&unp, &root);
    if (result.success) {
        // 确保根对象是一个 map
        if (root.type != MSGPACK_OBJECT_MAP) {
            fprintf(stderr, "Error: Root object is not a map.\n");
            return;
        }
        // 遍历 map 中的键值对
        for (uint32_t i = 0; i < root.via.map.size; i++) {
            msgpack_object key = root.via.map.ptr[i].key;
            msgpack_object val = root.via.map.ptr[i].val;
            // 根据 key 的类型和值来处理
            if (key.type == MSGPACK_OBJECT_STR) {
                if (strncmp(key.via.str.ptr, "name", key.via.str.size) == 0) {
                    if (val.type == MSGPACK_OBJECT_STR) {
                        strncpy(out_data->name, val.via.str.ptr, val.via.str.size);
                        out_data->name[val.via.str.size] = '\0';
                    }
                } else if (strncmp(key.via.str.ptr, "age", key.via.str.size) == 0) {
                    if (val.type == MSGPACK_OBJECT_POSITIVE_INTEGER) {
                        out_data->age = (int)val.via.u64;
                    }
                } else if (strncmp(key.via.str.ptr, "is_student", key.via.str.size) == 0) {
                    if (val.type == MSGPACK_OBJECT_BOOLEAN) {
                        out_data->is_student = val.via.boolean ? 1 : 0;
                    }
                } else if (strncmp(key.via.str.ptr, "scores", key.via.str.size) == 0) {
                    if (val.type == MSGPACK_OBJECT_ARRAY && val.via.array.size == 3) {
                        for (uint32_t j = 0; j < val.via.array.size; j++) {
                            if (val.via.array.ptr[j].type == MSGPACK_OBJECT_POSITIVE_INTEGER) {
                                out_data->scores[j] = (int)val.via.array.ptr[j].via.u64;
                            }
                        }
                    }
                }
            }
        }
    } else {
        fprintf(stderr, "Error: Failed to unpack.\n");
    }
}
int main() {
    // 1. 准备要序列化的数据
    user_data_t user_data;
    strncpy(user_data.name, "Bob", sizeof(user_data.name) - 1);
    user_data.age = 25;
    user_data.is_student = 1;
    int temp_scores[] = {95, 88, 91};
    memcpy(user_data.scores, temp_scores, sizeof(temp_scores));
    printf("--- Original Data ---\n");
    printf("Name: %s\n", user_data.name);
    printf("Age: %d\n", user_data.age);
    printf("Is Student: %s\n", user_data.is_student ? "true" : "false");
    printf("Scores: [%d, %d, %d]\n\n", user_data.scores[0], user_data.scores[1], user_data.scores[2]);
    // 2. 序列化
    msgpack_sbuffer* sbuf = msgpack_sbuffer_new();
    serialize_user_data(sbuf, &user_data);
    printf("--- Serialized Data (Hex) ---\n");
    for (size_t i = 0; i < sbuf->size; i++) {
        printf("%02x ", (unsigned char)sbuf->data[i]);
    }
    printf("\n\nSize: %zu bytes\n\n", sbuf->size);
    // 3. 反序列化
    user_data_t deserialized_user;
    deserialize_user_data(sbuf->data, sbuf->size, &deserialized_user);
    printf("--- Deserialized Data ---\n");
    printf("Name: %s\n", deserialized_user.name);
    printf("Age: %d\n", deserialized_user.age);
    printf("Is Student: %s\n", deserialized_user.is_student ? "true" : "false");
    printf("Scores: [%d, %d, %d]\n", deserialized_user.scores[0], deserialized_user.scores[1], deserialized_user.scores[2]);
    // 4. 清理
    msgpack_sbuffer_free(sbuf);
    return 0;
}

如何编译和运行:

确保你已经安装了 libmsgpack-dev

# 编译
gcc msgpack_example.c -o msgpack_example -lmsgpack
# 运行
./msgpack_example

预期输出:

--- Original Data ---
Name: Bob
Age: 25
Is Student: true
Scores: [95, 88, 91]
--- Serialized Data (Hex) ---
84 a4 6e 61 6d 65 a3 42 6f 62 a3 61 67 65 1a 19 a9 69 73 5f 73 74 75 64 65 6e 74 c3 a6 73 63 6f 72 65 94 58 5f 5a 5c 5a 5c 5a 4c
Size: 43 bytes
--- Deserialized Data ---
Name: Bob
Age: 25
Is Student: true
Scores: [95, 88, 91]

高级用法

  • 流式处理: 对于非常大的数据,一次性加载到内存中不现实。msgpack-unpack 模块支持流式处理,可以逐块读取数据并解析。
  • Zone-based 内存管理: msgpack-c 提供了一个 msgpack_zone 机制,可以更高效地管理反序列化时创建的字符串等动态内存,避免频繁的 malloc/free
  • C++ 绑定: msgpack-c 也提供了 C++ API,可以使用 msgpack::object 和模板函数,代码更简洁、类型更安全。

与其他格式的对比

特性 MessagePack JSON Protocol Buffers (Protobuf)
数据格式 二进制 文本 二进制
可读性 极佳
性能 非常高 较低 非常高
空间占用 非常小 较大
Schema/IDL 无需 无需 需要 (.proto 文件)
代码生成 需要 (编译 .proto 生成代码)
动态性 (可解析未知结构) (可解析未知结构) 低 (需要预定义所有结构)

选择建议:

  • MessagePack: 当你需要高性能、低带宽,并且数据结构是动态或半动态的,非常适合 RPC 框架(如 MessagePack-RPC)、日志系统、缓存等。
  • JSON: 当你需要人类可读性,并且性能要求不是极端苛刻的场景,Web API 是其最经典的应用。
  • Protocol Buffers: 当你需要极致的性能和强类型,并且愿意接受静态 Schema 和代码生成的开销,适合微服务架构、游戏后端等对性能和类型安全要求极高的场景。
-- 展开阅读全文 --
头像
C语言中sigsegv错误究竟是如何产生的?
« 上一篇 今天
织梦 当前位置 主页为何显示异常?
下一篇 » 今天

相关文章

取消
微信二维码
支付宝二维码

目录[+]