跳转至

12 Protobuf数据处理

作者:沈园园

一、PROTOBUF 数据处理简介

Protobuf(Protocol Buffers)是 google 开发的一种数据描述语言,它能够将结构化的数据序列化,并且可以将序列化的数据进行反序列化恢复原有的数据结构。一般用于数据存储以及通信协议方面。

它类似于 JSON,但更小更快,并且可以生成原生语言绑定。您只需定义一次数据结构,然后可以使用特定生成的源代码,轻松地在各种数据流中以及使用各种语言读写结构化数据。

Protobuf 解决了哪些问题?

Protobuf 提供了一种适合用于几兆字节大小的类型化、结构化数据包的序列化格式。该格式适用于瞬时网络流量和长期数据存储。Protobuf 可以扩展新的信息,而不会使现有数据无效,也不需要更新代码。

具体细节可以参考官方文档:https://protobuf.dev/overview/

二、注意事项

1、protobuf 核心库支持 Proto2 和 Proto3 协议版本;

2、person.proto 文件是消息结构定义文件(Schema),它是人类可读的文本文件,使用 Protobuf IDL 语法,该文件定义了消息类型 Person 的结构(字段名、类型、编号等),文件中示例内容如下:

syntax = "proto2";

message Person {
  optional string name = 1;
  optional int32 id = 2;
  optional string email = 3;
}

该文件需要由开发者手动创建编写,语言规范参考:

Protocol Buffers 官方语言指南(proto3):Language Guide (proto 3) | Protocol Buffers Documentation

Protocol Buffers 官方语言指南(proto2):Language Guide (proto 2) | Protocol Buffers Documentation

3、person.pb 文件是 Schema 描述的二进制文件(FileDescriptorSet),是 person.proto 经过 protoc -o 编译后生成的二进制元数据,该文件包含 Person 消息的完整结构描述(字段名、编号、类型),用于被 protobuf.load() 加载,供后续 protobuf.encode() 和 protobuf.decode() 能知道 Person 的结构,从而正确编解码;

4、关于如何得到 person.pb 文件说明如下:

  • 需要先下载 protoc.exe,下载链接:https://github.com/protocolbuffers/protobuf/releases
  • 在 protoc.exe 文件目录下打开 CMD,输入 protoc -o person.pb person.proto 即可生成 person.pb 文件,前提需要同目录下包含 person.proto 文件;

5、person.pbtxt 文件是用户数据的文本表示(TextFormat),它是人类可读的文本文件,表示一个具体的 Person 消息实例,内容是字段赋值,文件中示例内容如下:

name: "wendal"
id: 123
email: "abc@qq.com"

该文件需要用户手动编写(用于配置、测试),或者通过程序把用户数据以人类可读的文本格式保存在 .pbtxt 文件下;

该文件内容需要遵循 Protobuf 文本格式语法规范(Text Format Syntax),语言规范参考:

Protobuf Text Format 官方语言规范(草案):Text Format Language Specification | Protocol Buffers Documentation

6、person.pbtxt 文件用于调试、写测试用例、作为配置文件,可被支持 TextFormat 的 Protobuf 库解析为内存对象,在 LuatOS 中不使用 .pbtxt 文件(protobuf 库只支持 binary encode/decode,也就是 .pb 文件);

三、演示功能概述

1、main.lua:主程序入口;

2、protobuf_app.lua:protobuf 编解码功能模块;

四、准备硬件环境

1、Air780EGH核心板一块

2、TYPE-C USB数据线一根

3、Air780EGH核心板和数据线的硬件接线方式为

  • Air780EGH核心板通过TYPE-C USB口连接TYPE-C USB 数据线,数据线的另外一端连接电脑的USB口;
  • 核心板正面的 ON/OFF 拨动开关 拨到ON一端;

五、准备软件环境

5.1 软件环境

在开始实践本示例之前,先筹备一下软件环境:

1、烧录工具:Luatools 下载调试工具

2、内核固件:Air780EGH最新版本的内核固件

3、脚本文件:https://gitee.com/openLuat/LuatOS/tree/master/module/Air780EHM_Air780EHV_Air780EGH/demo/protobuf

4、lib脚本文件:使用Luatools烧录时,勾选 添加默认lib 选项,使用默认lib脚本文件

