mpack简明教程

文章目录

    • 摘要
    • MessagePack简介
    • MPACK的简单使用
    • 在定长的buffer存储不定长的数据
    • 读取截断的数据

摘要

本文先简单介绍MessagePack的基本概念。

然后,介绍一个MessagePack C API - MPack的通常使用。

接着尝试对MPack截断数据的读取。

注:本文完整代码见仓库。


MessagePack简介

如果你使用过C/C++的json库,那么上手MessagePack是比较容易的。关于C/C++ Json库的使用可见:C++ JSON库的一般使用方法-CSDN博客。

这里,我先说下结论,对于用户层面而言,MessagePack相对于json节省空间,但牺牲了可读性(对于人类而言)。(更多区别见:Why Not Just Use JSON?)

下面我们来看两个示例,了解下MessagPack是如何压缩json内容。这个压缩过程,遵循MessagePack specification(MessagePack规范)

第一个示例,来自MessagePack首页。它将27个字节的JSON内容,压缩到18个字节。

在这里插入图片描述

我们根据MessagePack规范来看下上面是如何压缩的。

# 82的含义
## 对于元素个数不超过15个的map存储,遵循如下格式
## 82 == 1000 0010 表示该结构为map,有两个元素
+--------+~~~~~~~~~~~~~~~~~+
|1000XXXX|   N*2 objects   |
+--------+~~~~~~~~~~~~~~~~~+# A7的含义
## 对于长度不超过31的固定长度字符串存储,遵循如下格式
## A7 == 1010 0111 表示该结构为字符串,有7个字符
+--------+========+
|101XXXXX|  data  |
+--------+========+# C3含义
## false:
+--------+
|  0xc2  |
+--------+
## true:
+--------+
|  0xc3  |
+--------+# A6的含义--略,查询过程同上面A7# 00的含义
## 可以使用7-bit存储的正整数,遵循如下格式
## 00 = 0000 0000
+--------+
|0XXXXXXX|
+--------+

我们再来看一个示例,练习下。这个示例来自: mpack/docs/expect.md at develop · ludocode/mpack

["hello","world!",[1,2,3,4]
]

上面这个json,使用MessagPack编码如下。

93                     # an array containing three elementsa5 68 65 6c 6c 6f    # "hello"a6 77 6f 72 6c 64 21 # "world!"94                   # an array containing four elements01                 # 102                 # 203                 # 304                 # 4

MPACK的简单使用

了解了MessagePack的概念之后,我们看下它的C api的使用。MPACK是MessagePack C语言实现的API之一。mpack的stars没有msgpack-c多。但是mpack对libc的版本没有要求。下面是对mpack的使用教程。

JSON的使用包含两个部分:将json数据写入内存/文件;从已有的json数据中读取内容。

mapck在使用结构上,和json类似,分为写和读。MPack api是mpack的api文档,接口使用的详细介绍见文档。写数据的部分调用Write API。如果没有内存限制,读取的时候使用Node API。如果有内存限制,则使用Reader API+Expect API。(这里说的内存限制是指,程序运行的内存限制,而不是数据存储的内存限制)

这个参考示例不错,在开始之前,可以一读:一个C语言MessagePack库:mpack

下面我们开始写demo。写一个最简单,也是最常用的demo:存储数据的内存无限制时,使用mpack进行数据的写入和读取。示例参考自官方的README:ludocode/mpack: MPack - A C encoder/decoder for the MessagePack serialization format / msgpack.org[C]. 这里对这些API进行简单的介绍,详细介绍见官方文档。

  • mpack_writer_init_growable(mpack_writer_t* writer, char** target_data, size_t* target_size)使用一个会增长的buffer。
  • 在调用mpack_writer_destroy后,将上面target_data指向写入数据的地址。
  • 最后必须调用MPACK_FREE()释放申请的数据。
  • 使用mpack_build_map开始构建一个map,我们此时不知道map中元素的个数。如果知道元素个数,使用mpack_start_map()替代。
  • 使用mpack_build_map后,后面必须跟偶数个数的元素,在结束的时候调用 mpack_complete_map()
  • 使用Node API读取数据。
