搜索
bottom↓
回复: 31

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

[复制链接]

出615入1076汤圆

发表于 2021-10-18 18:59:17 | 显示全部楼层 |阅读模式
本帖最后由 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,库改起来也很简单)

譬如一个数据结构:

  1. {
  2.     "dev": "abc123",
  3.     "data": {
  4.         "voltage": 3.6,
  5.         "angle": [45, 90]
  6.     }
  7. }
复制代码



转换成字节流的示范:

  1. uint8_t msg_buf[256];

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

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

  8.     pos = mpk_w_str(pos, "data");       // create second item
  9.     pos =   mpk_w_map_hdr(pos, 2);      // create sub map
  10.     pos =   mpk_w_str(pos, "voltage");
  11.     pos =     mpk_w_float(pos, 3.6f);
  12.     pos =   mpk_w_str(pos, "angle");
  13.     pos =     mpk_w_array_hdr(pos, 2);
  14.     pos =       mpk_w_int(pos, 45);
  15.     pos =       mpk_w_int(pos, 90);

  16.     return pos - msg_buf;               // return length
  17. }
复制代码



解析字节流的示范:

  1. void check_data(void)
  2. {
  3.     mpk_t st;
  4.     uint8_t *payload, *end;
  5.     size_t num;
  6.     float voltage;


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


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


  21.     st = mpk_map_search(msg_buf, &payload, NULL, NULL, &num,
  22.                         "data", "angle", NULL);
  23.     if (st == MPK_ARRAY) {
  24.         uint8_t *pos = payload;
  25.         printf("data.angle[] length: %d\n", num);
  26.         for (int i = 0; i < num; i++) {
  27.             int32_t val;                                // val must be at least 4 bytes
  28.             st = mpk_parse(pos, NULL, &end, (uint8_t *)&val, NULL);
  29.             if (st != MPK_INT && st != MPK_UINT) {
  30.                 printf("  parse data.angle error\n");
  31.                 break;
  32.             }
  33.             printf("  data.angle[%d]: %ld\n", i, val);
  34.             pos = end;
  35.         }
  36.     } else {
  37.         printf("parse data.angle error: %d\n", st);
  38.     }
  39. }
复制代码



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

  1. #!/usr/bin/env python3

  2. import cgi, sys, os, json
  3. import umsgpackel

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

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

  15. if data: # for POST
  16.     class Form(dict):
  17.         pass
  18.     if is_mpk:
  19.         unpk = umsgpackel.unpackb(data)
  20.     else:
  21.         unpk = json.loads(data)
  22.     form = Form(unpk)
  23.     form.getvalue = form.get
  24. else: # for GET
  25.     form = cgi.FieldStorage()

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

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

  29. if is_mpk:
  30.     sys.stdout.buffer.write(umsgpackel.packb({"status": "ok"}))
  31. else:
  32.     print(json.dumps({"status": "ok"}))
复制代码


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

阿莫论坛20周年了!感谢大家的支持与爱护!!

月入3000的是反美的。收入3万是亲美的。收入30万是移民美国的。收入300万是取得绿卡后回国,教唆那些3000来反美的!

出0入16汤圆

发表于 2021-10-18 19:05:07 | 显示全部楼层
厉害!!!

出0入4汤圆

发表于 2021-10-18 19:09:10 | 显示全部楼层
大佬好像啥多会

出0入0汤圆

发表于 2021-10-19 01:57:38 | 显示全部楼层
2021的今天为啥要用message pack. 不学, 不研究, 不在意

出615入1076汤圆

 楼主| 发表于 2021-10-19 03:41:40 来自手机 | 显示全部楼层
BongBong 发表于 2021-10-19 01:57
2021的今天为啥要用message pack. 不学, 不研究, 不在意

有更好的推薦?

出0入0汤圆

发表于 2021-10-19 08:24:11 | 显示全部楼层
message pack remark!

出0入42汤圆

