当前位置: 首页 > article >正文

Protocol Buffers在MCU上的nanopb介绍及使用详解

在嵌入式系统和资源受限的环境中,传统的Protocol Buffers 可能显得过于庞大。因此,nanopb 应运而生,它是一个轻量级的 Protocol Buffers 生成器,专为嵌入式系统设计c语言设计。本文将介绍如何安装和使用 nanopb,以及通过一个简单的例子来展示它的基本用法。

网上的大多数文章都是只讲如何使用。其实新手刚拿到后,很重要的一点是如何用起来?如何安装环境?protoc工具在哪里搞到?这里从环境介绍到详细使用做个总结,留作备忘。

什么是Protocol Buffers

Protocol Buffer是谷歌推出的,和开发语言无关、平台无关、可扩展的机制,用于序列化结构化数据——像XML,但它更小、更快、更简单。
关键特点:
1、跨平台。
2、可扩展。
3、序列化结构化数据。
4、使用简单。

官方网址:https://developers.google.com/protocol-buffers

支持最常使用的语言
1、C++
2、C#
3、Java
4、Python

什么是 nanopb

nanopb 是一个非常轻量级的 C 库,用于 Protocol Buffers 的序列化和反序列化。它专为嵌入式系统设计,可以运行在内存和存储空间有限的环境中。nanopb 支持 Protocol Buffers 2.3 和 3.0 版本的标准,因此可以用于大多数现有的 Protocol Buffers 定义文件。

仓库地址:https://gitcode.com/gh_mirrors/na/nanopb
github仓:https://github.com/nanopb/nanopb

安装 nanopb

这个很重要。很多人拿到仓库后,苦于找不到protoc工具,不知道如何生成pb.c文件。其实原因就出在这里,没有安装环境。而官方在这里的介绍简单了些。nanopb 的安装可以通过 pip3 和从源码编译两种方式进行。本文推荐使用 pip3 安装,因为它更加简便快捷。

首先,确保你的开发环境中已经安装了 Python3 和 pip3。如果还没有安装,可以通过以下命令安装:

sudo apt-get update
sudo apt-get install python3 python3-pip

接下来,使用 pip3 安装 nanopb 的相关工具。这里我们使用阿里云的镜像源来加速安装:

pip3 install protobuf grpcio-tools -i https://mirrors.aliyun.com/pypi/simple/

安装完成后,你需要确保 nanopb 的生成器工具 protocnanopb_generator 可用。上面安装完依赖后,这两个工具自然可用。可以将/root/test/c/nanopb/generator/加入搭排环境变量里,linux下方便使用protoc。 你可以通过以下命令来生成 .pb.c.pb.h 文件:

../../generator/protoc  --nanopb_out=. simple.proto
# 或者
nanopb_generator  simple.proto

这里 simple.proto 是你的 Protocol Buffers 定义文件。生成器会根据这个文件生成对应的 C 代码文件。

使用 nanopb

要使用 Nanopb 库,您需要执行以下两个步骤:

  1. 使用 protoc 编译您的 .proto 文件以生成适用于 Nanopb 的文件。
  2. 在项目中包含 pb_encode.c、pb_decode.c 和 pb_common.c。

开始学习的最佳方式是研究 “examples/simple” 目录中的示例项目。它包含了一个 Makefile,在大多数 Linux 系统上可以直接工作。然而,对于其他类型的构建系统,可以参考该目录下 README.txt 中的手动步骤。

下面,我们将通过一个简单的例子来展示如何使用 nanopb。
假设我们有一个简单的 Protocol Buffers 定义文件 simple.proto
内容如下:

syntax = "proto2";message SimpleMessage {required int32 lucky_number = 1;
}

这个定义文件中只包含一个消息定义 SimpleMessage,它有一个 int32 类型的字段 lucky_number

接下来,我们将编写 C 代码来处理这个消息。示例代码如下:

