protobuf —— 快速上手

protobuf —— 快速上手

  • 创建 .proto 文件
    • 添加注释
    • 指定proto3语法
    • package 声明符
    • 定义消息(message)
  • 定义消息字段
      • 字段定义基本格式
      • 字段名称命名规范
      • 字段类型
      • 字段唯一编号
      • 示例
    • 转换关系
      • 示例:增加姓名和年龄字段
    • 字段唯一编号
      • 字段编号范围
      • 编码效率
      • 实践指导
  • 编译 contacts.proto 文件,生成 C++ 文件
    • 编译命令
  • 编译 contacts.proto 文件后会生成什么
      • 序列化注意事项
  • 序列化和反序列化

我们今天来快速上手protobuf的用法,我们会用一个简单的通讯录来进行讲解:

如果还没有装好protobuf的小伙伴可以点击这里:

https://blog.csdn.net/qq_67693066/article/details/139215057

如果大家使用Linux的话,并且对版本没有太多要求的,可以直接用Linux的软件包来安装,Centos和Ubuntu都可以,我这里以Centos为例:
在这里插入图片描述
Ubuntu就换成apt,安装的版本是14的,也是21年发布的版本,也不错。

创建 .proto 文件

装好protobuf之后,我们用vscode连接,大家可以下载一个支持protobuf语法的插件,我下的是这一个:
在这里插入图片描述

然后创建一个后缀为.proto的文件:
在这里插入图片描述
创建 .proto 文件时,文件命名应该使用全小写字母命名,多个字母之间用 _ 连接。 例如:lower_snake_case.proto

添加注释

向文件添加注释,可使使用 // 或者 /* ... */
在这里插入图片描述

指定proto3语法

Protocol Buffers 语言版本3,简称 proto3,是 .proto 文件的最新语法版本。Proto3 简化了 Protocol Buffers 语言,既易于使用,又能在更多编程语言中运用自如。它支持使用 Java、C++、Python 等多种语言生成 Protocol Buffer 代码。

.proto 文件里,需使用 syntax = "proto3"; 来指定文件遵循 proto3 语法,这条声明必须位于除去注释内容的首行。如果未作指定,编译器将默认采用 proto2 语法。
例如,在通讯录 1.0 的 contacts.proto 文件中,指定使用 proto3 语法的方式如下所示:
在这里插入图片描述

package 声明符

在 Protocol Buffers(protobuf)的 .proto 文件中,package 是一个可选但强烈推荐的声明语句。它扮演着命名空间的角色,用于组织和分隔不同的消息类型定义,确保即便在导入多个 .proto 文件到同一个项目时,消息类型之间也不会发生名称冲突。简而言之,通过指定独一无二的包名,可以有效避免定义的消息类型重名问题。

以“通讯录 1.0”项目的文件为例,为了明确该文件中消息类型的所属命名空间,可以在文件开头添加如下的 package 声明:
在这里插入图片描述

定义消息(message)

在分布式系统和网络通信中,消息(message)扮演着至关重要的角色,它是定义数据交换格式的基础。为何需要定义消息? 主要原因有二:

  1. 协议定制:网络通信依赖于双方遵守的协议,这些协议定义了数据如何封装和解析。通过在.proto文件中定义消息,您可以精确地描述数据的结构,包括字段类型、名称及其顺序。这样,Protocol Buffers工具就能自动生成对应语言的源代码,实现序列化(将数据结构转换为字节流以便网络传输)和反序列化(将接收到的字节流还原为数据结构)功能。比如TCP/IP协议中的报文头和数据部分,就是一个典型的结构化数据示例。
  1. 数据持久化:当数据需要被存储到诸如数据库这类持久化存储介质时,清晰、统一的数据结构变得尤为重要。消息定义不仅帮助组织数据,还能确保数据的一致性和高效存储。通过将数据封装在消息中,可以更容易地映射到数据库的表结构或文档模型中,简化数据的存取逻辑。

