mpack简明教程

news2025/1/10 1:53:19

文章目录

    • 摘要
    • 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 elements
  a5 68 65 6c 6c 6f    # "hello"
  a6 77 6f 72 6c 64 21 # "world!"
  94                   # an array containing four elements
    01                 # 1
    02                 # 2
    03                 # 3
    04                 # 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:zhangsan
  score:76.80
  name:lisi
  score: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 35
  char 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 100
  char 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.coloradmin.cn/o/1451621.html

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

相关文章

Android 回退页面不是上个页面

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

【lesson57】信号量和生产者消费者模型(环形队列版)

文章目录 信号量概念信号量接口初始化销毁等待发布 基于环形队列的生产者消费者模型编码Common.hLockGuard.hppTask.hppsem.hppRingQueue.hppConProd.cc 信号量概念 POSIX信号量和SystemV信号量作用相同&#xff0c;都是用于同步操作&#xff0c;达到无冲突的访问共享资源目的…

Python 使用 raise 语句抛出异常

在 Python 编程中&#xff0c;异常处理是至关重要的一部分。异常能够帮助程序在面对错误和意外情况时进行适当的处理&#xff0c;从而使程序具有更好的稳定性和可靠性。而 raise 语句则是 Python 中用来手动触发异常的关键工具之一。本文将探讨 Python 中 raise 语句的使用方法…

算法--数论二

这里写目录标题 高斯消元高斯消元求线性方程组用途高斯消元的数学思想例题代码 二级目录 一级目录二级目录二级目录二级目录 一级目录二级目录二级目录二级目录 一级目录二级目录二级目录二级目录 一级目录二级目录二级目录二级目录 高斯消元 高斯消元求线性方程组 用途 这个…

【机器学习案例5】语言建模 - 最常见的预训练任务一览表

自监督学习 (SSL) 是基于 Transformer 的预训练语言模型的支柱,该范例涉及解决有助于建模自然语言的预训练任务 (PT)。本文将所有流行的预训练任务放在一起,以便我们一目了然地评估它们。 SSL 中的损失函数 这里的损失函数只是模型训练的各个预训练任务损失的加权和。 以BE…

CSP-201909-1-小明种苹果

CSP-201909-1-小明种苹果 #include <iostream> using namespace std; int main() {long long sumApple 0, maxNum 0, maxAppleNum 0, n, m;cin >> n >> m;for (long long i 0; i < n; i){long long appleNum, delta 0;cin >> appleNum;for (l…

Kibana:如何嵌入 Kibana 仪表板

作者&#xff1a;Carly Richmond 像我这样的前端工程师经常提出的要求是将 Kibana 等来源的现有仪表板嵌入到 JavaScript Web 应用程序中。 这是我必须多次执行的任务&#xff0c;因为我们希望快速部署用户生成的视图或允许用户控制给定的视图。 从我们从精彩的开发者社区收到的…

模拟电子技术——分压式偏置放大电路、多级放大电路、差动放大电路、互补输出级

文章目录 前言基本放大电路链接&#xff0c;上一篇 [基本放大电路](https://blog.csdn.net/weixin_47541751/article/details/136112075?spm1001.2014.3001.5502) 一、分压式偏置放大电路什么是分压式偏置电路分压式电路组成电路分析估算静态工作点 二、多级放大电路什么是多级…

【方法】如何打开带密码的RAR分卷压缩文件?

RAR分卷文件是一种特殊的RAR压缩文件格式&#xff0c;也就是将文件压缩成多个相同大小的压缩包&#xff0c;可以更方便传输。那如果收到了带有密码的RAR分卷压缩文件&#xff0c;要如何打开呢&#xff1f; 无论RAR分卷压缩文件是否设置了密码保护&#xff0c;在打开或者解压分…

【题解】数的范围(二分模板)