#include <stdio.h>
#include <pb_encode.h>
#include <pb_decode.h>
#include "simple.pb.h"int main()
{/* This is the buffer where we will store our message. */uint8_t buffer[128];size_t message_length;bool status;/* Encode our message */{/* Allocate space on the stack to store the message data.** Nanopb generates simple struct definitions for all the messages.* - check out the contents of simple.pb.h!* It is a good idea to always initialize your structures* so that you do not have garbage data from RAM in there.*/SimpleMessage message = SimpleMessage_init_zero;/* Create a stream that will write to our buffer. */pb_ostream_t stream = pb_ostream_from_buffer(buffer, sizeof(buffer));/* Fill in the lucky number */message.lucky_number = 13;/* Now we are ready to encode the message! */status = pb_encode(&stream, SimpleMessage_fields, &message);message_length = stream.bytes_written;/* Then just check for any errors.. */if (!status){printf("Encoding failed: %s\n", PB_GET_ERROR(&stream));return 1;}}/* Now we could transmit the message over network, store it in a file or* wrap it to a pigeon's leg.*//* But because we are lazy, we will just decode it immediately. */{/* Allocate space for the decoded message. */SimpleMessage message = SimpleMessage_init_zero;/* Create a stream that reads from the buffer. */pb_istream_t stream = pb_istream_from_buffer(buffer, message_length);/* Now we are ready to decode the message. */status = pb_decode(&stream, SimpleMessage_fields, &message);/* Check for errors... */if (!status){printf("Decoding failed: %s\n", PB_GET_ERROR(&stream));return 1;}/* Print the data contained in the message. */printf("Your lucky number was %d!\n", (int)message.lucky_number);}return 0;
}

这段代码首先初始化了一个 SimpleMessage 结构体,然后使用 pb_encode 函数将这个结构体编码到一个字节缓冲区中。接着,它又将这个缓冲区中的数据解码回一个 SimpleMessage 结构体,并输出其中的 lucky_number 字段。

Makefile 文件

为了简化编译过程,我们可以使用 Makefile 文件来管理编译规则。以下是示例 Makefile 文件的内容:

# Include the nanopb provided Makefile rules
include ../../extra/nanopb.mk# Compiler flags to enable all warnings & debug info
CFLAGS = -Wall -Werror -g -O0
CFLAGS += -I$(NANOPB_DIR)# C source code files that are required
CSRC  = simple.c                   # The main program
CSRC += simple.pb.c                # The compiled protocol definition
CSRC += $(NANOPB_DIR)/pb_encode.c  # The nanopb encoder
CSRC += $(NANOPB_DIR)/pb_decode.c  # The nanopb decoder
CSRC += $(NANOPB_DIR)/pb_common.c  # The nanopb common parts# Build rule for the main program
simple: $(CSRC)$(CC) $(CFLAGS) -osimple $(CSRC)# Build rule for the protocol
simple.pb.c: simple.proto$(PROTOC) $(PROTOC_OPTS) --nanopb_out=. $<

这个 Makefile 文件首先包含了 nanopb 提供的 Makefile 规则 nanopb.mk。然后,它定义了一些编译器标志 CFLAGS,用于在编译过程中启用所有警告并包含调试信息。CSRC 变量列出了所有需要编译的 C 源代码文件,包括主程序文件 simple.c、编译后的 Protocol Buffers 定义文件 simple.pb.c 以及 nanopb 库的核心文件。

最后,Makefile 文件定义了生成最终可执行文件 simple 的规则,以及从 Protocol Buffers 定义文件 simple.proto 生成 C 源代码文件 simple.pb.c 的规则。

nanopb.mk 文件

nanopb.mk 文件包含了 nanopb 提供的 Makefile 规则,用于生成 .pb.c.pb.h 文件以及定义 nanopb 库的核心文件路径。以下是 nanopb.mk 文件的内容:

# This is an include file for Makefiles. It provides rules for building
# .pb.c and .pb.h files out of .proto, as well the path to nanopb core.# Path to the nanopb root directory
NANOPB_DIR := $(patsubst %/,%,$(dir $(patsubst %/,%,$(dir $(lastword $(MAKEFILE_LIST)))))))# Files for the nanopb core
NANOPB_CORE = $(NANOPB_DIR)/pb_encode.c $(NANOPB_DIR)/pb_decode.c $(NANOPB_DIR)/pb_common.c# Check if we are running on Windows
ifdef windir
WINDOWS = 1
endif
ifdef WINDIR
WINDOWS = 1
endif# Check whether to use binary version of nanopb_generator or the
# system-supplied python interpreter.
ifneq "$(wildcard $(NANOPB_DIR)/generator-bin)" ""# Binary packagePROTOC = $(NANOPB_DIR)/generator-bin/protocPROTOC_OPTS =
else# Source only or git checkoutPROTOC_OPTS =ifdef WINDOWSPROTOC = python $(NANOPB_DIR)/generator/protocelsePROTOC = $(NANOPB_DIR)/generator/protocendif
endif# Rule for building .pb.c and .pb.h
%.pb.c %.pb.h: %.proto %.options$(PROTOC) $(PROTOC_OPTS) --nanopb_out=. $<%.pb.c %.pb.h: %.proto$(PROTOC) $(PROTOC_OPTS) --nanopb_out=. $<