以“通讯录1.0”为例,为“联系人”创建一个消息定义,就是预先规划好联系人信息的结构,比如姓名、电话号码、电子邮箱等,使得在实际编码时,可以直接利用Protocol Buffers生成的类来操作这些数据,无需手动处理序列化和解析的细节。这样,无论是网络间的数据交换,还是数据库的存储读取,都能保持高效和一致。
.proto 文件中定义⼀个消息类型的格式为:

message 消息类型名{
}

在这里插入图片描述

简单来说,message有点像C++中的class进行属性的封装。

定义消息字段

字段定义基本格式

字段定义遵循以下基本格式:

字段类型 字段名 = 字段唯一编号;

字段名称命名规范

  • 全小写字母:字段名应全部使用小写字母,以保持一致性并符合protobuf的命名约定。
  • 使用下划线_连接:如果字段名由多个单词组成,单词间应使用下划线分隔,提高可读性。例如,first_name而非firstName

字段类型

字段类型分为标量数据类型和特殊类型两大类:

  • 标量数据类型包括但不限于:

    • int32, int64: 整型
    • float, double: 浮点数
    • bool: 布尔值
    • string: 字符串
    • bytes: 二进制数据
  • 特殊类型

    • 枚举(enum): 自定义的枚举类型,用于限定某个字段的取值范围。
    • 其他消息类型: 引用其他已定义的消息类型作为字段类型,实现复杂数据结构的嵌套。

字段唯一编号

  • 编号要求:每个字段都应分配一个唯一的整数编号,用于在序列化和反序列化过程中识别字段。
  • 编号范围:通常情况下,编号1到15的字段在编码时较为节省空间,因为它们可以用一个字节编码(如果字段没有被省略)。16及以上的编号需要更多的字节来编码。
  • 不可变更性:一旦消息发布并被使用,字段编号就不应该被修改或重新分配,因为这将破坏与旧版本的兼容性。新增字段应分配新的编号,而避免更改现有字段编号。

示例

下面是一个简单的Person消息定义示例:

syntax = "proto3";message Person {int32 id = 1;          // 唯一IDstring first_name = 2;  // 名string last_name = 3;   // 姓int32 age = 4;          // 年龄bool is_student = 5;    // 是否为学生
}

在这个例子中,Person消息包含了几个基本的标量数据类型的字段,并且每个字段都分配了一个唯一的编号。

转换关系

这个表格更清晰地展示.proto文件中定义的消息字段类型及其与C++语言类型的对应关系:

.proto TypeC++ TypeNotes
doubledouble-
floatfloat-
int32int32_t使用变长编码[1]。负数编码效率较低,如果字段可能为负值,建议使用sint32
int64int64_t使用变长编码[1]。负数编码效率较低,如果字段可能为负值,建议使用sint64
uint32uint32_t使用变长编码[1]
uint64uint64_t使用变长编码[1]
sint32int32_t使用变长编码[1]。符号整型,负值编码效率高于常规int32
sint64int64_t使用变长编码[1]。符号整型,负值编码效率高于常规int64
fixed32uint32_t定长4字节。如果值通常大于2^28,则比uint32更高效。
fixed64uint64_t定长8字节。如果值通常大于2^56,则比uint64更高效。
sfixed32int32_t定长4字节。
sfixed64int64_t定长8字节。
boolbool-
stringstd::string包含UTF-8和ASCII编码的字符串,长度不超过2^32。
bytesstd::string可包含任意字节序列,长度不超过2^32。

[1] 变长编码指的是Protocol Buffers使用一种可变长度的编码方式(如VarInt编码),这种编码方式对于较小数值占用空间较少,但大数值会占用更多字节。对于fixed32fixed64sfixed32sfixed64,它们使用固定长度编码,不论数值大小,始终占用指定的字节数。

示例:增加姓名和年龄字段

假设我们要在一个消息中增加name(字符串类型)和age(假设为非负整数,使用uint32类型)字段,那么在.proto文件中的定义可能是这样的:

message Person {string name = 1;  // 姓名uint32 age = 2;   // 年龄
}

