dukelec 发表于 2021-10-18 18:59:17

分享刚造的轮子:MessagePack 的 C 库(little-endian 版本)

本帖最后由 dukelec 于 2021-10-19 03:56 编辑

很早之前就一直在用 MessagePack(简称 msgpack),不过是在电脑和服务器,我一直觉得 msgpack 很适合 MCU,但之前一直没有机会在 MCU 里面用它

msgpack 和 json 差不多,不过 msgpack 可以包含二进制数据,MCU 处理起来比 json 方便超多,所以用来代替 json


最近需要在 MCU 里面使用 msgpack,找了一圈,没有相中的库,我的需求是足够简约且不使用 malloc

所以开始自己写,msgpack 对于 MCU 有一个让我很不爽的地方是,它是大端的,需要转换来转换去

思考了很久,打算创建一个小端版本的 MessagePack 分支:MessagePackEL,除了大小端,其它完全一致

我建的分支已经包含了 C、Python 和 Javascript 的实现: https://github.com/dukelec/msgpackel

其中 C 实现是我新写的,使用方法我贴过来:
(如果你想用于 大端 的 msgpack,库改起来也很简单)

譬如一个数据结构:

{
    "dev": "abc123",
    "data": {
      "voltage": 3.6,
      "angle":
    }
}


转换成字节流的示范:

uint8_t msg_buf;

size_t prepare_data(void)
{
    uint8_t *pos = msg_buf;
    pos = mpk_w_map_hdr(pos, 2);      // create top map

    pos = mpk_w_str(pos, "dev");      // create first item
    pos =   mpk_w_str(pos, "abc123");

    pos = mpk_w_str(pos, "data");       // create second item
    pos =   mpk_w_map_hdr(pos, 2);      // create sub map
    pos =   mpk_w_str(pos, "voltage");
    pos =   mpk_w_float(pos, 3.6f);
    pos =   mpk_w_str(pos, "angle");
    pos =   mpk_w_array_hdr(pos, 2);
    pos =       mpk_w_int(pos, 45);
    pos =       mpk_w_int(pos, 90);

    return pos - msg_buf;               // return length
}


解析字节流的示范:

void check_data(void)
{
    mpk_t st;
    uint8_t *payload, *end;
    size_t num;
    float voltage;


    st = mpk_map_search(msg_buf, NULL, NULL, (uint8_t *)&voltage, NULL,
                        "data", "voltage", NULL);       // must end with NULL
    if (st == MPK_FLOAT)
      printf("found data.voltage: %f\n", voltage);
    else
      printf("parse data.voltage error: %d\n", st);


    st = mpk_map_search(msg_buf, &payload, &end, NULL, NULL,
                        "dev", NULL);
    if (st == MPK_STR) {
      int str_len = end - payload;
      printf("found dev: %.*s\n", str_len, payload);// print non-null terminated string
    } else {
      printf("parse dev error: %d\n", st);
    }


    st = mpk_map_search(msg_buf, &payload, NULL, NULL, &num,
                        "data", "angle", NULL);
    if (st == MPK_ARRAY) {
      uint8_t *pos = payload;
      printf("data.angle[] length: %d\n", num);
      for (int i = 0; i < num; i++) {
            int32_t val;                              // val must be at least 4 bytes
            st = mpk_parse(pos, NULL, &end, (uint8_t *)&val, NULL);
            if (st != MPK_INT && st != MPK_UINT) {
                printf("parse data.angle error\n");
                break;
            }
            printf("data.angle[%d]: %ld\n", i, val);
            pos = end;
      }
    } else {
      printf("parse data.angle error: %d\n", st);
    }
}


服务器也可以同时支持 json 和 msgpack,收到 json 就回覆 json,收到 msgpack 就回 msgpack,中间处理完全相同,譬如:

#!/usr/bin/env python3

import cgi, sys, os, json
import umsgpackel

mime = os.environ.get('CONTENT_TYPE', '')
is_mpk = True if mime.find('msgpackel') != -1 else False
if is_mpk:
    print('Content-Type: application/msgpackel; charset=binary\n')
else:
    print('Content-Type: application/json; charset=utf-8\n')
sys.stdout.flush()

if is_mpk:
    data = sys.stdin.buffer.read(int(os.environ.get('CONTENT_LENGTH', 0)))
else:
    data = sys.stdin.read(int(os.environ.get('CONTENT_LENGTH', 0))) # default=0

if data: # for POST
    class Form(dict):
      pass
    if is_mpk:
      unpk = umsgpackel.unpackb(data)
    else:
      unpk = json.loads(data)
    form = Form(unpk)
    form.getvalue = form.get
else: # for GET
    form = cgi.FieldStorage()