这个文件首先定义了 nanopb 的根目录路径 NANOPB_DIR,然后列出了 nanopb 库的核心文件路径 NANOPB_CORE。接着,它检查当前操作系统是否为 Windows,并根据操作系统的不同来设置 PROTOC 变量,以指向正确的 protoc 生成器工具。最后,它定义了生成 .pb.c.pb.h 文件的规则。

稍复杂的使用示例

接下来做一个稍复杂的使用,proto文件定义了两个message:KeyValue和DeviceConfig。其中,DeviceConfig包含一些基本类型的字段和一个重复的KeyValue字段。生成的nanopb头文件中对于字符串和重复字段,使用了pb_callback_t类型,这意味着这些字段需要通过回调函数来处理,或者在生成时可能没有设置相应的选项来优化为静态数组。

假如有以下proto文件定义:

syntax = "proto2";package example;message KeyValue {required string key = 1;required int32 value = 2;
}message DeviceConfig {required int32 BrdNum = 1;required int32 address = 2;required string type = 3;required int32 priority = 4;required int32 accessTime = 5;required string brdLocation = 6;repeated KeyValue bitMean = 7;
}

编码(序列化)的过程大致是:初始化消息结构体,填充数据,然后调用pb_encode函数。对于回调处理的字段(如字符串和重复字段),需要设置回调函数或者在结构体中正确填充数据。例如,pb_callback_t类型的字段需要用户提供函数来处理数据的编码,或者在结构体中直接设置对应的数据指针和大小。

在提供的DeviceConfig结构体中,type、brdLocation和bitMean都是回调类型。对于字符串字段,通常可以使用pb_callback_t的简单方式,例如直接指定字符串的指针和长度,或者设置一个encode函数。而bitMean是repeated的KeyValue,需要处理为重复字段,可能使用多次回调或者预分配数组。但根据生成的代码,这里可能还是需要使用回调来处理每个条目。

因此在以下代码中,处理这些回调字段是关键。对于编码,需要为每个pb_callback_t字段设置对应的函数,或者直接填充数据。例如,对于字符串字段,可能可以使用pb_ostream_from_buffer来创建一个输出流,然后使用pb_encode函数来编码数据。