这里name字段使用了字符串类型(对应C++中的std::string),age字段使用了无符号32位整型(对应C++中的uint32_t),并为每个字段分配了唯一的编号。

字段唯一编号

这里还要说明一下字段唯一编号:

在Protocol Buffers(protobuf)中,字段唯一编号的选取是一个关键设计决策,因为它直接影响到消息的序列化效率和兼容性。下面是关于字段编号范围及编码的一些关键点:

字段编号范围

字段编号的有效范围是从1到536,870,911(即2^29 - 1)。但是,需要注意的是,19000至19999这一区间内的编号是被protobuf协议实现预留的,不允许用户直接使用。尝试使用这些预留编号会导致编译时错误,提醒开发者这些编号不可用,因为它们在protobuf内部有特殊的用途。

编码效率

  • 编号与编码大小:为了优化序列化后的消息体积,protobuf采用了变长编码。其中,编号小于等于15的字段仅需1个字节来编码编号和类型信息;编号在16至2047之间的字段需要2个字节;更大的编号则需要更多的字节。这意味着,编号较小的字段在序列化时更为高效。
  • 优化建议:鉴于此,建议将频繁出现且长度较短的字段分配编号在1至15之间,以减少消息的整体大小。同时,应保留一部分低编号以备未来可能添加的常用字段。

实践指导

  • 避免预留区间:确保在定义.proto文件时,字段编号避开19000至19999这个预留区间。
  • 编号策略:合理规划字段编号,考虑当前及未来的扩展性。频繁使用的字段应给予更低的编号以优化效率,同时留出一定编号空间供后续扩展使用。

通过遵循上述原则,可以确保你的protobuf消息定义既高效又具有良好的向前兼容性,便于维护和升级。

编译 contacts.proto 文件,生成 C++ 文件

编译命令

protoc [--proto_path=IMPORT_PATH] --cpp_out=DST_DIR path/to/file.proto

protoc 是 Protocol Buffers(protobuf)的编译器,用于将 .proto 文件转换为目标编程语言的源代码文件。您给命令行参数示例中,各部分的含义如下:

  • --proto_path=IMPORT_PATH-I=IMPORT_PATH:
  • 作用:指定 .proto 文件的搜索路径。当你在 .proto 文件中使用 import 语句引入其他 .proto 文件时,protoc 会根据这个路径去查找这些被引用的文件。
  • 参数说明IMPORT_PATH 是一个或多个目录路径,可以多次使用该选项指定多个路径。如果不指定,默认会在当前目录下查找。
  • --cpp_out=DST_DIR
  • 作用:指定编译生成的C++源代码输出目录。当您希望将 .proto 文件编译为C++代码时,使用这个选项。
  • 参数说明DST_DIR 是您希望输出C++源代码的目录路径。protoc 会在这个目录下生成对应的 .pb.cc(源文件)和 .pb.h(头文件),这些文件包含了序列化和反序列化消息所需的函数和其他必要的代码。
  • path/to/file.proto
  • 作用:指定需要编译的 .proto 文件路径。
  • 说明:这是您要编译的protobuf定义文件的完整或相对路径。protoc 会读取这个文件并根据指定的输出选项(如 --cpp_out)生成目标语言的代码。

综上,整个命令的含义是:使用 protoc 编译器,从指定的导入路径中查找任何被导入的 .proto 文件,然后将 path/to/file.proto 文件编译为C++源代码,并将生成的文件输出到 DST_DIR 目录下。这样做的目的是为了让开发者能够方便地将protobuf消息类型集成到他们的C++项目中。

举个例子
当然,让我们通过一个具体的例子来进一步说明如何使用这些参数。假设你有一个项目结构如下:

/my_project/
|-- protos/
|   |-- message.proto
|-- src/
|-- CMakeLists.txt
|-- main.cpp

在这个场景中,你有一个名为 message.proto 的 Protocol Buffers 定义文件,位于 protos/ 目录下,你想将它编译成C++代码,并将生成的文件放在 src/ 目录中以便在你的C++项目中使用。