准备好软件环境之后,接下来查看如何烧录项目文件到Air780EGH核心板,将本篇文章中演示使用的项目文件烧录到780EGH核心板中。

5.2 API 介绍

protobuf 核心库:https://docs.openluat.com/osapi/core/protobuf/

六、程序结构

protobuf/
│── main.lua
│── protobuf_app.lua
│── person.pb
│── person.pbtxt
│── person.proto 
│── readme.md

6.1 文件说明

  1. main.lua:主程序入口文件。
  2. protobuf_app.lua:protobuf 编解码功能模块。
  3. person.pb:二进制文件,用于被 protobuf.load() 加载,详细说明见第二部分注意事项。
  4. person.pbtxt:文件用于调试、写测试用例,详细说明见第二部分注意事项。
  5. person.proto:消息结构定义文件,详细说明见第二部分注意事项。

七、代码详解

7.1 main.lua

主程序文件 main.lua 是整个项目的入口点。它负责初始化系统环境。

7.2 protobuf_app.lua

protobuf 编解码功能模块。

7.2.1 加载 protobuf 定义文件

-- 加载 pb 文件, 这个是 proto 经过 protoc.exe 编译后生成的二进制文件
-- 下载资源到模块时不需要下载 proto 文件
-- 转换命令: protoc -o person.pb person.proto
-- protoc.exe 下载地址: https://github.com/protocolbuffers/protobuf/releases
local pb_file = "/luadb/person.pb"

local tbdata = {
    name = "wendal",
    id = 123,
    email = "abc@qq.com"
}

if io.exists(pb_file) then
    local success, bytesRead = protobuf.load(io.readFile(pb_file))
    if not success then
        log.info("protobuf", "加载 protobuf 定义失败,已读取 " .. bytesRead .. " 字节")
        return
    else
        log.info("protobuf", "加载 protobuf 定义成功,共解析 " .. bytesRead .. " 字节")
    end
else
    log.info("protobuf", "pb 文件不存在")
    return
end

7.2.2 编码数据

-- 编码数据;
local pbdata = protobuf.encode("Person", tbdata)
if pbdata then
    -- 编码成功,编码后的数据通常包含不可见字符;
    -- 打印长度和十六进制内容(便于调试);
    log.info("protobuf", "编码成功,数据长度:" .. #pbdata)
    log.info("protobuf", "十六进制内容:" .. pbdata:toHex())
else
    log.info("protobuf", "编码失败:数据格式或类型不匹配")
end

-- 对比 protobuf 编码和 json 编码的大小;
local jdata = json.encode(tbdata)
if jdata then
    log.info("json", "编码成功,数据长度:" .. #jdata)
    log.info("json", "数据内容:" .. jdata)
else
    log.info("json", "编码失败:数据格式或类型不匹配")
end
-- 可见 protobuffs 比 json 节省很多空间;

7.2.3 数据解码

-- 数据解码;
local tbdata = protobuf.decode("Person", pbdata)
if tbdata then
    -- 解码后的数据为 Lua table 格式,需要转化为 json 进行显示;
    log.info("protobuf", "解码成功,数据内容:", json.encode(tbdata))
else
    log.info("protobuf", "解码失败")
end

7.2.4 清除所有已加载的定义数据

-- 清除所有已加载的定义数据;
protobuf.clear()
log.info("protobuf", "所有 protobuf 定义已清除")

八、运行结果展示

出现类似于下面的日志,就表示运行成功:

[000000000.744] I/user.main protobuf 001.000.000
[000000000.753] I/user.protobuf 加载 protobuf 定义成功,共解析 85 字节
[000000000.753] I/user.protobuf 编码成功,数据长度:22
[000000000.754] I/user.protobuf 十六进制内容:0A0677656E64616C107B1A0A6162634071712E636F6D
[000000000.754] I/user.json 编码成功,数据长度:47
[000000000.754] I/user.json 数据内容:{"name":"wendal","id":123,"email":"abc@qq.com"}
[000000000.755] I/user.protobuf 解码成功,数据内容: {"name":"wendal","id":123,"email":"abc@qq.com"}
[000000000.755] I/user.protobuf 所有 protobuf 定义已清除

九、总结

通过本文学习,你可以学习到protobuf 编解码功能。