Msgpack C语言如何高效使用?

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

MessagePack 是一种高效的二进制序列化格式,类似于 JSON,但更小、更快,非常适合在网络传输或存储数据时使用。

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

这篇指南将涵盖以下内容:

  1. 为什么选择 MessagePack for C?
  2. 安装与准备
  3. 核心概念:msgpack_sbuffer_t, msgpack_packer_t, msgpack_unpacker_t
  4. 基本操作:序列化
  5. 基本操作:反序列化
  6. 一个完整的 C 示例
  7. 高级数据结构处理
  8. 总结与最佳实践

为什么选择 MessagePack for C?

  • 高性能:二进制格式,解析速度快,体积小。
  • 跨语言:几乎所有主流编程语言都有成熟的库。
  • 类型安全:C 库提供了类型检查机制,防止解析错误。
  • 灵活:可以处理 C 语言中的基本数据类型、数组和关联数组(Map)。

安装与准备

你需要安装 MessagePack 的 C 语言库,最常用和维护最好的是官方的 msgpack-c 库。

在 Linux (Debian/Ubuntu) 上安装

sudo apt-get update
sudo apt-get install libmsgpack-dev

这会安装头文件 (msgpack.h) 和库文件 (libmsgpack.so)。

在 macOS (Homebrew) 上安装

brew install msgpack

从源码编译安装

如果包管理器中没有,或者你需要最新版本,可以从 GitHub 克隆并编译:

msgpack c语言
(图片来源网络,侵删)
git clone https://github.com/msgpack/msgpack-c.git
cd msgpack-c
# 创建构建目录
mkdir build && cd build
# 配置
cmake ..
# 编译和安装
make
sudo make install

在 Visual Studio (Windows) 上

  1. GitHub Releases 下载最新的 zip 文件。
  2. 解压文件。
  3. 在 Visual Studio 中创建一个新的 C/C++ 项目。
  4. 项目属性 -> C/C++ -> 常规 -> 附加包含目录:添加 msgpack-c/include 目录。
  5. 链接器 -> 常规 -> 附加库目录:添加 msgpack-c/lib 目录(根据你的编译配置选择 x64 或 x86)。
  6. 链接器 -> 输入 -> 附加依赖项:添加 msgpackc.lib

核心概念

MessagePack-C 的 API 主要围绕三个核心结构体:

  1. msgpack_sbuffer_t (String Buffer)

    • 一个内存缓冲区,用于存储序列化后的二进制数据。
    • 你可以把它想象成一个动态增长的字节数组。
  2. msgpack_packer_t (Packer)

    • 一个“打包器”对象,它持有一个 msgpack_sbuffer_t
    • 你通过调用它的函数(如 pack_int, pack_str, pack_array 等)将 C 数据类型写入缓冲区,完成序列化。
  3. msgpack_unpacker_t (Unpacker)

    msgpack c语言
    (图片来源网络,侵删)
    • 一个“解包器”对象,用于从二进制数据流中逐步解析数据。
    • 它非常适合处理从网络或文件读取的流式数据,不需要一次性将所有数据加载到内存中。

数据结构 msgpack_object

  • 当你反序列化数据时,解析结果会被填充到一个 msgpack_object 结构体中。
  • 这个结构体是一个联合体,可以表示 MessagePack 支持的所有类型(整数、浮点数、字符串、数组、Map等)。
  • 你需要检查 msgpack_objecttype 字段,然后根据类型访问其对应的值(如果是 MAP,就访问 via.map 成员)。

基本操作:序列化

序列化的过程就是将 C 数据结构转换成 MessagePack 二进制格式。

步骤:

  1. 创建并初始化一个 msgpack_sbuffer_t
  2. 创建并初始化一个 msgpack_packer_t,将其与 sbuffer 关联。
  3. 使用 packer 的 API 依次写入数据。
  4. sbuffer 中获取二进制数据。
  5. 销毁 packersbuffer 以释放内存。