你可以打开终端(命令行界面),并导航到 /my_project/ 目录,然后执行以下命令:

protoc --proto_path=./protos --cpp_out=./src ./protos/message.proto//或者
protoc -I=./protos --cpp_out=./src ./protos/message.proto

这里发生了什么:

  • --proto_path=./protos 指定了 .proto 文件的搜索路径为当前目录下的 protos/ 目录。如果有 message.protoimport 了其他 .proto 文件,编译器会在这个目录下查找它们。
  • --cpp_out=./src 指定编译生成的C++源代码放置在 src/ 目录中。
  • ./protos/message.proto 是你要编译的具体 .proto 文件路径。

执行完这个命令后, message.proto 定义了一个消息类型,protoc 编译器将在 src/ 目录下生成两个文件:message.pb.ccmessage.pb.h,这些就是你可以包含在C++项目中使用的源代码和头文件,用于处理该消息类型的序列化与反序列化等操作。

编译 contacts.proto 文件后会生成什么

编译 contacts.proto 文件后,会生成所选择语言的代码,我们选择的是C++,所以编译后生成了两个文件: contacts.pb.h contacts.pb.cc 。
在这里插入图片描述

对于编译生成的 C++ 代码,包含了以下内容 :

  • 对于每个 message ,都会生成⼀个对应的消息类。
  • 在消息类中,编译器为每个字段提供了获取和设置⽅法,以及⼀下其他能够操作字段的方法。
  • 编辑器会针对于每个 .proto 文件生成 .h 和 .cc 文件,分别用来存放类的声明与类的实现。

contacts.pb.h 部分代码展示:

 // string name = 1;void clear_name();const std::string& name() const;template <typename ArgT0 = const std::string&, typename... ArgT>void set_name(ArgT0&& arg0, ArgT... args);std::string* mutable_name();PROTOBUF_NODISCARD std::string* release_name();void set_allocated_name(std::string* name);private:const std::string& _internal_name() const;inline PROTOBUF_ALWAYS_INLINE void _internal_set_name(const std::string& value);std::string* _internal_mutable_name();public:// int32 age = 2;void clear_age();int32_t age() const;void set_age(int32_t value);private:int32_t _internal_age() const;void _internal_set_age(int32_t value);public:

上述的例子中:

  • 每个字段都有设置和获取的方法, getter 的名称与小写字段完全相同,setter 方法以 set_ 开头。
  • 每个字段都有⼀个 clear_ 方法,可以将字段重新设置回 empty 状态。
    contacts.pb.cc 中的代码就是对类声明方法的⼀些实现,在这里就不展开了。

序列化和反序列化方法在哪里呢?在消息类的父类MessageLite 中,提供了读写消息实例的方法,包括序列化方法和反序列化方法:

 bool MergeFromCodedStream(io::CodedInputStream* input);bool ParseFromCodedStream(io::CodedInputStream* input);bool ParseFromZeroCopyStream(io::ZeroCopyInputStream* input);bool ParseFromArray(const void* data, int size);inline bool ParseFromString(const std::string& data) {return ParseFromArray(data.data(), static_cast<int>(data.size()));}// Merges this message's unknown field data (if any).  This works whether// the message is a lite or full proto (for legacy reasons, lite and full// return different types for MessageType::unknown_fields()).template <typename MessageType>bool MergeFromMessage(const MessageType& message);// Serialization.bool SerializeToString(std::string* output) const;bool SerializeToCodedStream(io::CodedOutputStream* output) const;static const UnknownFieldSet& default_instance();

理解您的要求,这里是对于序列化概念及Protocol Buffers(protobuf)中消息序列化方法的解释,稍作调整以供参考:

