nanopb(一)——从零到一:在资源受限的嵌入式世界构建高效数据通道
1. 当KB级内存遇上结构化数据为什么是nanopb如果你和我一样常年混迹在嵌入式开发的“底层世界”肯定对下面这个场景不陌生你手上有一个基于某款ARM Cortex-M0内核的微控制器主频几十兆赫兹RAM可能只有可怜的8KB甚至4KB。这时候产品经理跑过来说我们需要这个设备每隔5秒上报一次数据包里面要包含设备ID、时间戳、一组传感器读数温度、湿度、压力最好还能带点状态标志位和可选的调试信息。你的第一反应可能是用JSON——毕竟它太通用了在电脑上调试也方便。但当你试着把那个cJSON库塞进项目跑一遍序列化看着瞬间被吃掉的几百字节甚至上K的堆栈和内存碎片心就凉了半截。在资源以KB计的世界里JSON的文本格式和动态内存分配很多时候就像穿着一身豪华礼服去跑马拉松既笨重又低效。这就是nanopb要解决的痛点。它不是什么全新的协议而是Google Protocol BuffersProtobuf在嵌入式领域的“瘦身”版本。你可以把它理解为一个专门为“小个子”设备定制的、高效的“数据打包和解包”工具。Protobuf本身是一种二进制序列化格式它通过一个预先定义好的.proto文件来描述数据结构然后生成对应的代码。数据按照这个描述被编码成紧凑的二进制流体积小解析速度快。而nanopb则把这个过程做到了极致它用纯C语言实现几乎没有运行时依赖内存占用可以精确到字节级别来控制甚至支持你一边从UART接收数据一边就进行解码流式处理完全不需要等整个数据包到齐。我最初接触nanopb是在一个LoRa传感器节点的项目上。节点用电池供电需要通过LoRa无线模块把数据发送到几公里外的网关。LoRa的传输速率很低每秒也就几百个字节每一次发射都极其耗电。我们必须让每一个数据包都“斤斤计较”。用JSON的话光是字段名temperature、humidity这些字符串就占去大量空间。换成nanopb后同样的数据内容包大小直接缩小了60%以上传输时间变短功耗也降下来了。更关键的是在MCU端编码和解码过程都是在静态分配的缓冲区里完成的内存使用是可预测的再也没有令人头疼的malloc/free带来的内存泄漏或碎片化风险。这种确定性和高效性正是资源受限嵌入式系统的生命线。所以简单来说nanopb就是为那些“内存不够用、算力不够强、带宽不够宽”的嵌入式设备提供的一套结构化数据交换的“标准答案”。它让你能用一种高效、可靠且跨平台的方式在单片机与云端、单片机与单片机之间“说话”而不用担心资源开销把你压垮。2. 从.proto文件到C代码定义你的数据“合同”使用nanopb的第一步不是写C代码而是定义一份数据“合同”。这份合同就是.proto文件。它是一种与语言无关的接口定义语言IDL用来精确描述你的数据结构。这有点像你先画好一张图纸然后nanopb根据这张图纸生成对应的C语言“模具”结构体和函数。2.1 编写你的第一个.proto文件让我们从一个实际场景开始。假设我们有一个环境监测传感器需要上报数据。我们创建一个sensor_data.proto文件。syntax proto2; package my_project; message SensorData { required uint32 device_id 1; required uint64 timestamp_ms 2; required float temperature_c 3; required float humidity_percent 4; optional float pressure_hpa 5; enum Status { OK 0; ERROR_SENSOR 1; ERROR_COMM 2; } required Status status 6 [default OK]; repeated int32 adc_readings 7 [max_count 4]; }我来解释一下这里面几个关键点这直接关系到生成代码的行为和内存占用syntax proto2nanopb目前主要支持proto2语法它足够我们使用。proto3更简洁但nanopb对其支持是实验性的在嵌入式环境里proto2的明确性如required/optional反而更让人安心。字段类型uint32,float,int32这些映射到C语言就是uint32_t,float,int32_t。选择合适大小的类型很重要在单片机上能用uint16就别用uint32能省则省。标签Tag数字如 1这是Protobuf编码的核心它不是一个值而是字段在二进制流中的唯一标识。编码后字段名如temperature_c本身不占用空间只有标签数字和值会被编码。所以字段名再长也没关系这也就是为什么Protobuf比JSON省空间的关键。requiredvsoptionalvsrepeatedrequired必须存在的字段。编码/解码时会严格检查。除非万不得已我个人现在更倾向于用optional因为向后兼容性更好。如果一个required字段在未来版本想取消会非常麻烦。optional可选字段。在生成的C结构体里它会对应一个has_字段比如has_pressure_hpa来表示该字段是否存在。这给了你很大的灵活性。repeated数组或列表。这是内存管理的重点注意我后面加的[max_count 4]这是一个nanopb选项不是标准Protobuf语法。它告诉nanopb编译器“这个数组最多有4个元素”。没有这个选项nanopb会默认使用动态内存分配malloc这在很多嵌入式项目里是禁止的。通过max_count我们可以让nanopb生成一个固定大小的数组比如int32_t adc_readings[4]从而实现静态内存分配。枚举Enum用枚举来表示状态码、模式等非常合适编码后只是一个整数既直观又高效。2.2 生成C代码让合同变成模具定义好合同后我们需要“编译器”把它变成C代码。这里需要两个工具Google官方的protoc编译器和nanopb提供的插件protoc-gen-nanopb。第一步安装工具。在Linux或macOS上你可以用包管理器安装protobuf-compiler。nanopb的生成器插件protoc-gen-nanopb通常就是Python脚本nanopb_generator.py。最简单的方法是从nanopb的GitHub仓库直接下载整个发布包里面工具和库都齐了。第二步生成代码。假设你把nanopb的目录放在了/path/to/nanopb你的.proto文件在当前目录。# 这条命令做了两件事 # 1. 调用protoc编译器。 # 2. 通过--plugin指定使用nanopb的生成器插件。 # 3. --nanopb_out. 表示将生成的nanopb专用C文件输出到当前目录。 protoc --pluginprotoc-gen-nanopb/path/to/nanopb/generator/protoc-gen-nanopb --nanopb_out. sensor_data.proto执行成功后你会得到两个文件sensor_data.pb.c包含消息的编码/解码函数实现。sensor_data.pb.h包含对应的C结构体定义和函数声明。打开.pb.h文件看一眼你会发现之前定义的message SensorData变成了一个C结构体/* 自动生成的结构体 */ typedef struct _my_project_SensorData { uint32_t device_id; uint64_t timestamp_ms; float temperature_c; float humidity_percent; bool has_pressure_hpa; float pressure_hpa; my_project_SensorData_Status status; pb_size_t adc_readings_count; int32_t adc_readings[4]; // 看固定大小的数组因为我们在.proto里指定了max_count4 } my_project_SensorData;这个结构体就是你程序中用来操作数据的“容器”。所有的required和optional字段都直接内联在其中而repeated字段因为指定了max_count也变成了内联数组。这意味着整个消息所需的内存在编译期就确定了就是sizeof(my_project_SensorData)。这种确定性对嵌入式系统来说是无比珍贵的。3. 在MCU上集成nanopb极简主义集成指南生成代码只是拿到了“模具”接下来要把nanopb这个“工厂”本身搬到你的MCU项目里。好消息是这个过程简单得超乎想象。3.1 获取与包含库文件nanopb的核心库文件非常精简通常只需要以下文件pb.h/pb.c核心编码解码逻辑。pb_common.h/pb_common.c公共函数。pb_encode.h/pb_encode.c编码序列化实现。pb_decode.h/pb_decode.c解码反序列化实现。你可以直接将这些文件复制到你的项目源码树中。我更推荐的做法是将nanopb作为一个“外部库”的子模块比如用git submodule引入这样更新方便。在你的项目编译配置如Makefile, CMakeLists.txt中把pb.c,pb_encode.c,pb_decode.c以及你生成的sensor_data.pb.c加入编译源文件列表并确保头文件路径包含nanopb的目录和生成文件的目录。3.2 内存管理静态分配是王道在桌面环境动态内存分配司空见惯。但在只有几KB RAM的MCU上动态分配malloc往往是系统不稳定、内存碎片化的罪魁祸首。nanopb的设计哲学与嵌入式开发完美契合鼓励静态内存分配。如何做到回顾我们生成的结构体my_project_SensorData。它所有的字段除了指针都是内联的。这意味着你可以在栈上或者静态存储区分配它// 方式一在栈上分配适合临时使用函数退出即释放 void process_sensor_data() { my_project_SensorData message my_project_SensorData_init_zero; // 使用初始化宏 // ... 填充message ... } // 方式二作为全局变量或静态变量生命周期长 static my_project_SensorData g_sensor_msg;对于编码输出缓冲区同样使用静态数组uint8_t encode_buffer[128]; // 根据你消息的最大估算大小来定义这里有个关键技巧如何确定缓冲区大小你可以先用一个足够大的缓冲区测试然后通过打印stream.bytes_written来获取实际编码后的字节数。更稳妥的方法是使用nanopb提供的pb_get_encoded_size函数在编码前来计算所需大小。但在资源极端受限时我通常根据.proto定义手动估算一个保守值并加上一些余量然后使用静态数组。这避免了任何运行时的大小计算开销。3.3 基础编码解码一个完整的例子现在让我们把数据装进“容器”编码成二进制流再解码读出来。假设我们要通过UART发送这个传感器数据。#include sensor_data.pb.h #include pb_encode.h #include pb_decode.h #include uart.h // 假设你的UART驱动头文件 bool send_sensor_data_over_uart(void) { // 1. 初始化消息结构体 my_project_SensorData message my_project_SensorData_init_zero; // 2. 填充数据 message.device_id 0x12345678; message.timestamp_ms system_get_tick_count(); message.temperature_c 25.6f; message.humidity_percent 60.5f; message.has_pressure_hpa true; // 记得设置has_标志 message.pressure_hpa 1013.25f; message.status my_project_SensorData_Status_OK; message.adc_readings_count 3; // 实际使用的数组元素个数 message.adc_readings[0] 1024; message.adc_readings[1] 2048; message.adc_readings[2] 3072; // 3. 准备编码缓冲区和输出流 uint8_t buffer[MY_MAX_MESSAGE_SIZE]; pb_ostream_t stream pb_ostream_from_buffer(buffer, sizeof(buffer)); // 4. 执行编码 if (!pb_encode(stream, my_project_SensorData_fields, message)) { // 编码失败处理错误如缓冲区不足 uart_send_string(Encode failed!\n); return false; } // 5. 通过UART发送编码后的数据 uart_send_buffer(buffer, stream.bytes_written); return true; } // 在接收端解码 bool receive_and_decode_sensor_data(uint8_t* data, size_t length) { my_project_SensorData decoded_message my_project_SensorData_init_zero; pb_istream_t stream pb_istream_from_buffer(data, length); if (!pb_decode(stream, my_project_SensorData_fields, decoded_message)) { // 解码失败数据损坏、字段不匹配等 return false; } // 使用解码后的数据 if (decoded_message.has_pressure_hpa) { printf(Pressure: %.2f hPa\n, decoded_message.pressure_hpa); } printf(ADC readings count: %d\n, decoded_message.adc_readings_count); // ... 其他处理 return true; }这个过程清晰明了初始化、填充、编码、发送接收、解码、使用。pb_encode和pb_decode是核心函数它们接收一个流对象和消息描述符my_project_SensorData_fields这也是自动生成的完成所有繁重的工作。错误处理至关重要特别是在通信中数据可能损坏必须检查编码解码的返回值。4. 与通信栈适配让数据流动起来nanopb只负责把数据打包成紧凑的二进制包怎么把这个包送出去是UART、I2C、SPI、LoRa模块还是TCP/IP套接字的事情。这里的关键是将nanopb与你的通信驱动无缝连接。4.1 适配字节流接口UART/SPI/I2C对于UART这类字节流设备上面的例子已经展示了最直接的方式编码到一个内存缓冲区然后一次性调用发送函数将整个缓冲区发出去。这是最简单、最常用的模式。但在某些低功耗或流控场景下你可能希望“边编码边发送”或者“边接收边解码”以节省内存不需要一个完整的大缓冲区。nanopb的流式处理能力就在这里大放异彩。你需要实现一个自定义的pb_ostream_t或pb_istream_t。例如实现一个直接输出到UART的pb_ostream_tbool uart_write_callback(pb_ostream_t *stream, const uint8_t *buf, size_t count) { // 忽略stream参数或者用它传递UART句柄 UART_HandleTypeDef *huart (UART_HandleTypeDef*)stream-state; if (HAL_UART_Transmit(huart, buf, count, 1000) HAL_OK) { return true; } return false; // 写出错 } pb_ostream_t stream_to_uart { .callback uart_write_callback, .state huart1, // 把UART句柄作为状态传递进去 .max_size SIZE_MAX, // 理论上无限大 .bytes_written 0 }; // 现在可以直接编码到UART无需中间缓冲区 pb_encode(stream_to_uart, my_project_SensorData_fields, message);接收端类似你可以实现一个从UART读取数据的pb_istream_t回调函数在回调函数里阻塞或非阻塞地读取字节pb_decode会按需调用这个回调来获取数据。这种方式允许你解码一个理论上无限大的消息而只用很小的接收缓冲区。4.2 适配包式通信LoRa、TCP/IP包对于LoRa模块或TCP/IP比如lwIP裸套接字数据通常是以“包”的形式收发的。这时你需要解决两个问题包边界和长度指示。Protobuf编码本身没有长度信息也没有起始/结束标志。常见的做法有两种定长包头在nanopb编码的数据包前添加一个固定格式的包头。我最常用的是一个简单的2字节长度字段小端序。uint8_t tx_packet[2 MY_MAX_MESSAGE_SIZE]; // 2字节长度 数据 uint16_t payload_len stream.bytes_written; tx_packet[0] payload_len 0xFF; tx_packet[1] (payload_len 8) 0xFF; memcpy(tx_packet[2], buffer, payload_len); // 然后发送 tx_packet 长度为 2 payload_len接收方先读取2字节得到长度N再精确读取后续N字节这就是一个完整的nanopb消息包。这种方法简单可靠额外开销极小。使用PB_WITH_PACKED_STRUCT高级选项nanopb有一个编译选项PB_FIELD_16BIT等可以影响编码格式。但对于包边界还是推荐显式添加长度头因为这样不依赖于nanopb的内部细节更通用也方便调试用串口工具抓包时一眼就能看出包长。在LoRa项目中我就是采用“2字节长度头 nanopb消息体”的格式。网关收到后先解长度再解码nanopb非常稳定。对于TCP流同样需要在应用层定义这样的简单协议因为TCP是流无边界。5. 性能与内存的极致权衡nanopb配置实战nanopb之所以强大在于它提供了丰富的编译时配置选项让你能根据手头芯片的资源情况进行精细的裁剪。配置文件通常是pb.h开头的宏定义或者一个独立的pb_syshdr.h文件。5.1 关键配置选项解析以下是我在KB级RAM的Cortex-M0项目中最常调整的几个选项PB_BUFFER_ONLY这是最重要的优化之一。当你确定所有消息都使用静态缓冲区即repeated和string字段都用了max_size等选项指定了最大长度可以定义PB_BUFFER_ONLY。这会让编码解码过程完全避免调用malloc即使在处理字符串和数组时也使用栈或全局缓冲区。强烈建议在无操作系统的嵌入式环境中开启此选项。PB_FIELD_32BIT,PB_FIELD_16BIT,PB_FIELD_8BIT这组宏控制字段标签和数组计数的存储类型。默认可能是32位。如果你的.proto文件中最大字段标签号很小比如小于256消息结构也很简单可以定义为PB_FIELD_16BIT甚至PB_FIELD_8BIT。这能减少每个消息结构体几到十几个字节的内存占用。对于有大量小消息的场景积少成多很可观。PB_MAX_REQUIRED_FIELDS定义消息中required字段的最大数量。nanopb内部会使用这个值来分配一些状态数组。如果你消息里required字段不多把它设小点能省点内存。PB_NO_ERRMSG禁用内置的错误字符串。nanopb在出错时会返回错误信息的字符串常量。关闭这个功能可以把这些字符串常量从ROM里去掉节省Flash空间。代价是错误信息变成了数字代码需要查手册。PB_ENCODE_ARRAYS_UNPACKED/PB_DECODE_ARRAYS_UNPACKED这些性能优化选项。在某些架构上以非打包方式处理数组可能更快但代码体积会稍大。需要根据你的处理器和编译器实测。5.2 配置实践为STM32G0系列量身定制以一颗64KB Flash8KB RAM的STM32G0为例。我的.proto文件里最大标签号是127没有复杂的嵌套repeated字段都指定了max_count。我会在项目的编译选项中定义-DPB_BUFFER_ONLY -DPB_FIELD_16BIT -DPB_NO_ERRMSG -DPB_MAX_REQUIRED_FIELDS20然后在编码解码的代码中我确保所有缓冲区都是静态分配的。经过这样配置后nanopb库本身的代码体积RO Data和运行时RAM占用RW Data会降到最低。你可以通过编译生成的map文件来验证会发现pb_encode/pb_decode等函数非常小巧全局变量几乎为零。5.3 调试技巧当数据对不上时跨设备通信数据对不上是常事。nanopb编码是二进制的肉眼难以阅读。我的调试三板斧是打印十六进制在发送端和接收端都把编码后的缓冲区以十六进制形式打印出来比如通过串口。对比两边是否完全一致。这是最直接的方法。使用pb_get_encoded_size在编码前计算大小确保你的缓冲区足够大。很多解码错误都是因为缓冲区溢出破坏了数据。检查.proto一致性确保通信双方使用的.proto文件定义是完全相同的。哪怕只是字段标签顺序变了或者修改了required/optional都可能导致解码失败。Protobuf的兼容性规则很强大但前提是你要正确使用它。踩过几次坑之后我养成了一个习惯为每一个重要的消息定义分配一个唯一的“消息类型”枚举并把它作为任何数据包的第一个字段。接收方先解码这个类型字段再决定用哪个消息结构体去解码后续数据。这构建了一个简单而健壮的应用层协议框架。在资源受限的嵌入式世界里nanopb就像一把精密的瑞士军刀它不提供华而不实的功能每一个特性都直指效率与确定性的核心。从定义数据合同到生成静态的、内存可预测的C代码再到与各种通信接口的无缝适配最后通过精细的配置将资源占用压到极限这个过程本身就是嵌入式工程师与硬件约束共舞的典型写照。当你看到那些精心定义的结构体在几十KB的Flash和几KB的RAM中流畅地序列化、穿越嘈杂的无线信道、在另一端被完美重构时那种对系统了如指掌的掌控感正是嵌入式开发的乐趣所在。

