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

(图片来源网络,侵删)
下面我将从以下几个方面进行介绍:
- 什么是 MessagePack?
- 为什么在 C 语言中使用 MessagePack?
- 环境搭建(安装)
- 核心概念与 API
- 完整代码示例(序列化与反序列化)
- 高级用法
- 与其他格式的对比
什么是 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

(图片来源网络,侵删)
可以看到,二进制数据比文本形式的 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):
(图片来源网络,侵删)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 和代码生成的开销,适合微服务架构、游戏后端等对性能和类型安全要求极高的场景。