以下为编码和解码的使用:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pb_encode.h>
#include <pb_decode.h>
#include "simple.pb.h"// 编码回调 ---------------------------------------------------
bool encode_string(pb_ostream_t *stream, const pb_field_t *field, void *const *arg) {const char *str = *(const char **)*arg;if (!pb_encode_tag_for_field(stream, field))return false;return pb_encode_string(stream, (uint8_t*)str, strlen(str));
}bool encode_bitMean(pb_ostream_t *stream, const pb_field_t *field, void *const *arg) {const example_KeyValue *items = (const example_KeyValue *)*arg;for (size_t i = 0; i < 2; i++) {if (!pb_encode_tag_for_field(stream, field))return false;if (!pb_encode_submessage(stream, example_KeyValue_fields, &items[i]))return false;}return true;
}// 解码回调 ---------------------------------------------------
typedef struct {char *key;int32_t value;
} KeyValueData;bool decode_string(pb_istream_t *stream, const pb_field_t *field, void **arg) {char **strptr = (char **)*arg;size_t len = stream->bytes_left;*strptr = malloc(len + 1);if (!pb_read(stream, (uint8_t*)*strptr, len)) {free(*strptr);return false;}(*strptr)[len] = '\0';return true;
}bool decode_bitMean(pb_istream_t *stream, const pb_field_t *field, void **arg) {KeyValueData **items = (KeyValueData **)*arg;size_t count = (*items == NULL) ? 0 : (*items)[0].value;// 扩展数组空间(使用[0]存储计数)KeyValueData *new_items = realloc(*items, (count + 1 + 1) * sizeof(KeyValueData));if (!new_items) return false;*items = new_items;(*items)[0].value = count + 1; // 更新计数KeyValueData *item = &(*items)[count + 1]; // 数据从[1]开始item->key = NULL;example_KeyValue kv = example_KeyValue_init_zero;kv.key.arg = &item->key;kv.key.funcs.decode = decode_string;if (!pb_decode(stream, example_KeyValue_fields, &kv)) {return false;}item->value = kv.value;return true;
}int main() {/**************** 编码阶段 ****************/example_DeviceConfig encode_config = example_DeviceConfig_init_default;uint8_t buffer[256];// 配置编码参数encode_config.BrdNum = 123;encode_config.address = 456;encode_config.priority = 1;encode_config.accessTime = 999;const char *type_str = "temperature";encode_config.type.arg = &type_str;encode_config.type.funcs.encode = encode_string;const char *loc_str = "Room 101";encode_config.brdLocation.arg = &loc_str;encode_config.brdLocation.funcs.encode = encode_string;example_KeyValue bit_means[2] = {{.key.arg = (void*)&(const char*[]){"status"},   .key.funcs.encode = encode_string, .value = 100},{.key.arg = (void*)&(const char*[]){"error"},   .key.funcs.encode = encode_string, .value = 0}};encode_config.bitMean.arg = bit_means;encode_config.bitMean.funcs.encode = encode_bitMean;pb_ostream_t ostream = pb_ostream_from_buffer(buffer, sizeof(buffer));if (!pb_encode(&ostream, example_DeviceConfig_fields, &encode_config)) {fprintf(stderr, "Encode error: %s\n", PB_GET_ERROR(&ostream));return 1;}/**************** 解码阶段 ****************/example_DeviceConfig decode_config = example_DeviceConfig_init_zero;KeyValueData *bit_means_decoded = NULL;char *decoded_type = NULL;char *decoded_loc = NULL;// 配置解码回调decode_config.type.arg = &decoded_type;decode_config.type.funcs.decode = decode_string;decode_config.brdLocation.arg = &decoded_loc;decode_config.brdLocation.funcs.decode = decode_string;decode_config.bitMean.arg = &bit_means_decoded;decode_config.bitMean.funcs.decode = decode_bitMean;pb_istream_t istream = pb_istream_from_buffer(buffer, ostream.bytes_written); // 关键修正点if (!pb_decode(&istream, example_DeviceConfig_fields, &decode_config)) {fprintf(stderr, "Decode error: %s\n", PB_GET_ERROR(&istream));free(decoded_type);free(decoded_loc);if (bit_means_decoded) {for (size_t i = 1; i <= bit_means_decoded[0].value; i++)free(bit_means_decoded[i].key);free(bit_means_decoded);}return 1;}/**************** 输出结果 ****************/printf("Decoded config:\n""Type: %s\n""Location: %s\n""BitMeans count: %d\n",decoded_type, decoded_loc, bit_means_decoded ? bit_means_decoded[0].value : 0);if (bit_means_decoded) {for (int i = 1; i <= bit_means_decoded[0].value; i++) {printf("  [%d] %s = %d\n", i, bit_means_decoded[i].key, bit_means_decoded[i].value);}}/**************** 清理资源 ****************/free(decoded_type);free(decoded_loc);if (bit_means_decoded) {for (int i = 1; i <= bit_means_decoded[0].value; i++) {free(bit_means_decoded[i].key);}free(bit_means_decoded);}return 0;
}

优化使用示例

上述示例,使用了回调和动态内存分配,稍显复杂,改为以下方式则代码更简洁、内存管理更安全。
以下是通过添加nanopb选项优化proto定义后的使用示例:

1. 修改后的proto文件

syntax = "proto2";
import "nanopb.proto";  // 需要nanopb库中的选项定义package example;message KeyValue {required string key = 1 [(nanopb).max_size = 64];  // 限制key最大64字节required int32 value = 2;
}message DeviceConfig {required int32 BrdNum = 1;required int32 address = 2;required string type = 3 [(nanopb).max_size = 64];    // 限制type长度required int32 priority = 4;required int32 accessTime = 5;required string brdLocation = 6 [(nanopb).max_size = 128]; // 限制位置字符串repeated KeyValue bitMean = 7 [(nanopb).max_count = 10];   // 最多10个元素
}