相关新闻

玩转工业4.0仿真:用Factory IO+博途V16搭建电机控制实验平台

玩转工业4.0仿真:用Factory IO+博途V16搭建电机控制实验平台

玩转工业4.0仿真:用Factory IO博途V16搭建电机控制实验平台 在工业自动化教学与工程师技能提升的领域里,理论学习与实际操作之间往往存在一道鸿沟。昂贵的硬件设备、复杂的现场环境以及潜在的安全风险,让许多学习者望而却步。然而&#xff0c…

2026/5/17 2:19:03 阅读更多 →
Cadence Allegro进阶:负片层热风焊盘(Flash)设计全解析

Cadence Allegro进阶:负片层热风焊盘(Flash)设计全解析

1. 负片层与热风焊盘:为什么你的电源层需要它? 很多刚开始用Cadence Allegro做多层板设计的工程师,尤其是从Altium Designer转过来的朋友,经常会遇到一个困惑:为什么我画个过孔,在电源层上它要么全连上&…

2026/5/17 12:35:56 阅读更多 →
深入解析MySQL ERROR 2002 (HY000):从诊断到修复的完整指南

深入解析MySQL ERROR 2002 (HY000):从诊断到修复的完整指南

1. 当你的MySQL突然“失联”:ERROR 2002 (HY000) 到底是什么? “Can‘t connect to local MySQL server through socket...” 这句话,相信很多朋友在捣鼓数据库的时候都见过。屏幕上一弹出这个错误,心里就咯噔一下,感觉…