#dev = form.getvalue('dev')
# do some work ...

print(f"receive: {unpk} |", file=sys.stderr)

if is_mpk:
    sys.stdout.buffer.write(umsgpackel.packb({"status": "ok"}))
else:
    print(json.dumps({"status": "ok"}))

这是 http(s) 的示范,msgpack 也可以用于 mqtt 传输,同样是取代 json

河河河 发表于 2021-10-18 19:05:07

厉害!!!

chendaon 发表于 2021-10-18 19:09:10

大佬好像啥多会{:lol:}

BongBong 发表于 2021-10-19 01:57:38

2021的今天为啥要用message pack. 不学, 不研究, 不在意

dukelec 发表于 2021-10-19 03:41:40

BongBong 发表于 2021-10-19 01:57
2021的今天为啥要用message pack. 不学, 不研究, 不在意

有更好的推薦?

sinc_mark 发表于 2021-10-19 08:24:11

message pack remark!

albert_w 发表于 2021-10-19 08:32:19

BongBong 发表于 2021-10-19 01:57
2021的今天为啥要用message pack. 不学, 不研究, 不在意

可有合适的推荐?
JSON解析起来麻烦, modbus做板级数据交互能力不太够.

lindabell 发表于 2021-10-19 08:33:10

我都直接cjson

albert_w 发表于 2021-10-19 09:11:49

lindabell 发表于 2021-10-19 08:33
我都直接cjson

回头试试看, 能json方便不少

dukelec 发表于 2021-10-19 09:19:53

本帖最后由 dukelec 于 2021-10-19 09:40 编辑

lindabell 发表于 2021-10-19 08:33
我都直接cjson

mcu 上用,我不喜欢依赖 malloc 的库,因为我不用动态内存分配,做为代替,我用链表来实现,避免内存碎片化


我手上一个 iot 项目,一开始平台方用的就是 json,我让他们改用 msgpack,对方没同意

我当时让对方换 msgpack 的理由是,虽然目前是一些简单的传感器数据,但日后会增加摄像头,需要传输图片,用 msgpack 可以传输二进制方便一点,协议改动也小,认证什么的都不用改

对方说要兼容其他家的硬件,不想改,而且对方觉得摄像头短期不会上马,所以懒得改

我说可以同时兼容 json 和 msgpack,不影响其他家硬件,对方还是没有动力改

我只能被迫先写了一个简单的不严谨的 json 解析器,凑合一下

这段时间已经上马摄像头,需要传输少量图片,还是要对方支持上 msgpack


我的其它开源项目,譬如 CDBUS GUI、CD-PnP 的 python 后端 和 网页前端 之间的交互,以及配置的导入导出,还有 CDEncrypt 加密工具输出文件打包的格式等等,都统一用的是 msgpack

和 json 相比,msgpack 更高效、用途更多,对于高级语言序列化更方便(譬如很多数据类型用 json 序列化则会报错),对于 mcu 上使用也更简单
(只有需要人工直接编辑的配置文件,我才会选择 json,譬如 CDBUS GUI 的按钮配置文件用的是 json5,或者直接用 python 文件当作配置文件)