2. 生成新的头文件

使用nanopb生成器命令:

protoc --nanopb_out=. simple.proto

3. 优化后的使用示例

#include "simple.pb.h"
#include <pb_encode.h>
#include <pb_decode.h>
#include <stdio.h>
#include <string.h>int main() {/**************** 编码示例 ****************/example_DeviceConfig config = example_DeviceConfig_init_default;// 直接赋值字符串(不再需要回调)strncpy(config.type, "temperature", sizeof(config.type));strncpy(config.brdLocation, "Room 101", sizeof(config.brdLocation));// 设置基本数值字段config.BrdNum = 123;config.address = 456;config.priority = 1;config.accessTime = 999;// 填充重复字段config.bitMean_count = 2;  // 自动生成的数组长度字段strncpy(config.bitMean[0].key, "status", sizeof(config.bitMean[0].key));config.bitMean[0].value = 100;strncpy(config.bitMean[1].key, "error", sizeof(config.bitMean[1].key));config.bitMean[1].value = 0;// 编码缓冲区uint8_t buffer[256];pb_ostream_t ostream = pb_ostream_from_buffer(buffer, sizeof(buffer));if (!pb_encode(&ostream, example_DeviceConfig_fields, &config)) {fprintf(stderr, "Encode failed: %s\n", PB_GET_ERROR(&ostream));return 1;}/**************** 解码示例 ****************/example_DeviceConfig decoded = example_DeviceConfig_init_default;pb_istream_t istream = pb_istream_from_buffer(buffer, ostream.bytes_written);if (!pb_decode(&istream, example_DeviceConfig_fields, &decoded)) {fprintf(stderr, "Decode failed: %s\n", PB_GET_ERROR(&istream));return 1;}/**************** 输出结果 ****************/printf("Decoded config:\n""Type: %s\n""Location: %s\n""BitMeans count: %d\n",decoded.type,decoded.brdLocation,decoded.bitMean_count);for (int i = 0; i < decoded.bitMean_count; i++) {printf("  [%d] %s = %d\n", i, decoded.bitMean[i].key, decoded.bitMean[i].value);}return 0;
}

优化后代码的主要变化

  1. 数据结构变化

    // 原始回调方式
    pb_callback_t type;// 优化后静态分配
    char type[64]; 
    size_t type_size;
    
  2. 重复字段处理

    // 原始方式
    pb_callback_t bitMean;// 优化后静态数组
    example_KeyValue bitMean[10];
    size_t bitMean_count;
    
  3. 字符串处理

    // 之前需要回调
    config.type.funcs.encode = encode_string;// 现在直接操作
    strncpy(config.type, "text", sizeof(config.type));
    

关键优势说明

  1. 内存管理简化

    • 所有字符串字段变为固定长度char数组
    • 重复字段变为固定大小的数组+count计数器
    • 不再需要手动malloc/free
  2. 性能提升

    • 消除回调函数开销
    • 数据内存连续,提高缓存命中率
  3. 代码可读性增强

    • 字段访问方式与常规结构体一致
    • 减少约60%的样板代码

注意事项

  1. 字段长度限制

    • 超出max_size的字符串会被截断
    • 超过max_count的数组元素会被丢弃
  2. 默认值初始化

    example_DeviceConfig_init_default  // 会清零所有字段
    example_DeviceConfig_init_zero     // 同上,两者等效
    
  3. 字符串处理建议

    // 使用strncpy防止溢出
    strncpy(config.type, src, sizeof(config.type));
    config.type[sizeof(config.type)-1] = '\0'; // 确保终止符
    
  4. 协议兼容性

    • 修改后的proto仍然与标准protobuf兼容
    • nanopb选项仅影响生成代码的实现方式

推荐使用场景

  • 嵌入式系统(内存受限环境)
  • 需要确定性内存分配的场景
  • 对性能要求较高的实时系统
  • 希望简化代码逻辑的项目