2026/7/5 17:04:41 阅读更多 →

最新新闻

告别传统测试困境:Catch2现代化测试框架的进阶实战指南

告别传统测试困境:Catch2现代化测试框架的进阶实战指南

告别传统测试困境:Catch2现代化测试框架的进阶实战指南 【免费下载链接】Catch2 A modern, C-native, test framework for unit-tests, TDD and BDD - using C14, C17 and later (C11 support is in v2.x branch, and C03 on the Catch1.x branch) 项目地址: http…

2026/7/5 18:39:31 阅读更多 →
3步让电子阅读器变身漫画图书馆:Kindle Comic Converter使用全攻略

3步让电子阅读器变身漫画图书馆:Kindle Comic Converter使用全攻略

3步让电子阅读器变身漫画图书馆:Kindle Comic Converter使用全攻略 【免费下载链接】kcc KCC (a.k.a. Kindle Comic Converter) is a comic and manga converter for ebook readers. 项目地址: https://gitcode.com/gh_mirrors/kc/kcc 还在为电子阅读器上看漫…

2026/7/5 18:37:29 阅读更多 →
hexo-tag-aplayer从入门到精通:构建博客音乐系统的完整路线图

hexo-tag-aplayer从入门到精通:构建博客音乐系统的完整路线图

hexo-tag-aplayer从入门到精通:构建博客音乐系统的完整路线图 【免费下载链接】hexo-tag-aplayer Embed aplayer in Hexo posts/pages 项目地址: https://gitcode.com/gh_mirrors/he/hexo-tag-aplayer hexo-tag-aplayer是一款强大的Hexo标签插件,…