发表于 2021-10-19 08:32:19 | 显示全部楼层
BongBong 发表于 2021-10-19 01:57
2021的今天为啥要用message pack. 不学, 不研究, 不在意

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

出0入8汤圆

发表于 2021-10-19 08:33:10 | 显示全部楼层
我都直接cjson

出0入42汤圆

发表于 2021-10-19 09:11:49 | 显示全部楼层

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

出615入1076汤圆

 楼主| 发表于 2021-10-19 09:19:53 | 显示全部楼层
本帖最后由 dukelec 于 2021-10-19 09:40 编辑


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/

出0入0汤圆

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

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

出0入42汤圆

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

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

出0入0汤圆

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

那就json好了。

出615入1076汤圆

 楼主| 发表于 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/ ... ssage-serialization

出0入0汤圆

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

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

出615入1076汤圆

 楼主| 发表于 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,用鏈表代之,後續整理一下,可能也會分享出來)

出0入8汤圆

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

出615入1076汤圆

 楼主| 发表于 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 的場合,和遠程服務器通訊

出0入0汤圆

发表于 2021-10-20 16:02:12 | 显示全部楼层
mark  msgpack    正在折腾初步的http东东

出0入0汤圆

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


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

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

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

出615入1076汤圆

 楼主| 发表于 2021-11-10 13:11:55 来自手机 | 显示全部楼层
BongBong 发表于 2021-11-10 12:47
其实了解下AT的历史,就知道AT是非常有意义的, 可打印, 直观的方案。
当有好AT解析工具(20年年前就有), ...

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

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

出0入0汤圆

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

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

出0入42汤圆

发表于 2021-11-24 12:03:36 | 显示全部楼层
楼主,工程里直接放 c文件和h文集,编译报错 /msgpackel.c:26:9: warning: implicit declaration of function ‘put_unaligned16’ 这个函数是不是在其他c文件里

出615入1076汤圆

 楼主| 发表于 2021-11-24 13:15:27 来自手机 | 显示全部楼层
我是一个大白菜 发表于 2021-11-24 12:03
楼主,工程里直接放 c文件和h文集,编译报错 /msgpackel.c:26:9: warning: implicit declaration of functi ...

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

出0入10汤圆

发表于 2021-11-24 14:23:26 | 显示全部楼层
楼主来个json  MessagePack ProtocolBuf 的对比评测呗。

出0入42汤圆

发表于 2021-11-24 16:03:55 | 显示全部楼层
dukelec 发表于 2021-11-24 13:15
是的,readme 有說其它 h 的連接,要手動下載一下

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

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出615入1076汤圆

 楼主| 发表于 2021-11-24 16:56:15 | 显示全部楼层
mmiker 发表于 2021-11-24 14:23
楼主来个json  MessagePack 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 需要提前告知协议格式,做为文件分享也不合适)

出215入169汤圆

发表于 2021-11-24 19:09:45 来自手机 | 显示全部楼层
BongBong 发表于 2021-11-10 22:37
一般人即使不太懂电脑,敲个AT5分钟教会
msgpack估计要5天或者半年

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

出0入0汤圆

发表于 2021-11-25 22:00:27 | 显示全部楼层
monkeynav 发表于 2021-11-24 19:09
AT或者结构体这些是面向mcu的方案,面向现代互联网得用json protobuf messagepack。 ...

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

出0入25汤圆

发表于 2021-11-26 10:38:57 | 显示全部楼层
过来膜拜大佬!!!!


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

出215入169汤圆

发表于 2021-11-26 12:42:06 | 显示全部楼层
BongBong 发表于 2021-11-25 22:00
也不存在所谓现代这个问题, 适合就好.

开发效率天壤之别

出0入0汤圆

发表于 2021-11-26 19:53:51 | 显示全部楼层
monkeynav 发表于 2021-11-26 12:42
开发效率天壤之别

没有觉得开发效率高在哪里,世界比较大,适合就好,你现在觉得现代,再过几年就是落后。
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-4-26 00:14

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表