// 序列化一个简单的结构体: { "name": "Alice", "age": 30, "scores": [88, 95, 76] }
#include <msgpack.h>
#include <stdio.h>
#include <stdlib.h>
void serialize_example() {
    // 1. 创建并初始化缓冲区
    msgpack_sbuffer* sbuf = msgpack_sbuffer_new();
    msgpack_sbuffer_init(sbuf);
    // 2. 创建并初始化打包器
    msgpack_packer* pk = msgpack_packer_new();
    msgpack_packer_init(pk, sbuf, msgpack_sbuffer_write);
    // 3. 开始打包数据
    // 这是一个 Map (关联数组),包含 3 个 key-value 对
    msgpack_pack_map(pk, 3);
    // --- 第一个键值对: "name" -> "Alice" ---
    // Key: "name" (一个长度为 4 的字符串)
    msgpack_pack_str(pk, 4);
    msgpack_pack_str_body(pk, "name", 4);
    // Value: "Alice" (一个长度为 5 的字符串)
    msgpack_pack_str(pk, 5);
    msgpack_pack_str_body(pk, "Alice", 5);
    // --- 第二个键值对: "age" -> 30 ---
    // Key: "age" (一个长度为 3 的字符串)
    msgpack_pack_str(pk, 3);
    msgpack_pack_str_body(pk, "age", 3);
    // Value: 30 (一个 32 位有符号整数)
    msgpack_pack_int(pk, 30);
    // --- 第三个键值对: "scores" -> [88, 95, 76] ---
    // Key: "scores" (一个长度为 6 的字符串)
    msgpack_pack_str(pk, 6);
    msgpack_pack_str_body(pk, "scores", 6);
    // Value: 一个包含 3 个元素的数组
    msgpack_pack_array(pk, 3);
    msgpack_pack_int(pk, 88);
    msgpack_pack_int(pk, 95);
    msgpack_pack_int(pk, 76);
    // 4. 获取二进制数据
    // sbuf->data 指向二进制数据,sbuf->size 是数据长度
    printf("Serialized data length: %zu\n", sbuf->size);
    // printf("Serialized data (hex): ");
    // for (size_t i = 0; i < sbuf->size; i++) {
    //     printf("%02x ", (unsigned char)sbuf->data[i]);
    // }
    // printf("\n");
    // 5. 释放资源
    msgpack_packer_free(pk);
    msgpack_sbuffer_free(sbuf);
}

基本操作:反序列化

反序列化的过程是将 MessagePack 二进制数据还原成 C 的 msgpack_object 结构。

步骤:

  1. 创建并初始化一个 msgpack_unpacker_t
  2. 将二进制数据“喂”给 unpacker
  3. 循环调用 msgpack_unpacker_next 来解析数据对象。
  4. 检查 msgpack_object 的类型,并安全地访问其数据。
  5. 销毁 unpacker
// 反序列化上面生成的数据
void deserialize_example(const char* data, size_t len) {
    // 1. 创建并初始化解包器
    msgpack_unpacker* unp = msgpack_unpacker_new();
    msgpack_unpacker_init(unp, MSGPACK_UNPACKER_BUFFER_SIZE);
    // 将数据喂给解包器
    msgpack_unpacker_reserve_buffer(unp, len);
    memcpy(msgpack_unpacker_buffer(unp), data, len);
    msgpack_unpacker_buffer_consumed(unp, len);
    // 2. 解析数据
    msgpack_object root;
    msgpack_unpack_return ret = msgpack_unpacker_next(unp, &root);
    if (ret == MSGPACK_UNPACK_SUCCESS) {
        // 3. 检查类型并访问数据
        if (root.type == MSGPACK_OBJECT_MAP) {
            printf("Deserialized a map with %d elements.\n", root.via.map.size);
            for (size_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;
                if (key.type == MSGPACK_OBJECT_STR) {
                    printf("  Key: %.*s\n", (int)key.via.str.size, key.via.str.ptr);
                    if (strcmp(key.via.str.ptr, "name") == 0 && val.type == MSGPACK_OBJECT_STR) {
                        printf("    Value: %.*s\n", (int)val.via.str.size, val.via.str.ptr);
                    } else if (strcmp(key.via.str.ptr, "age") == 0 && val.type == MSGPACK_OBJECT_POSITIVE_INTEGER) {
                        printf("    Value: %d\n", (int)val.via.u64);
                    } else if (strcmp(key.via.str.ptr, "scores") == 0 && val.type == MSGPACK_OBJECT_ARRAY) {
                        printf("    Value (array): [");
                        for (size_t j = 0; j < val.via.array.size; j++) {
                            msgpack_object score = val.via.array.ptr[j];
                            if (score.type == MSGPACK_OBJECT_POSITIVE_INTEGER) {
                                printf("%d", (int)score.via.u64);
                                if (j < val.via.array.size - 1) printf(", ");
                            }
                        }
                        printf("]\n");
                    }
                }
            }
        } else {
            printf("Root object is not a map.\n");
        }
    } else {
        printf("Unpack failed: %s\n", msgpack_unpacker_strerror(unp));
    }
    // 4. 释放资源
    msgpack_unpacker_free(unp);
}

一个完整的 C 示例

下面是一个将序列化和反序列化结合在一起的完整可运行示例。