序列化注意事项

  • 二进制输出:序列化过程将结构化数据转换成紧凑的二进制形式,而非易于阅读的文本格式。这有助于减小程序体积,提高网络传输效率和存储效率。
  • 多样化的序列化方法:Protocol Buffers提供了多种序列化API,包括但不限于SerializeToString()SerializeToOstream()SerializeToArray()等。这些方法虽然输出形式各异——字符串、输出流或字节数组,但核心目的相同:将消息对象转化为二进制数据,适应不同应用场景的需要。
  • 不变性保证:序列化操作通过const成员函数实现,意味着调用序列化函数不会修改消息对象本身的内在状态。数据的转换发生在序列化过程中,并将结果输出到指定的目标(如内存、字符串或流),而不影响原始对象。
  • 深入探索API:欲了解更多关于protobuf消息对象的序列化及其他功能,可查阅protobuf官方文档中的消息API完整列表,那里详尽地介绍了每种方法的使用方法和适用场景,帮助开发者高效利用protobuf的强大功能。

序列化和反序列化

创建⼀个测试文件 main.cc,方法中我们实现:

  • 对⼀个联系人的信息使用 PB 进行序列化,并将结果打印出来。
  • 对序列化后的内容使用 PB 进行反序列,解析出联系人信息并打印出来。
