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.mzph.cn/news/686594.shtml

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

相关文章

【制作100个unity游戏之25】3D背包、库存、制作、快捷栏、存储系统、砍伐树木获取资源、随机战利品宝箱9(附带项目源码)

效果演示 文章目录 效果演示系列目录前言箱子库存源码完结 系列目录 前言 欢迎来到【制作100个Unity游戏】系列&#xff01;本系列将引导您一步步学习如何使用Unity开发各种类型的游戏。在这第25篇中&#xff0c;我们将探索如何用unity制作一个3D背包、库存、制作、快捷栏、存…

信号系统之卷积性质

1 常见的脉冲响应 最简单的脉冲响应是一个δ函数&#xff0c;如图7-1所示。也就是说&#xff0c;输入上的脉冲在输出上产生相同的脉冲。这意味着所有信号都毫无变化地通过系统。将任何信号与 δ函数进行卷积都会产生完全相同的信号。从数学上来说&#xff0c;可以这样写&#…

元器件焊盘的PCB处理方式分析与总结

对于高速信号走线的特性阻抗&#xff0c;都需要按照实际要求进行精度控制&#xff0c;所以&#xff0c;任何因设计因素带来的阻抗波动都应该进行优化&#xff0c;如下图所示&#xff0c;为一个12层板设计中的50Ω微带走线&#xff0c;需要在走线之上放置电感&#xff1b; 但是&…

枚举(C/C++)

没有什么成套的算法&#xff0c;直接上例题&#xff01;&#xff01; 例题1&#xff1a;赢球票 代码&#xff1a; #include <bits/stdc.h> using namespace std;const int maxn 105; int n,num1[maxn],num2[maxn],cnt,cnt1,sum,ans;int check1()//检查剩余个数 {cnt1…

Guava RateLimiter单机实战指南

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 Guava RateLimiter单机实战指南 前言maven坐标引入业务实现重要参数和方法关于warmupPeriod实战 前言 想象一下你是一位大厨&#xff0c;正在烹饪美味佳肴。突然之间&#xff0c;前来就餐的人潮如潮水…

得物面试:Redis用哈希槽,而不是一致性哈希,为什么?

尼恩说在前面 在40岁老架构师 尼恩的读者交流群(50)中&#xff0c;最近有小伙伴拿到了一线互联网企业如得物、阿里、滴滴、极兔、有赞、希音、百度、网易、美团的面试资格&#xff0c;遇到很多很重要的面试题&#xff1a; Redis为何用哈希槽而不用一致性哈希&#xff1f; 最近…

WebServer 之 http连接处理(下)

目录 ✊请求报文--解析 流程图 && 状态机 状态机 -- 状态转移图 主状态机 从状态机 http 报文解析 HTTP_CODE 含义 从状态机 逻辑 主状态机 逻辑 &#x1f41e;请求报文--响应 基础API stat mmap iovec writev 流程图 HTTP_CODE 含义(2) 代码分析 …

图表示学习 Graph Representation Learning chapter2 背景知识和传统方法

图表示学习 Graph Representation Learning chapter2 背景知识和传统方法 2.1 图统计和核方法2.1.1 节点层次的统计和特征节点的度 节点中心度聚类系数Closed Triangles, Ego Graphs, and Motifs 图层次的特征和图的核节点袋Weisfieler–Lehman核Graphlets和基于路径的方法 邻域…

qt-C++笔记之捕获鼠标滚轮事件并输出滚轮角度增量

qt-C笔记之捕获鼠标滚轮事件并输出滚轮角度增量 code review! 文章目录 qt-C笔记之捕获鼠标滚轮事件并输出滚轮角度增量1.运行2.main.cpp3.main.pro 1.运行 2.main.cpp #include <QApplication> #include <QWidget> #include <QWheelEvent> #include <…

Android 回退页面不是上个页面

问题 Android 回退页面不是上个页面 详细问题 笔者进行Android 开发&#xff0c;点击返回上一层&#xff0c;显示页面不是上个页面&#xff0c;而是之前的某个页面 页面跳转代码 private void navigateToActivity(Context context, Class<?> targetActivityClass) {I…

C#,二分法(Bisection Method)求解方程的算法与源代码