2026/7/5 18:35:29 阅读更多 →
网盘直链下载助手完整指南:一键获取八大网盘真实下载地址的终极解决方案

网盘直链下载助手完整指南:一键获取八大网盘真实下载地址的终极解决方案

网盘直链下载助手完整指南:一键获取八大网盘真实下载地址的终极解决方案 【免费下载链接】Online-disk-direct-link-download-assistant 一个基于 JavaScript 的网盘文件下载地址获取工具。基于【网盘直链下载助手】修改 ,支持 百度网盘 / 阿里云盘 / 中…

2026/7/5 18:33:28 阅读更多 →
如何扩展Runno:添加自定义编程语言运行时的完整指南

如何扩展Runno:添加自定义编程语言运行时的完整指南

如何扩展Runno:添加自定义编程语言运行时的完整指南 【免费下载链接】runno Sandboxed runtime for programming languages and WASI binaries. Works in the browser, on your server, or via MCP. 项目地址: https://gitcode.com/gh_mirrors/ru/runno Runn…

2026/7/5 18:33:28 阅读更多 →
对字符串排序的影响

对字符串排序的影响

字符串的大小比较并不是如C那样按照字符串字符内码大小顺序从头到尾来比较的。由于我是从C/C转过来的,我一直以来都以为.net 下字符串的比较规则和C是一样的,直到有一天我的程序在英文操作系统下出错。 .net 下,字符串的排序受 System.Threa…