通过这种优化方式,代码复杂度显著降低,同时保持了协议的高效性。建议根据实际字段的预期最大长度来设置合理的max_sizemax_count值,在内存使用和灵活性之间取得平衡。
在这里插入图片描述

总结

通过本文,我们了解了什么是 nanopb,及其在嵌入式系统中的应用场景。我们还学习了如何安装和使用 nanopb,包括编写 Protocol Buffers 定义文件、C 代码文件以及 Makefile 文件。通过这些步骤,你可以轻松地在你的项目中集成 nanopb,以便更高效地进行消息的序列化和反序列化。

相关文章:

Protocol Buffers在MCU上的nanopb介绍及使用详解

在嵌入式系统和资源受限的环境中&#xff0c;传统的Protocol Buffers 可能显得过于庞大。因此&#xff0c;nanopb 应运而生&#xff0c;它是一个轻量级的 Protocol Buffers 生成器&#xff0c;专为嵌入式系统设计c语言设计。本文将介绍如何安装和使用 nanopb&#xff0c;以及通…...

【Elasticsearch】自定义内置的索引生命周期管理(ILM)策略。

以下是对 Elasticsearch 官方教程《Customize built-in ILM policies》的详细解读&#xff0c;结合原文内容&#xff0c;帮助您更好地理解如何自定义内置的索引生命周期管理&#xff08;ILM&#xff09;策略。 --- Elasticsearch 教程&#xff1a;自定义内置 ILM 策略 1.背景…...

测试工程师Ai应用实战指南简例prompt

以下是一个真实具体的案例,展示测试工程师如何在不同阶段结合DeepSeek提升效率。案例基于电商平台"订单超时自动关闭"功能测试: 案例背景 项目名称:电商平台订单系统V2.3 测试目标:验证"用户下单后30分钟未支付,订单自动关闭并释放库存"功能 技术栈:…...

(十 二)趣学设计模式 之 享元模式!

目录 一、 啥是享元模式&#xff1f;二、 为什么要用享元模式&#xff1f;三、 享元模式的实现方式四、 享元模式的优缺点五、 享元模式的应用场景六、 总结 &#x1f31f;我的其他文章也讲解的比较有趣&#x1f601;&#xff0c;如果喜欢博主的讲解方式&#xff0c;可以多多支…...

Trae:国内首款AI原生IDE,编程效率大提升

今年一月&#xff0c;在新闻上看到字节跳动面向海外市场推出了一款名为Trae的AI集成开发环境&#xff08;IDE&#xff09;。起初&#xff0c;我并未给予过多关注&#xff0c;因为市面上已有不少IDE集成了AI插件&#xff0c;功能也非常全面&#xff0c;而字节跳动自家的MarsCode…...

深入解析 Vue Router 的 beforeEach:功能、用法与实践指南

什么是 beforeEach&#xff1f;基本语法与参数解析next() 的 4 种调用方式常见使用场景与代码示例动态路由加载的实践技巧常见陷阱与避坑指南总结 1. 什么是 beforeEach&#xff1f; beforeEach 是 Vue Router 提供的 全局前置守卫&#xff08;Global Before Guards&#xff0…...

RocketMQ定时/延时消息实现机制

RocketMQ 的延迟消息是其核心特性之一&#xff0c;允许消息在指定延迟时间后才被消费者消费。 定时消息生命周期 一、延迟消息的核心机制 RocketMQ&#xff08;5.0之前&#xff09; 不支持任意时间精度的延迟&#xff0c;而是通过预定义的 延迟级别&#xff08;Delay Level&a…...

基于SpringBoot的校园二手交易平台(源码+论文+部署教程)

运行环境 校园二手交易平台运行环境如下&#xff1a; • 前端&#xff1a;Vue • 后端&#xff1a;Java • IDE工具&#xff1a;IntelliJ IDEA&#xff08;可自行更换&#xff09; • 技术栈&#xff1a;SpringBoot Vue MySQL 主要功能 校园二手交易平台主要包含前台和…...

如何快速写出国内外现状的内容并且引用对应的参考文献(近三年的论文)

解决方法: 1.首先从知网或者谷歌学术中搜索相关关键字的论文根据时间排列(最新的在前面)。然后多选选中自己想要引用的论文(一般近三年的论文要占2/3),然后导出参考文献 [19] Lu L, Jin P, Karniadakis G E. DeepONet: Learning nonlinear operators for identifying dif…...