1 二分法 二分法是一种分治算法&#xff0c;是一种数学思维。 对于区间[a&#xff0c;b]上连续不断且f&#xff08;a&#xff09;f&#xff08;b&#xff09;<0的函数yf&#xff08;x&#xff09;&#xff0c;通过不断地把函数f&#xff08;x&#xff09;的零点所在的区间…

springboot登录校验

一、登录功能 二、登录校验 2.1 会话技术 2.2 JWT令牌 JWT令牌解析&#xff1a; 如何校验JWT令牌&#xff1f;Filter和Interceptor两种方式。 2.3 过滤器Filter 2.3.1 快速入门 修改上述代码&#xff1a; 2.3.2 详解 2.3.3 登录校验-Filter 2.4 Interceptor拦截器 2.4.1 …

量子算法入门——3.狄拉克符号与量子态(1)

参考资料&#xff1a; 【【零基础入门量子计算-第04讲】狄拉克符号与量子态】 来自b站up&#xff1a;溴锑锑跃迁 建议关注他的更多高质量文章&#xff1a;CSDN&#xff1a;【溴锑锑跃迁】 1. 狄拉克符号 从生活实例引导到狄拉克符号狄拉克符号 注意这里ket是| >(右矢)&a…

阿里云服务器租用价格 2024年新版活动报价及租用收费标准

2024年最新阿里云服务器租用费用优惠价格表&#xff0c;轻量2核2G3M带宽轻量服务器一年61元&#xff0c;折合5元1个月&#xff0c;新老用户同享99元一年服务器&#xff0c;2核4G5M服务器ECS优惠价199元一年&#xff0c;2核4G4M轻量服务器165元一年&#xff0c;2核4G服务器30元3…

【力扣】169.多数元素

这道题的解法是运用哈希表打擂台的思想 首先题目的意思是存在数字&#xff0c;意思就是最后返回的结果不可能为空就是了&#xff0c;所以便不用考虑{1&#xff0c;2&#xff0c;3&#xff0c;4&#xff0c;5}这种例子。那么就可以用哈希表存所出现数字出现的次数&#xff0c;然…

【前端工程化面试题】webpack proxy的工作原理,为什么能解决跨域问题

在 webpack 的配置文件 webpack.config.js 中有一个配置项 devServer 里面有一个属性是 proxy&#xff0c;这里面可以配置代理服务器&#xff0c;解决跨域问题&#xff0c;请参考官网。 一般来说 webpack 的代理就是说的开发服务器 webpack-dev-server。 其实不光是 webpack 其…

恢复被.target勒索病毒加密的数据文件:拒绝向.target勒索病毒支付赎金

引言&#xff1a; 在当今数字时代&#xff0c;勒索病毒已成为网络安全领域的一大威胁&#xff0c;而.target勒索病毒是其中引起广泛关注的一种变种。本文将深入探讨.target勒索病毒的特点以及被其加密的数据文件恢复方法。数据的重要性不容小觑&#xff0c;您可添加我们的技术…

安卓版本与鸿蒙不再兼容,鸿蒙开发工程师招疯抢

最近&#xff0c;互联网大厂纷纷开始急招华为鸿蒙开发工程师。这是一个新的信号。在Android和iOS长期霸占市场的今天&#xff0c;鸿蒙的崛起无疑为整个行业带来了巨大的震动。 2023年11月10日&#xff0c;网易更新了高级/资深Android开发工程师岗位&#xff0c;职位要求参与云音…

VS Code主题设置(美化VS Code)(主题+背景+图标+特效+字体)

目录 切换整体主题&#xff08;整体主题&#xff09; 切换文件图标主题 设置VS Code背景图案 字体特效 连击特效 字体设置 主题的具体效果放在了文章末尾&#xff0c;这篇文章后续也会进行更新 ————————————————————————————…

类和对象 第六部分第五小节:继承同名成员处理方式

问题&#xff1a;当子类与父类出现同名的成员&#xff0c;如何通过子类对象&#xff0c;访问到子类或父类中同名的数据&#xff1f; &#xff08;一&#xff09;同名成员属性处理方式 #include<iostream>using namespace std;class Base{public:Base(){m_A 100;}int m_A…