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

这篇指南将涵盖以下内容:
- 为什么选择 MessagePack for C?
- 安装与准备
- 核心概念:
msgpack_sbuffer_t,msgpack_packer_t,msgpack_unpacker_t - 基本操作:序列化
- 基本操作:反序列化
- 一个完整的 C 示例
- 高级数据结构处理
- 总结与最佳实践
为什么选择 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 克隆并编译:

git clone https://github.com/msgpack/msgpack-c.git cd msgpack-c # 创建构建目录 mkdir build && cd build # 配置 cmake .. # 编译和安装 make sudo make install
在 Visual Studio (Windows) 上
- 从 GitHub Releases 下载最新的 zip 文件。
- 解压文件。
- 在 Visual Studio 中创建一个新的 C/C++ 项目。
- 项目属性 -> C/C++ -> 常规 -> 附加包含目录:添加 msgpack-c/include 目录。
- 链接器 -> 常规 -> 附加库目录:添加 msgpack-c/lib 目录(根据你的编译配置选择 x64 或 x86)。
- 链接器 -> 输入 -> 附加依赖项:添加
msgpackc.lib。
核心概念
MessagePack-C 的 API 主要围绕三个核心结构体:
-
msgpack_sbuffer_t(String Buffer)- 一个内存缓冲区,用于存储序列化后的二进制数据。
- 你可以把它想象成一个动态增长的字节数组。
-
msgpack_packer_t(Packer)- 一个“打包器”对象,它持有一个
msgpack_sbuffer_t。 - 你通过调用它的函数(如
pack_int,pack_str,pack_array等)将 C 数据类型写入缓冲区,完成序列化。
- 一个“打包器”对象,它持有一个
-
msgpack_unpacker_t(Unpacker)
(图片来源网络,侵删)- 一个“解包器”对象,用于从二进制数据流中逐步解析数据。
- 它非常适合处理从网络或文件读取的流式数据,不需要一次性将所有数据加载到内存中。
数据结构 msgpack_object
- 当你反序列化数据时,解析结果会被填充到一个
msgpack_object结构体中。 - 这个结构体是一个联合体,可以表示 MessagePack 支持的所有类型(整数、浮点数、字符串、数组、Map等)。
- 你需要检查
msgpack_object的type字段,然后根据类型访问其对应的值(如果是MAP,就访问via.map成员)。
基本操作:序列化
序列化的过程就是将 C 数据结构转换成 MessagePack 二进制格式。
步骤:
- 创建并初始化一个
msgpack_sbuffer_t。 - 创建并初始化一个
msgpack_packer_t,将其与sbuffer关联。 - 使用
packer的 API 依次写入数据。 - 从
sbuffer中获取二进制数据。 - 销毁
packer和sbuffer以释放内存。
// 序列化一个简单的结构体: { "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 结构。
步骤:
- 创建并初始化一个
msgpack_unpacker_t。 - 将二进制数据“喂”给
unpacker。 - 循环调用
msgpack_unpacker_next来解析数据对象。 - 检查
msgpack_object的类型,并安全地访问其数据。 - 销毁
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 虽然略显底层,但功能强大且高效,一旦掌握了核心概念,处理各种数据序列化任务就会变得非常得心应手。