笔记 if (check(mid)) L mid &#xff0c;则 mid L R 1 >> 1 if (check(mid)) R mid &#xff0c;则 mid L R >> 1 题目 #include<bits/stdc.h> using namespace std;int n, q; int a[100010]; int b[10010]; typedef pair<int, int> PII;v…

html的列表标签

列表标签 列表在html里面经常会用到的&#xff0c;主要使用来布局的&#xff0c;使其整齐好看. 无序列表 无序列表[重要]&#xff1a; ul &#xff0c;li 示例代码1&#xff1a; 对应的效果&#xff1a; 无序列表的属性 属性值描述typedisc&#xff0c;square&#xff0c;…

java-8组合式异步编程

11.1 Future 接口 Future接口在Java5中被引人&#xff0c;设计初衷是对将来某个时刻会发生的结果进行建模。它建模了一种异步计算&#xff0c;返回一个执行运算结果的引用&#xff0c;当运算结束后&#xff0c;这个引用被返回给调用方。在Future中触发那些潜在耗时的操作把调用…

LEETCODE 164. 破解闯关密码

class Solution { public:string crackPassword(vector<int>& password) {vector<string> password_str;for(int i0;i<password.size();i){password_str.push_back(to_string(password[i]));}//希尔排序int gappassword.size()/2;while(gap>0){for(int i…

Netty Review - 底层零拷贝源码解析

文章目录 Pre概述源码解析入口索引AbstractNioByteChannel.NioByteUnsafe#readallocHandle.allocate(allocator) 小结传统的零拷贝 Pre Netty Review - 直接内存的应用及源码分析 概述 Netty 的零拷贝技术是通过优化数据传输过程中的数据复制操作&#xff0c;以降低系统的开销…

Kotlin基本语法 3 类

1.定义类 package classStudyclass Player {var name:String "jack"get() field.capitalize()set(value) {field value.trim()} }fun main() {val player Player()println(player.name)player.name " asdas "println(player.name)} 2.计算属性与防范…

Matlab|基于支持向量机的电力短期负荷预测【三种方法】

目录 主要内容 部分代码 结果一览 下载链接 主要内容 该程序主要是对电力短期负荷进行预测&#xff0c;采用三种方法&#xff0c;分别是最小二乘支持向量机&#xff08;LSSVM&#xff09;、标准粒子群算法支持向量机和改进粒子群算法支持向量机三种方法对负荷进行…

Codeforces Round 925 (Div. 3) D. Divisible Pairs (Java)

Codes Round 925 (Div. 3) D. Divisible Pairs (Java) 比赛链接&#xff1a;Codeforces Round 925 (Div. 3) D题传送门&#xff1a;D.Divisible Pairs 题目&#xff1a;D.Divisible Pairs 题目描述 输出格式 For each test case, output a single integer — the number o…

【Windows】MacOS制作纯净版Windows10安装U盘

方法一、在window系统中更新win10&#xff08;不更新引导程序&#xff09; cp -rp /Volumes/Windows10专业版\ 64位/* /Volumes/WIN10/https://baijiahao.baidu.com/s?id1760695844372493842&wfrspider&forpc 方法二、在window系统中更新win10&#xff08;更新引导程…

C语言学习day15:数组强化训练

题目一&#xff1a; 称体重&#xff1a;分别给10个值&#xff0c;来获得最大值 思路&#xff1a; 定义数组&#xff0c;给数组内赋10个值第一个下标的值与第二个下标的值进行比较定义max&#xff0c;将比较得来的较大的值赋值给max一直比较直到比较到最后一个下标&#xff0…

JavaScript中null和undefined的区别

JavaScript中null和undefined是两个特殊的值&#xff0c;经常在编程中遇到。虽然它们经常被混淆&#xff0c;但它们有着不同的含义和用法。本文将详细介绍JavaScript中null和undefined的区别&#xff0c;帮助开发者更好地理解和使用它们。 首先&#xff0c;让我们来了解一下nu…