#include "mpack.h"
#include <stdio.h>/*
{"name": "3-1","number": 56,"students": [{"name": "zhangsan","score": 76.8},{"name": "lisi","score": 77}]
}
*/mpack_error_t class_information_serialize(char **data, size_t *size) {mpack_writer_t writer;mpack_writer_init_growable(&writer, data, size);mpack_build_map(&writer);mpack_write_cstr(&writer, "name");mpack_write_cstr(&writer, "3-1");mpack_write_cstr(&writer, "number");mpack_write_u8(&writer, 56);mpack_write_cstr(&writer, "students");mpack_build_array(&writer);mpack_start_map(&writer, 2);mpack_write_cstr(&writer, "name");mpack_write_cstr(&writer, "zhangsan");mpack_write_cstr(&writer, "score");mpack_write_float(&writer, 76.8);mpack_finish_map(&writer);mpack_start_map(&writer, 2);mpack_write_cstr(&writer, "name");mpack_write_cstr(&writer, "lisi");mpack_write_cstr(&writer, "score");mpack_write_float(&writer, 77);mpack_finish_map(&writer);mpack_complete_array(&writer);mpack_complete_map(&writer);mpack_error_t ret = mpack_writer_destroy(&writer);return ret;
}mpack_error_t class_information_deserialize(const char *data, size_t length) {mpack_tree_t tree;mpack_tree_init_data(&tree, data, length);mpack_tree_parse(&tree);mpack_node_t root = mpack_tree_root(&tree);const char *name = mpack_node_str(mpack_node_map_cstr(root, "name"));size_t name_len = mpack_node_strlen(mpack_node_map_cstr(root, "name"));uint8_t number = mpack_node_u8(mpack_node_map_cstr(root, "number"));printf("name:%.*s\n", name_len, name);printf("number:%u\n", number);printf("students:\n");mpack_node_t students = mpack_node_map_cstr(root, "students");size_t student_num = mpack_node_array_length(students);for (unsigned int i = 0; i < student_num; i++) {mpack_node_t student = mpack_node_array_at(students, i);const char *name = mpack_node_str(mpack_node_map_cstr(student, "name"));size_t name_len = mpack_node_strlen(mpack_node_map_cstr(student, "name"));float score = mpack_node_float(mpack_node_map_cstr(student, "score"));printf("  name:%.*s\n", name_len, name);printf("  score:%.2f\n", score);}mpack_error_t ret = mpack_tree_destroy(&tree);return ret;
}int main(int argc, char *argv[]) {char *data = NULL;size_t size = 0;class_information_serialize(&data, &size);class_information_deserialize(data, size);MPACK_FREE(data);
}

程序输出如下。

name:3-1
number:56
students:name:zhangsanscore:76.80name:lisiscore:77.00

在定长的buffer存储不定长的数据

工作的时候,会想使用比较奇怪的调用。首先,所有的数据都要存储在一个定长的buffer里面。因为这个buffer是从一个内存池中取出的,所以它的长度是定长的。但是,往里面写入数据的时候,会写多次,长度不一定。

修改上面的示例:现在要写入不定个数的student到数组中;允许截断;

截断的时候发生了什么?截断后还能否读取?

遇到截断,writer会设置错误的标记位。正常编码的时候,这个writer数据不应该在后续进行读取。因为它已经被标记为错误。

但是,总有些头铁的需求,想用发生截断时,已经写入内存的数据。这个从API文档里面是看不出来的,要看mpack的源码。

我先说结论:

  • 从前往后,不断将build中的内容,复制写入buffer。写之前检查空间是否足够,不够则停止写入,并标记错误。
  • 使用 node api 无法读取。因为数据被截断,不合法。

大体结构图如下。

在这里插入图片描述

示例代码如下。