2026/7/5 18:29:28 阅读更多 →

日新闻

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容 【免费下载链接】BiliTools A cross-platform bilibili toolbox. 跨平台哔哩哔哩工具箱,支持下载视频、番剧等等各类资源 项目地址: https://gitcode.com/GitHub_Trending/bilit/BiliTools …

2026/7/5 0:03:34 阅读更多 →
威胁模型全解析:从新手入门到实战应用,助你构建安全产品!

威胁模型全解析:从新手入门到实战应用,助你构建安全产品!

威胁模型的陌生现状在忙碌疲惫的一天里,参与了关于混合后量子密码学的讨论,应付端点攻击找茬的人,还参与留言板讨论后,发现“威胁模型”对多数人仍是陌生概念,且多被当作时髦用语。有趣的相关画作有一幅由 Embyr 创作的…

2026/7/5 0:03:34 阅读更多 →
渗透测试入门指南:从零基础到实战环境搭建

渗透测试入门指南:从零基础到实战环境搭建

1. 从“看热闹”到“入门”:我理解的渗透测试到底是什么?每次看到新闻里说某个大公司的数据被“黑”了,或者某个网站被攻击导致服务瘫痪,你是不是和我一样,心里会冒出两个念头:一是“这黑客真厉害”&#x…

2026/7/5 0:07:38 阅读更多 →