至于数据调试,二进制直接阅读稍微麻烦一点,但是通常都可以用软件打印解析后的数据,还有很多工具可以用来解析,所以还好
(在线的转换工具譬如有: https://kawanet.github.io/msgpack-lite/ )

BongBong 发表于 2021-10-19 13:30:34

albert_w 发表于 2021-10-19 08:32
可有合适的推荐?
JSON解析起来麻烦, modbus做板级数据交互能力不太够.

自定义一个协议,同蓝牙物理底层一样。
例如:包头+SN+LEN+PAYLOAD+CHECKSUM
效率又高,并且PAYLOAD全部走结构体,要通用就cjson
2021年了,不走回头路。

albert_w 发表于 2021-10-19 13:43:10

BongBong 发表于 2021-10-19 13:30
自定义一个协议,同蓝牙物理底层一样。
例如:包头+SN+LEN+PAYLOAD+CHECKSUM
效率又高,并且PAYLOAD全部 ...

我们现在就类似这么干的, 但有很多麻烦, 和非C语言交换数据太麻烦了.

BongBong 发表于 2021-10-19 14:50:06

albert_w 发表于 2021-10-19 13:43
我们现在就类似这么干的, 但有很多麻烦, 和非C语言交换数据太麻烦了.

那就json好了。

dukelec 发表于 2021-10-19 22:19:17

本帖最后由 dukelec 于 2021-10-19 22:21 编辑

BongBong 发表于 2021-10-19 13:30
自定义一个协议,同蓝牙物理底层一样。
例如:包头+SN+LEN+PAYLOAD+CHECKSUM
效率又高,并且PAYLOAD全部 ...

我搞的 CDBUS GUI 用的協議就是你說的這種,只適合高實時性、高性能、協議開銷必須很低的場合,譬如伺服控制

但是這種 schemafull 數據結構的修改和版本維護很麻煩,要通訊雙方的一致性非常高才行,並不是很靈活

所以和服務器通訊,爲了靈活,方便擴充字段什麼的,應該選擇 schemaless 的方案,隨手找了一篇文,你可以看看:

THE SEEDY WORLD OF MESSAGE SERIALIZATION - by: Kerry Scharfglass
https://hackaday.com/2020/06/10/the-ceedy-world-of-message-serialization

BongBong 发表于 2021-10-20 00:20:33

dukelec 发表于 2021-10-19 22:19
我搞的 CDBUS GUI 用的協議就是你說的這種,只適合高實時性、高性能、協議開銷必須很低的場合,譬如伺服 ...

理解
我只是跳开技术来说
现在MCU内存大, 速度快, 开销无所谓
要么就精简, 要么就json一般都能覆盖了.
特殊的再说
毕竟时间也很贵
发明轮子的基础工程师是少数
应用工程师要赚钱就要搞主流的

dukelec 发表于 2021-10-20 00:37:37

本帖最后由 dukelec 于 2021-10-20 00:49 编辑

BongBong 发表于 2021-10-20 00:20
理解
我只是跳开技术来说
现在MCU内存大, 速度快, 开销无所谓


json 不方便傳輸二進制數據,擴展性不好,譬如傳輸波形數據、圖片、固件等,在 json 內使用 base64 的話效率太低

正是因爲時間寶貴,所以我才選擇更簡單方便擴展性好的 msgpack

msgpack 存在很久了,並不是小衆,很多大公司在用


對於 MCU,文本格式的協就沒有不操蛋的,除了 json 的解析有點麻煩之外,重點要點名一下 AT 命令,真是操蛋中的操蛋
(手上項目 AT 命令的解析也是我自己寫的,精簡的版本,同樣沒有使用 malloc,用鏈表代之,後續整理一下,可能也會分享出來)

lindabell 发表于 2021-10-20 14:15:16

目测没有校验的,存在干扰的场合就可能有问题
比如串口通信,还有电机的情况。

dukelec 发表于 2021-10-20 14:24:04

本帖最后由 dukelec 于 2021-10-20 14:32 编辑

lindabell 发表于 2021-10-20 14:15
目测没有校验的,存在干扰的场合就可能有问题
比如串口通信,还有电机的情况。 ...

要有分層的概念,msgpack 只是定義 payload 數據,你說的是更底層的封包

我在 14 樓說了,本地串口通訊沒必要用 msgpack
就算用,也可以配合底層的封包協議,譬如 cdbus / cdnet

我一樓說的適合 MCU 是特指替代 json 的場合,和遠程服務器通訊

chewy 发表于 2021-10-20 16:02:12

markmsgpack    正在折腾初步的http东东

BongBong 发表于 2021-11-10 12:47:52

dukelec 发表于 2021-10-20 00:37
json 不方便傳輸二進制數據,擴展性不好,譬如傳輸波形數據、圖片、固件等,在 json 內使用 base64 的話 ...

其实了解下AT的历史,就知道AT是非常有意义的, 可打印, 直观的方案。
当有好AT解析工具(20年年前就有), 这都不算事
当然直观的还有json
对于串口(本身速率限制)来说,都差不多,效率提高不了多少。
网络就不一样了,应用场合(图像,视频)决定了一定要干二进制。
但为了直观还是会有http

总结:
1、直观非常有意义
2、一切以场景需求为准

正如,iphone出红色款就能再卖一次,因为我们真的在意颜色

dukelec 发表于 2021-11-10 13:11:55

BongBong 发表于 2021-11-10 12:47
其实了解下AT的历史,就知道AT是非常有意义的, 可打印, 直观的方案。
当有好AT解析工具(20年年前就有), ...

沒有意義,想要直觀,在工具上做文章就好,調試也是一樣,譬如收到二進制的 msgpack 數據,你完全可以按照 json 的字符串形態打印出來

特別是 AT,為了方便人肉敲命令,才設計成現在的模樣,問題是,現在誰沒事人肉敲 AT 命令?就算有人肉控制的需求,把這個需求做到調試軟件裡面不香嗎?為了這個偽需求,要頻繁的在字符串模式和二進制模式之間切換,切的不好還要強制復位模式,還有不同功能之間也經常相互干擾,簡直不可理喻,現在很多人用 opencpu 的移動通訊模組,很大的一個原因就是不想碰噁心的 AT。

BongBong 发表于 2021-11-10 22:37:20

dukelec 发表于 2021-11-10 13:11
沒有意義,想要直觀,在工具上做文章就好,調試也是一樣,譬如收到二進制的 msgpack 數據,你完全可以按 ...

一般人即使不太懂电脑,敲个AT5分钟教会
msgpack估计要5天或者半年

我是一个大白菜 发表于 2021-11-24 12:03:36

楼主,工程里直接放 c文件和h文集,编译报错 /msgpackel.c:26:9: warning: implicit declaration of function ‘put_unaligned16’ 这个函数是不是在其他c文件里

dukelec 发表于 2021-11-24 13:15:27

我是一个大白菜 发表于 2021-11-24 12:03
楼主,工程里直接放 c文件和h文集,编译报错 /msgpackel.c:26:9: warning: implicit declaration of functi ...

是的,readme 有說其它 h 的連接,要手動下載一下

mmiker 发表于 2021-11-24 14:23:26

楼主来个jsonMessagePack ProtocolBuf 的对比评测呗。

我是一个大白菜 发表于 2021-11-24 16:03:55

dukelec 发表于 2021-11-24 13:15
是的,readme 有說其它 h 的連接,要手動下載一下

好的,我下载了,发一份上来。

dukelec 发表于 2021-11-24 16:56:15

mmiker 发表于 2021-11-24 14:23
楼主来个jsonMessagePack ProtocolBuf 的对比评测呗。

json 是文本格式,mcu 解析麻烦,不能传输二进制数据
json 做为配置文件,人工编辑比较方便,但 json 不支持注释等特性,做配置文件可用其变种,譬如 json5
做为通讯,用文本较为方便 debug


ProtocolBuf 类似 c 语言的 struct 结构,数据本身的含义要查看协议文档才能知晓,譬如文档规定前 4 字节是一个 float 型数据,表示电压值
ProtocolBuf 的版本改动,通讯的双方都要判断版本,不同版本的协议要调用不同的「协议文档」来解析,很麻烦,但效率最高


MessagePack 是高度类似 json 的自解析格式,譬如 {"voltage": 3.23},没有协议文档,也能猜到含义
和 json 不同的是,MessagePack 是二进制,方便 mcu 解析,解析效率也非常高,但低于 ProtocolBuf
自解析格式由于包含了 "voltage" 这样的信息,数据量也比 ProtocolBuf 大很多
协议版本变化不大不影响通讯,譬如增加一些字段,接收方直接忽略即可

MessagePack 除了用来通讯,也可以用来保存数据,譬如做为某个软件导出的工程文件(譬如 CDEncrypt 开源加密小工具导出的文件(加密前))
做为通讯,debug 稍微麻烦一点,但也是可以解决的,譬如转成类似 json 的文本再打印
做为保存数据的文件,人工手动编辑不现实,但一般也没这个必要,因为哪个软件导出,往往也是用同一个软件打开编辑;也可以用第三方的 MessagePack 文件编辑工具


如果让我打分,0~5 分,数值越大表示越优秀:

数据大小方面:Json 0; ProtocolBuf 5; MessagePack 3
解析效率方面:Json 0; ProtocolBuf 5; MessagePack 4
方便程度方面:Json 5; ProtocolBuf 0; MessagePack 4
通用性方面 :Json 1; ProtocolBuf 3; MessagePack 5 (Json 不支持二进制,所以很多场合不能用;ProtocolBuf 需要提前告知协议格式,做为文件分享也不合适)

monkeynav 发表于 2021-11-24 19:09:45

BongBong 发表于 2021-11-10 22:37
一般人即使不太懂电脑,敲个AT5分钟教会
msgpack估计要5天或者半年

AT或者结构体这些是面向mcu的方案,面向现代互联网得用json protobuf messagepack。

BongBong 发表于 2021-11-25 22:00:27

monkeynav 发表于 2021-11-24 19:09
AT或者结构体这些是面向mcu的方案,面向现代互联网得用json protobuf messagepack。 ...

也不存在所谓现代这个问题, 适合就好.

rei1984 发表于 2021-11-26 10:38:57

过来膜拜大佬!!!!


论坛里面除了。古大师 ,就属你在数字电路 程序设计里面最牛逼。

monkeynav 发表于 2021-11-26 12:42:06

BongBong 发表于 2021-11-25 22:00
也不存在所谓现代这个问题, 适合就好.

开发效率天壤之别

BongBong 发表于 2021-11-26 19:53:51

monkeynav 发表于 2021-11-26 12:42
开发效率天壤之别

没有觉得开发效率高在哪里,世界比较大,适合就好,你现在觉得现代,再过几年就是落后。
页: [1]
查看完整版本: 分享刚造的轮子:MessagePack 的 C 库(little-endian 版本)