#include "mpack.h"
#include <stdio.h>/*
{"name": "3-1","number": 56,"students": [{"name": "zhangsan","score": 76.8},{"name": "lisi","score": 77}]
}
*/mpack_error_t class_information_serialize(char *data, size_t *size) {mpack_writer_t writer;mpack_writer_init(&writer, data, *size);mpack_build_map(&writer);mpack_write_cstr(&writer, "name");mpack_write_cstr(&writer, "3-1");mpack_write_cstr(&writer, "number");mpack_write_u8(&writer, 56);mpack_write_cstr(&writer, "students");mpack_build_array(&writer);for (unsigned int i = 0; i < 56; i++) {mpack_start_map(&writer, 2);mpack_write_cstr(&writer, "name");mpack_write_cstr(&writer, "zhangsan");mpack_write_cstr(&writer, "score");mpack_write_float(&writer, 76.8);if (mpack_writer_error(&writer) != mpack_ok) {printf("error_%u: %d\n", i, mpack_writer_error(&writer));}mpack_finish_map(&writer);}mpack_complete_array(&writer);mpack_complete_map(&writer);if (mpack_writer_error(&writer) != mpack_ok) {printf("after write all students error: %d\n", mpack_writer_error(&writer));}*size = mpack_writer_buffer_used(&writer);mpack_error_t ret = mpack_writer_destroy(&writer);return ret;
}mpack_error_t class_information_deserialize(const char *data, size_t length) {mpack_tree_t tree;mpack_tree_init_data(&tree, data, length);mpack_tree_parse(&tree);if (mpack_tree_error(&tree) != mpack_ok) {printf("parse tree error: %d\n", mpack_tree_error(&tree));}mpack_node_t root = mpack_tree_root(&tree);const char *name = mpack_node_str(mpack_node_map_cstr(root, "name"));size_t name_len = mpack_node_strlen(mpack_node_map_cstr(root, "name"));uint8_t number = mpack_node_u8(mpack_node_map_cstr(root, "number"));printf("name:%.*s\n", name_len, name);printf("number:%u\n", number);printf("students:\n");mpack_node_t students = mpack_node_map_cstr(root, "students");size_t student_num = mpack_node_array_length(students);for (unsigned int i = 0; i < student_num; i++) {mpack_node_t student = mpack_node_array_at(students, i);const char *name = mpack_node_str(mpack_node_map_cstr(student, "name"));size_t name_len = mpack_node_strlen(mpack_node_map_cstr(student, "name"));float score = mpack_node_float(mpack_node_map_cstr(student, "score"));printf("  name:%.*s\n", name_len, name);printf("  score:%.2f\n", score);}mpack_error_t ret = mpack_tree_destroy(&tree);return ret;
}int main(int argc, char *argv[]) {
#define DATA_BUFFER_SIZE 35char data[DATA_BUFFER_SIZE] = {0};size_t size = DATA_BUFFER_SIZE;class_information_serialize(data, &size);class_information_deserialize(data, size);
}

输出如下。

after write all students error: 6
parse tree error: 3
name:
number:0
students:

读取截断的数据

既然使用node api解析数据会失败。那不要解析,顺序读取,一直读取到异常。

参考自:Using the Expect API。代码如下。

#include "mpack.h"
#include <stdio.h>/*
[{"name": "zhangsan","score": 76.8},{"name": "lisi","score": 77}...
]*/mpack_error_t class_information_serialize(char *data, size_t *size) {mpack_writer_t writer;mpack_writer_init(&writer, data, *size);mpack_build_array(&writer);for (unsigned int i = 0; i < 56; i++) {mpack_build_map(&writer);mpack_write_cstr(&writer, "name");mpack_write_cstr(&writer, "zhangsan");mpack_write_cstr(&writer, "score");mpack_write_float(&writer, 76.8);mpack_complete_map(&writer);}mpack_complete_array(&writer);if (mpack_writer_error(&writer) != mpack_ok) {printf("after write all students error: %d\n", mpack_writer_error(&writer));}*size = mpack_writer_buffer_used(&writer);mpack_error_t ret = mpack_writer_destroy(&writer);return ret;
}mpack_error_t class_information_deserialize(const char *data, size_t length) {mpack_reader_t reader;mpack_reader_init_data(&reader, data, length);uint32_t students_num = mpack_expect_array(&reader);printf("students num: %u\n", students_num);for (unsigned int i = 0; i < students_num; i++) {uint32_t elem_cnt = mpack_expect_map(&reader);mpack_expect_cstr_match(&reader, "name");char name[20];size_t name_len = mpack_expect_str_buf(&reader, name, 20);mpack_expect_cstr_match(&reader, "score");float score = mpack_expect_float(&reader);if (mpack_reader_error(&reader) != mpack_ok) {break;}printf("name:%.*s\n", name_len, name);printf("score:%.2f\n", score);mpack_done_map(&reader);}return mpack_ok;
}int main(int argc, char *argv[]) {
#define DATA_BUFFER_SIZE 100char data[DATA_BUFFER_SIZE] = {0};size_t size = DATA_BUFFER_SIZE;class_information_serialize(data, &size);class_information_deserialize(data, size);
}