周新闻

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容 【免费下载链接】BiliTools A cross-platform bilibili toolbox. 跨平台哔哩哔哩工具箱,支持下载视频、番剧等等各类资源 项目地址: https://gitcode.com/GitHub_Trending/bilit/BiliTools …

2026/7/5 0:03:34 阅读更多 →
威胁模型全解析:从新手入门到实战应用,助你构建安全产品!

威胁模型全解析:从新手入门到实战应用,助你构建安全产品!

威胁模型的陌生现状在忙碌疲惫的一天里,参与了关于混合后量子密码学的讨论,应付端点攻击找茬的人,还参与留言板讨论后,发现“威胁模型”对多数人仍是陌生概念,且多被当作时髦用语。有趣的相关画作有一幅由 Embyr 创作的…

2026/7/5 0:03:34 阅读更多 →
渗透测试入门指南:从零基础到实战环境搭建

渗透测试入门指南:从零基础到实战环境搭建

1. 从“看热闹”到“入门”:我理解的渗透测试到底是什么?每次看到新闻里说某个大公司的数据被“黑”了,或者某个网站被攻击导致服务瘫痪,你是不是和我一样,心里会冒出两个念头:一是“这黑客真厉害”&#x…

2026/7/5 0:07:38 阅读更多 →

月新闻