SQL的select语句完整的执行顺序

SQL的SELECT语句的执行顺序可以用"做菜流程"来类比理解。虽然我们写SQL时按SELECT…FROM…WHERE…顺序写&#xff0c;但数据库执行顺序完全不同。以下是通俗易懂的讲解&#xff08;附流程图和示例&#xff09;&#xff1a; &#x1f527; 执行顺序流程图&#xff1a…...

开源操作系统纷争:CentOS停服后的新战场

开源操作系统纷争&#xff1a;CentOS停服后的新战场 引言 2020年12月&#xff0c;Red Hat宣布将停止维护CentOS Linux&#xff0c;转而专注于CentOS Stream。这一决策在开源社区掀起轩然大波&#xff0c;尤其是那些依赖CentOS作为生产环境操作系统的企业和开发者们&#xff0…...

【知识】torchrun 与 torch.multiprocessing.spawn 的对比

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你&#xff0c;欢迎[点赞、收藏、关注]哦~ 来自ChatGPT、DeepSeek 有点干&#xff0c;可仅做了解。 torchrun 和 torch.multiprocessing.spawn 都是在 PyTorch 中用于并行化和分布式训练的工具&a…...

利用 LangChain 和一个大语言模型(LLM)构建一个链条,自动从用户输入的问题中提取相关的 SQL 表信息,再生成对应的 SQL 查询

示例代码&#xff1a; from langchain_core.runnables import RunnablePassthrough from langchain.chains import create_sql_query_chain from operator import itemgetter from langchain.chains.openai_tools import create_extraction_chain_pydantic# 系统消息&#xff…...

力扣hot 100之矩阵四题解法总结

本期总结hot100 中二维矩阵的题&#xff0c;时空复杂度就不分析了 1.矩阵置零 原地标记&#xff0c;用第一行和第一列作为当前行列是否为0的标记&#xff0c;同时用两个标签分别记录0行、0列的标记空间中原本是否有0 class Solution:def setZeroes(self, matrix: List[List[…...

使用python运行网格世界环境下 TD算法

一、概述 本代码实现了在网格世界环境中使用 TD (0)&#xff08;Temporal Difference (0)&#xff09;算法进行策略评估&#xff0c;并对评估结果进行可视化展示。通过模拟智能体在网格世界中的移动&#xff0c;不断更新状态值函数&#xff0c;最终得到每个状态的价值估计。 二…...

在Linux上使用APT安装Sniffnet的详细步骤

一、引言 Sniffnet 是一款开源的网络流量监控工具&#xff0c;适用于多种Linux发行版。如果你的Linux系统使用APT&#xff08;Advanced Package Tool&#xff09;作为包管理器&#xff0c;以下是如何通过APT安装Sniffnet的详细步骤。 二、系统要求 在开始安装之前&#xff0…...

zookeeper-docker版

Zookeeper-docker版 1 zookeeper概述 1.1 什么是zookeeper Zookeeper是一个分布式的、高性能的、开源的分布式系统的协调&#xff08;Coordination&#xff09;服务&#xff0c;它是一个为分布式应用提供一致性服务的软件。 1.2 zookeeper应用场景 zookeeper是一个经典的分…...

StableDiffusion本地部署 3 整合包猜想

本地部署和整合包制作猜测 文章目录 本地部署和整合包制作猜测官方部署第一种第二种 StabilityMatrix下载整合包制作流程猜测 写了这么多python打包和本地部署的文章&#xff0c;目的是向做一个小整合包出来&#xff0c;不要求有图形界面&#xff0c;只是希望一键就能运行。 但…...

数据结构(初阶)(七)----树和二叉树(前中后序遍历)

实现链式结构的二叉树 实现链式结构的二叉树遍历前序遍历中序遍历后序遍历 节点个数叶子节点个数⼆叉树第k层结点个数⼆叉树的深度/⾼度查找值为X的节点二叉树的销毁 层序遍历判断二叉树是否为完全二叉树 ⽤链表来表⽰⼀棵⼆叉树&#xff0c;即⽤链来指⽰元素的逻辑关系。 通常…...

SOME/IP 教程知识点总结