输出如下。

after write all students error: 6
students num: 56
name:zhangsan
score:76.80
name:zhangsan
score:76.80
name:zhangsan
score:76.80

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/258465.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Android 13.0 SystemUI下拉状态栏定制二 锁屏页面横竖屏解锁图标置顶显示功能实现

1.前言 在13.0的系统rom定制化开发中,在关于systemui的锁屏页面功能定制中,由于在平板横屏锁屏功能中,时钟显示的很大,并且是在左旁边居中显示的, 由于需要和竖屏显示一样,所以就需要用到小时钟显示,然后同样需要居中,所以就来分析下相关的源码,来实现具体的功能 如图…

【MySQL】:DQL查询

&#x1f3a5; 屿小夏 &#xff1a; 个人主页 &#x1f525;个人专栏 &#xff1a; MySQL从入门到进阶 &#x1f304; 莫道桑榆晚&#xff0c;为霞尚满天&#xff01; 文章目录 &#x1f4d1;前言一. DQL1.1 基本语法1.2 基础查询1.3 条件查询1.3 聚合函数 &#x1f324;️ 全篇…

如何解决缓存和数据库的数据不一致问题

数据不一致问题是操作数据库和操作缓存值的过程中&#xff0c;其中一个操作失败的情况。实际上&#xff0c;即使这两个操作第一次执行时都没有失败&#xff0c;当有大量并发请求时&#xff0c;应用还是有可能读到不一致的数据。 如何更新缓存 更新缓存的步骤就两步&#xff0…

c语言--一维数组传参的本质(详解)

目录 一、前言二、代码三、形式3.1形式13.2形式2 四、总结 一、前言 首先从⼀个问题开始&#xff0c;我们之前都是在函数外部计算数组的元素个数&#xff0c;那我们可以把函数传给⼀个函数后&#xff0c;函数内部求数组的元素个数吗&#xff1f; 二、代码 直接上代码&#x…

初识Qt | 从安装到编写Hello World程序

文章目录 1.前端开发简单分类2.Qt的简单介绍3.Qt的安装和环境配置4.创建简单的Qt项目 1.前端开发简单分类 前端开发&#xff0c;这里是一个广义的概念&#xff0c;不单指网页开发&#xff0c;它的常见分类 网页开发&#xff1a;前端开发的主要领域&#xff0c;使用HTML、CSS …

『运维备忘录』之 Sed 命令详解

运维人员不仅要熟悉操作系统、服务器、网络等只是&#xff0c;甚至对于开发相关的也要有所了解。很多运维工作者可能一时半会记不住那么多命令、代码、方法、原理或者用法等等。这里我将结合自身工作&#xff0c;持续给大家更新运维工作所需要接触到的知识点&#xff0c;希望大…

C++中的volatile:穿越编译器的屏障

C中的volatile&#xff1a;穿越编译器的屏障 在C编程中&#xff0c;我们经常会遇到需要与硬件交互或多线程环境下访问共享数据的情况。为了确保程序的正确性和可预测性&#xff0c;C提供了关键字volatile来修饰变量。本文将深入解析C中的volatile关键字&#xff0c;介绍其作用、…

【c++】list 模拟

> 作者简介&#xff1a;დ旧言~&#xff0c;目前大二&#xff0c;现在学习Java&#xff0c;c&#xff0c;c&#xff0c;Python等 > 座右铭&#xff1a;松树千年终是朽&#xff0c;槿花一日自为荣。 > 目标&#xff1a;能手撕list模拟 > 毒鸡汤&#xff1a;不为模糊…

蓝桥杯:C++模运算、快速幂

模运算 模运算是大数运算中的常用操作。如果一个数太大&#xff0c;无法直接输出&#xff0c;或者不需要直接输出&#xff0c;则可以对它取模&#xff0c;缩小数值再输出。取模可以防止溢出&#xff0c;这是常见的操作。 模是英文mod的音译&#xff0c;取模实际上是求余。 取…

交通管理|交通管理在线服务系统|基于Springboot的交通管理系统设计与实现(源码+数据库+文档)

交通管理在线服务系统目录 目录 基于Springboot的交通管理系统设计与实现 一、前言 二、系统功能设计 三、系统实现 1、用户信息管理 2、驾驶证业务管理 3、机动车业务管理 4、机动车业务类型管理 四、数据库设计 1、实体ER图 五、核心代码 六、论文参考 七、最新计…