#include <iostream> 
#include "contacts.pb.h" // 引⼊编译⽣成的头⽂件
using namespace std; int main() 
{ string people_str; {// .proto⽂件声明的package,通过protoc编译后,会为编译⽣成的C++代码声明同名的// 其范围是在.proto ⽂件中定义的内容contacts::PeopleInfo people; people.set_age(20); people.set_name("张珊"); // 调⽤序列化⽅法,将序列化后的⼆进制序列存⼊string中if (!people.SerializeToString(&people_str)) { cout << "序列化联系⼈失败." << endl; }// 打印序列化结果cout << "序列化后的 people_str: " << people_str << endl; }{contacts::PeopleInfo people; // 调⽤反序列化⽅法,读取string中存放的⼆进制序列,并反序列化出对象if (!people.ParseFromString(people_str)) { cout << "反序列化出联系⼈失败." << endl; } // 打印结果cout << "Parse age: " << people.age() << endl; cout << "Parse name: " << people.name() << endl; }
}

代码书写完成后,编译 main.cc,生成可执行程序 TestProtoBuf :

g++ main.cc contacts.pb.cc -o TestProtoBuf -std=c++11 -lprotobuf

在这里插入图片描述

其中-lprotobuf必加,否则会有连接错误,-std=c++11可以为更高级。

执行TestProtoBuf ,可以看见 people 经过序列化和反序列化后的结果:
在这里插入图片描述

由于 ProtoBuf 是把联系⼈对象序列化成了⼆进制序列,这里用 string 来作为接收二进制序列的容器。所以在终端打印的时候会有换行等⼀些乱码显示。

所以相对于 xml 和 JSON 来说,因为被编码成⼆进制,破解成本增本,ProtoBuf 编码是相对安全的。

最后,总结一下:
在这里插入图片描述

  1. 创建 .proto 文件:通过定义message类型,我们详细规划了数据对象的结构和组成部分,包括字段名称、类型及其编码规则,为后续的通信协议奠定基础。
  2. 通过 protoc 工具将 .proto 文件转换为编程语言绑定代码protoc 编译器读取.proto文件并生成目标语言(如C++、Java、Python等)的接口和实现代码。这些代码被组织在头文件(.h)中声明接口,源文件(.cc或其他后缀)中实现细节,方便开发者在项目中直接调用。
  3. 整合生成的接口到项目中,实现数据操作和消息的编解码:将编译得到的头文件包含到项目源代码中,即可利用预生成的类和方法来实例化消息对象,设置和检索字段值。同时,利用内置的序列化方法(如SerializeToStringParseFromString等)轻松地在二进制格式与消息对象之间转换,支撑了数据在网络间的高效传输与存储需求。这样,开发者便能集中精力于业务逻辑,而不必关注底层的数据序列化和协议细节。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/17756.shtml

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

相关文章

英伟达SSD视觉算法,jetson.inference在jetson nano中部署

一、用官方镜像刷机 安装SD卡擦除工具SD Card Formatter https://www.sdcardformatter.com/download/ 格式化SD卡 下载官方镜像 https://developer.nvidia.com/jetson-nano-sd-card-image 安装刷机工具balenaEtcher https://www.balena.io/etcher 将上面下载的镜像压缩包解…

spark的简单学习二

一 spark sql基础 1.1 Dataframe 1.介绍&#xff1a; DataFrame也是一个分布式数据容器。然而DataFrame更像传统数据库的二维表 格&#xff0c;除了数据以外&#xff0c;还掌握数据的结构信息&#xff0c;即schema。同时&#xff0c;与Hive类似&#xff0c;DataFrame也支 持…

gin框架精通篇(二)

原生数据库使用 导入模块&#xff1a;go get -u github.com/go-sql-driver/mysql 安装 mysql 数据库 安装数据库可能遇到的问题&#xff1a;&#xff08;网上的方法基本可以解决&#xff09; ERROR 1045 (28000): Access denied for user ‘-root’‘localhost’ (using passwo…

HTML 页面布局

慢慢生活&#xff0c;慢慢变好 —— 24.5.28 页面布局 盒子: 页面中所有的元素(标签)&#xff0c;都可以看做是一个盒子&#xff0c;由盒子将页面中的元素包含在一个矩形区域内&#xff0c;通过盒子的视角更方便的进行页面布局 盒子模型组成: 内容区域(content)、内边距区域(pa…

数据结构的希尔排序(c语言版)

一.希尔排序的概念 1.希尔排序的基本思想 希尔排序是一种基于插入排序算法的优化排序方法。它的基本思想如下: 选择一个增量序列 t1&#xff0c;t2&#xff0c;......&#xff0c;tk&#xff0c;其中 ti > tj, 当 i < j&#xff0c;并且 tk 1。 按增量序列个数k&#…

Centos安装,window、ubuntus双系统基础上安装Centos安装

文章目录 前言一、准备工作二、开始安装1、2、首先选择DATE&TIME2、选择最小安装3、 选择安装位置 总结 前言 因工作需要&#xff0c;我需要在工控机上额外装Centos7系统&#xff0c;不过我是装在机械硬盘上了不知道对性能是否有影响&#xff0c;若有影响&#xff0c;后面…

基于C#开发web网页管理系统模板流程-主界面管理员入库和出库功能完善

前言 紧接上篇->基于C#开发web网页管理系统模板流程-主界面管理员录入和编辑功能完善-CSDN博客 本篇将完善主界面的管理员入库和出库功能&#xff0c;同样的&#xff0c;管理员入库和出库的设计套路适用于动态表的录入和编辑 首先还是介绍一下本项目将要实现的功能 &#xf…

[Android]项目打包APK时报错PKCS12 keystore not in version 3 format

报错&#xff1a; PKCS12 keystore not in version 3 format Execution failed for task :app:packageRelease. > A failure occurred while executing com.android.build.gradle.tasks.PackageAndroidArtifact$IncrementalSplitterRunnable > com.android.ide.commo…

如何为个人网站部署SSL安全证书,以实现网站的 HTTPS 加密协议访问?

哈喽&#xff0c;大家好呀&#xff01;这里是码农后端。完成了域名的备案与解析后&#xff0c;就可以通过域名来访问我们的网站了。本篇将介绍如何为我们的网站部署SSL安全证书&#xff0c;实现网站的 HTTPS 加密协议访问。 1、购买SSL证书 未进行SSL证书部署&#xff0c;访问网…

回答篇二:测试开发高频面试题目

引用之前文章&#xff1a;测试开发高频面试题目 本篇文章是回答篇&#xff08;持续更新中&#xff09; 1. 在测试开发中使用哪些自动化测试工具和框架&#xff1f;介绍一下你对其中一个工具或框架的经验。 a. 测试中经常是用的自动化测试工具和框架有Selenium、Pytest、Postman…

调整表格大小

方法一&#xff1a;使用鼠标拖动表格边框或右下角的调整控点 在Word文档中&#xff0c;选中要缩小的表格&#xff0c;将鼠标指针放在表格的边框线上&#xff0c;直到指针变成双箭头的形状。 按住鼠标左键&#xff0c;拖动边框线&#xff0c;调整表格的宽度或高度。如果同时按住…

AI视频教程下载:使用ChatGPT进行商务写作

你将学到什么&#xff1f; 学习如何将ChatGPT集成到你的写作过程中&#xff0c;并有效地将其用作商务写作的个人写作助手。 学习如何使用ChatGPT生成想法&#xff0c;提高你的书面沟通的结构、清晰度和连贯性。 你将学习使用ChatGPT的最佳实践&#xff0c;包括如何自定义其设…

Win10版本TDengine使用分享

软件介绍 TDengine是一款开源、高性能、可扩展的时间序列数据库&#xff08;TSDB&#xff09;。它由涛思数据公司开发&#xff0c;专为处理大规模时间序列数据而设计。时间序列数据是指按时间顺序排列的数据点序列&#xff0c;广泛应用于物联网、大数据分析、金融等领域。TDen…

Redis解决缓存一致性问题

文章目录 ☃️概述☃️数据库和缓存不一致采用什么方案☃️代码实现☃️其他 ☃️概述 由于我们的 缓存的数据源来自于数据库, 而数据库的 数据是会发生变化的, 因此,如果当数据库中 数据发生变化,而缓存却没有同步, 此时就会有 一致性问题存在, 其后果是: 用户使用缓存中的过…

DSP6657 GPIO中断学习

1 简介 使用创龙板卡的KEY2按键通过中断的方式控制LED3的亮灭 2 中断学习 在C665x设备上&#xff0c;CPU中断是通过C66x CorePac中断控制器进行配置的。该中断控制器允许最多128个系统事件被编程到任意12个CPU可屏蔽中断输入&#xff08;CPUINT4至CPUINT15&#xff09;、CPU…

短剧解说一键生成原创文案的快速方法

如今短剧创作火的一塌糊涂&#xff0c;它们以其简洁明了的剧情、生动有趣的角色和紧凑的节奏&#xff0c;吸引了大量观众的关注。因此&#xff0c;它所带来的流量是非常巨大&#xff0c;不少人将流量的获取瞄准了短剧创作领域以及短剧解说领域。而对于短剧解说人员来讲&#xf…

微服务项目收获和总结---第5天(定时发布)

延迟任务 目录 延迟任务技术对比&#xff1a; Redis实现定时任务&#xff1a;​编辑新增任务&#xff1a;取消任务&#xff1a;拉取任务&#xff1a;Zset定时刷新数据到List中&#xff1a;分布式锁实现定时任务只刷新一次&#xff1a; 技术对比&#xff1a; Redis实现定时任…

香橙派 AIpro 昇腾 Ascend C++ 分类模型适配

香橙派 AIpro 昇腾 Ascend C 分类模型适配 flyfish 文章目录 香橙派 AIpro 昇腾 Ascend C 分类模型适配前言一、PyTorch官网resnet模型处理方式1、PyTorch模型 导出 onnx格式2、完整测试 输出top1结果3、完整测试 输出top5结果 二、YOLOv8官网resnet模型Python处理方式三、昇腾…

云衔科技:为什么推荐使用zoho crm客户管理系统?

在当今快速变化的商业环境中&#xff0c;企业对高效、智能化的客户关系管理&#xff08;CRM&#xff09;系统的需求日益增长。Zoho CRM&#xff0c;作为全球领先的企业级CRM解决方案提供商&#xff0c;凭借其全面的功能、高度的可定制性、以及无缝集成的生态系统&#xff0c;成…

探寻导师:2024年最新研究生导师评价汇总

随着考研热潮的不断升温&#xff0c;对于研究生导师的选择变得愈发重要。导师不仅关系到研究生的学术成长&#xff0c;还直接影响到未来的职业发展。然而&#xff0c;如何找到一位合适的导师成为许多考生头疼的问题。为了帮助广大考生更好地选择导师&#xff0c;整理了最新最全…