总结关于SOME/IP的教程,首先通读整个文件,理解各个部分的内容。看起来这个教程从介绍开始,讲到了为什么在车辆中使用以太网,然后详细讲解了SOME/IP的概念、序列化、消息传递、服务发现(SOME/IP-SD)、发布/订阅机制以及支持情况。 首先,我需要确认每个章节的主要知识点。…...

安装 Windows Docker Desktop - WSL问题

一、关联文章: 1、Docker Desktop 安装使用教程 2、家庭版 Windows 安装 Docker 没有 Hyper-V 问题 3、打开 Windows Docker Desktop 出现 Docker Engine Stopped 问题 二、问题解析 打开 Docker Desktop 出现问题,如下: Docker Desktop - WSL update failed An error o…...

科技赋能筑未来 中建海龙MiC建筑技术打造保障房建设新标杆

近日&#xff0c;深圳梅林路6号保障房项目顺利封顶&#xff0c;标志着国内装配式建筑领域又一里程碑式突破。中建海龙科技有限公司&#xff08;以下简称“中建海龙”&#xff09;以模块化集成建筑&#xff08;MiC&#xff09;技术为核心&#xff0c;通过科技创新与工业化建造深…...

json介绍、python数据和json数据的相互转换

目录 一 json介绍 json是什么&#xff1f; 用处 Json 和 XML 对比 各语言对Json的支持情况 Json规范详解 二 python数据和json数据的相互转换 dumps() : 转换成json loads(): 转换成python数据 总结 一 json介绍 json是什么&#xff1f; 实质上是一条字符串 是一种…...

关于学习一门新的编程语言的策略

实践 实践 实践 那么如何实践呢 &#xff0c;very easy&#xff0c;测验 测验 测验 测验 测验 测验 测验 测验 测验 测验 测验 测验 测验 测验 测验 测验 测验 测验 测验 测验 测验 测验 测验 测验 测验 测验 测验 测验 测验 测验 测验 测验 测验 测验 测验 测验 测验 测验 测…...

Rust 是什么

Rust 是什么 Rust 是一种由 Mozilla 开发的系统级编程语言,它于 2010 年首次亮相,在 2015 年发布 1.0 版本,此后迅速发展并受到广泛关注。 内存安全:Rust 最大的亮点之一是它在编译阶段就能够避免常见的内存错误,如空指针引用、数据竞争和内存泄漏等。它通过所有权(Owne…...

C#开发——时间间隔类TimSpan

TimeSpan 是 C# 中的一个结构&#xff08; struct &#xff09;&#xff0c;用于表示时间间隔或持续时间。它位于 System 命名空间中&#xff0c;是处理时间相关操作时非常重要的工具&#xff0c;尤其是在计算两个日期或时间之间的差值、表示时间段或执行时间相关的运算…...

计算机毕设JAVA——某高校宿舍管理系统(基于SpringBoot+Vue前后端分离的项目)

文章目录 概要项目演示图片系统架构技术运行环境系统功能简介 概要 网络上许多计算机毕设项目开发前端界面设计复杂、不美观&#xff0c;而且功能结构十分单一&#xff0c;存在很多雷同的项目&#xff1a;不同的项目基本上就是套用固定模板&#xff0c;换个颜色、改个文字&…...

[随手笔记]C#保留小数防止四舍五入有效解决办法

private decimal 截断小数(decimal 原小数值, int 保留小数个数) { string 原小数转字符串值 原小数值.ToString(); try { if (原小数转字符串值.Contains(".")) { int 原小数总长度 原小数转字符串值.Length; …...

C++ 二叉树代码

二叉树代码&#xff0c;见下 #include <iostream> using namespace std;template<typename T> struct TreeNode{T val;TreeNode *left;TreeNode *right;TreeNode():val(0), left(NULL), right(NULL)TreeNode(T x):val(x), left(NULL), right(NULL){} };template&l…...

Spring Boot 测试:单元、集成与契约测试全解析

一、Spring Boot 分层测试策略 Spring Boot 应用采用经典的分层架构&#xff0c;不同层级的功能模块对应不同的测试策略&#xff0c;以确保代码质量和系统稳定性。 Spring Boot 分层架构&#xff1a; Spring Boot分层架构 A[客户端] -->|HTTP 请求| B[Controller 层] …...