【超级干货】ArcGIS_空间连接_工具详解

帮助里对空间连接的解释&#xff1a; 根据空间关系将一个要素的属性连接到另一个要素。 目标要素和来自连接要素的被连接属性写入到输出要素类。 如上图所示&#xff0c;关键在于空间关系&#xff0c;只有当两个要素存在空间关系的时候&#xff0c;空间连接才有用武之地。 一…

方式0控制流水灯循环点亮

#include<reg51.h> //包含51单片机寄存器定义的头文件 #include<intrins.h> //包含函数_nop_&#xff08;&#xff09;定义的头文件 unsigned char code Tab[]{0xFE,0xFD,0xFB,0xF7,0xEF,0xDF,0xBF,0x7F};//流水灯控制码&#xff0c;该数组被定义为全局变量 sbit…

《UE5_C++多人TPS完整教程》学习笔记15 ——《P16 会话接口委托(Session Interface Delegates)》

本文为B站系列教学视频 《UE5_C多人TPS完整教程》 —— 《P16 会话接口委托&#xff08;Session Interface Delegates&#xff09;》 的学习笔记&#xff0c;该系列教学视频为 Udemy 课程 《Unreal Engine 5 C Multiplayer Shooter》 的中文字幕翻译版&#xff0c;UP主&#xf…

2.12日学习打卡----初学RocketMQ(三)

2.12日学习打卡 目录&#xff1a; 2.12日学习打卡一. RocketMQ高级特性&#xff08;续&#xff09;消息重试延迟消息消息查询 二.RocketMQ应用实战生产端发送同步消息发送异步消息单向发送消息顺序发送消息消费顺序消息全局顺序消息延迟消息事务消息消息查询 一. RocketMQ高级特…

红蓝对抗:网络安全领域的模拟实战演练

引言&#xff1a; 随着信息技术的快速发展&#xff0c;网络安全问题日益突出。为了应对这一挑战&#xff0c;企业和组织需要不断提升自身的安全防护能力。红蓝对抗作为一种模拟实战演练方法&#xff0c;在网络安全领域得到了广泛应用。本文将介绍红蓝对抗的概念、目的、过程和…

【精品】关于枚举的高级用法

枚举父接口 public interface BaseEnum {Integer getCode();String getLabel();/*** 根据值获取枚举** param code* param clazz* return*/static <E extends Enum<E> & BaseEnum> E getEnumByCode(Integer code, Class<E> clazz) {Objects.requireNonN…

ASCII编码的诞生:解决字符标准化与跨平台通信的需求

title: ASCII编码的诞生&#xff1a;解决字符标准化与跨平台通信的需求 date: 2024/2/17 14:27:01 updated: 2024/2/17 14:27:01 tags: ASCII编码标准化跨平台字符集兼容性简洁性影响力 在计算机的发展过程中&#xff0c;字符的表示和传输一直是一个重要的问题。为了实现字符的…

python-自动化篇-终极工具-用GUI自动控制键盘和鼠标-pyautogui

文章目录 用GUI自动控制键盘和鼠标pyautogui 模块鼠标——记忆宫殿屏幕位置——移动地图——pyautogui.size鼠标位置——自身定位——pyautogui.position()移动鼠标——pyautogui.moveTo拖动鼠标——滚动鼠标——scroll 键盘按下键盘释放键盘 开始与结束通过注销关闭所有程序 用…

linux系统zabbix监控分布式监控的部署

分布式监控 服务器安装分布式监控安装工具安装mysql导入数据结构配置proxy端浏览器配置 zabbix server端监控到大量zabbix agent端&#xff0c;这样会使zabbix server端压力过大&#xff0c;使用zabbix proxy进行分布式监控 服务器安装分布式监控 安装工具 rpm -Uvh https://…

HTML | DOM | 网页前端 | 常见HTML标签总结

文章目录 1.前端开发简单分类2.前端开发环境配置3.HTML的简单介绍4.常用的HTML标签介绍 1.前端开发简单分类 前端开发&#xff0c;这里是一个广义的概念&#xff0c;不单指网页开发&#xff0c;它的常见分类 网页开发&#xff1a;前端开发的主要领域&#xff0c;使用HTML、CS…