#include <msgpack.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
    // --- 1. 序列化 ---
    printf("--- Serialization ---\n");
    msgpack_sbuffer* sbuf = msgpack_sbuffer_new();
    msgpack_sbuffer_init(sbuf);
    msgpack_packer* pk = msgpack_packer_new();
    msgpack_packer_init(pk, sbuf, msgpack_sbuffer_write);
    // Pack a map: { "key": 123 }
    msgpack_pack_map(pk, 1);
    // Key: "key"
    msgpack_pack_str(pk, 3);
    msgpack_pack_str_body(pk, "key", 3);
    // Value: 123
    msgpack_pack_int(pk, 123);
    printf("Serialized %zu bytes.\n", sbuf->size);
    // --- 2. 反序列化 ---
    printf("\n--- Deserialization ---\n");
    deserialize_example(sbuf->data, sbuf->size);
    // --- 3. 清理 ---
    msgpack_packer_free(pk);
    msgpack_sbuffer_free(sbuf);
    return 0;
}
// 这里放上面定义的 deserialize_example 函数
void deserialize_example(const char* data, size_t len) {
    msgpack_unpacker* unp = msgpack_unpacker_new();
    msgpack_unpacker_init(unp, MSGPACK_UNPACKER_BUFFER_SIZE);
    msgpack_unpacker_reserve_buffer(unp, len);
    memcpy(msgpack_unpacker_buffer(unp), data, len);
    msgpack_unpacker_buffer_consumed(unp, len);
    msgpack_object root;
    msgpack_unpack_return ret = msgpack_unpacker_next(unp, &root);
    if (ret == MSGPACK_UNPACK_SUCCESS) {
        if (root.type == MSGPACK_OBJECT_MAP && root.via.map.size == 1) {
            msgpack_object key = root.via.map.ptr[0].key;
            msgpack_object val = root.via.map.ptr[0].val;
            if (key.type == MSGPACK_OBJECT_STR && strcmp(key.via.str.ptr, "key") == 0) {
                if (val.type == MSGPACK_OBJECT_POSITIVE_INTEGER) {
                    printf("Found key '%s' with value: %d\n", key.via.str.ptr, (int)val.via.u64);
                }
            }
        }
    } else {
        printf("Unpack failed.\n");
    }
    msgpack_unpacker_free(unp);
}

编译和运行:

gcc -o msgpack_example msgpack_example.c -lmsgpackc
./msgpack_example

预期输出:

--- Serialization ---
Serialized 6 bytes.
--- Deserialization ---
Found key 'key' with value: 123

高级数据结构处理

MessagePack-C 提供了更高级的 API 来简化复杂结构的处理。

使用 msgpack_zone 进行动态内存分配

当你反序列化一个包含字符串或数组的对象时,msgpack_object 中的指针指向的是 msgpack_zone 分配的内存,这个内存是临时的,一旦解包器被销毁,这些内存就会失效,如果你需要长期保存这些数据,你需要自己复制。

使用 msgpack_object_print 调试

有一个非常方便的函数 msgpack_object_print 可以将 msgpack_object 打印成类似 JSON 的格式,非常适合调试。

#include <msgpack.h>
#include <msgpack/fbuffer.h> // 需要包含这个头
// 在你的反序列化循环中
msgpack_object obj;
// ... 解析 obj ...
printf("Parsed object: ");
msgpack_object_print(stdout, obj);
printf("\n");

C++ 集成

如果你在使用 C++,强烈推荐使用 msgpack-c 的 C++ 绑定,它提供了更自然、更安全的接口,可以序列化 STL 容器(如 std::vector, std::map, std::string),并且支持自定义类型的序列化。


总结与最佳实践

  • 检查类型:在访问 msgpack_object 的成员之前,必须检查其 type,否则会导致未定义行为和程序崩溃。
  • 释放资源msgpack_sbuffer_t, msgpack_packer_t, msgpack_unpacker_t 都需要手动 free,否则会造成内存泄漏。
  • 流式处理:对于网络数据或大文件,优先使用 msgpack_unpacker_t,因为它可以分块处理数据,避免一次性加载全部内容。
  • 调试:善用 msgpack_object_print 来查看你反序列化得到的数据结构是否正确。
  • 从简单开始:先掌握基本数据类型(int, str, bool, nil),再尝试数组和 Map,最后处理嵌套结构。

MessagePack for C 的 API 虽然略显底层,但功能强大且高效,一旦掌握了核心概念,处理各种数据序列化任务就会变得非常得心应手。

-- 展开阅读全文 --
头像
dede如何实现多条件搜索功能?
« 上一篇 04-21
c语言Gretchen是什么?
下一篇 » 04-21

相关文章

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

目录[+]