第 5 篇:责任链模式 (Chain of Responsibility) —— 协议栈的流水线
专栏导读当一个请求可能被多个对象中的某一个处理但具体由谁处理在运行时才确定时责任链模式是最佳选择。在嵌入式通信中它能将复杂的混合协议解析解耦成一个个独立的“处理器”让你的代码像工厂流水线一样井井有条。1. 场景还原 (The Pain)你负责维护一个DTU (数据传输单元)项目。串口数据进来后你需要判断它是什么格式。菜鸟的写法逻辑面条 (Spaghetti Code)// 这种函数在工业界随处可见往往长达 2000 行void UART_Parse_Super_Function(uint8_t* data, uint16_t len) {// 1. 先猜是不是 Modbus (判断包头和 CRC)if (data[0] 0x01 CheckCRC(data)) {Modbus_Process(data);}// 2. 猜是不是 AT 指令 (找 \r\nOK)else if (strstr((char*)data, OK\r\n)) {AT_Process(data);}// 3. 猜是不是调试命令else if (strncmp((char*)data, reboot, 6) 0) {System_Reset();}// ... 后面还有 GPS 协议、OTA 协议 ...else {// 甚至还有不知名的私有协议夹杂其中}}架构师的审视违反单一职责原则 (SRP)这个函数管得太宽了。只要任何一个协议的解析逻辑变动都要修改这个主函数。维护噩梦新加入一个成员负责开发“JSON 协议”他不得不小心翼翼地修改这个大函数稍有不慎就把同事写的 Modbus 解析搞挂了。优先级混乱if-else的顺序决定了优先级。如果想调整优先级比如让调试命令最先响应必须手动剪切粘贴代码块。2. 模式图解 (The Concept)责任链模式把每个协议解析器看作一个Handler (节点)。我们将这些节点串成一条链表。- (Next?) - [AT Handler] - (Next?) - [CLI Handler] - Drop]Request: 刚收到的原始数据包。Handler: 包含两个核心动作CanHandle(data): 我能处理这个数据吗Process(data): 处理数据。Next: 指向下一个接盘侠的指针。Flow: 数据从链头进入谁认识谁处理都不认识丢弃。3. 代码实战 (The Code)为了避免递归导致的栈溢出嵌入式大忌我们将采用迭代 (Iteration)方式实现链表遍历。3.1 定义抽象处理者 (Handler Interface)#include stdint.h#include stdbool.h#include stddef.h// 定义 Handler 结构体typedef struct ProtocolHandler_t ProtocolHandler;struct ProtocolHandler_t {const char* handler_name; // 用于调试// 核心判定函数返回 true 表示是我的菜false 表示不认识bool (*match)(ProtocolHandler* self, const uint8_t* data, uint16_t len);// 核心处理函数void (*process)(ProtocolHandler* self, const uint8_t* data, uint16_t len);// 链表指针ProtocolHandler* next;};3.2 实现具体处理者 (Concrete Handlers)Handler 1: Modbus 解析器// ModbusHandler.c#include Chain.hstatic bool IsModbus(ProtocolHandler* self, const uint8_t* data, uint16_t len) {// 简单的特征判断地址码 功能码合法性if (len 4) return false;if (data[0] ! 0x01) return false; // 假设本机地址 01return true;}static void HandleModbus(ProtocolHandler* self, const uint8_t* data, uint16_t len) {printf([%s] Processing Modbus Frame...\n, self-handler_name);// ... 具体业务逻辑 ...}// 静态定义对象无需 mallocstatic ProtocolHandler s_modbus_handler {.handler_name Modbus,.match IsModbus,.process HandleModbus,.next NULL};ProtocolHandler* Get_Modbus_Handler(void) {return s_modbus_handler;}Handler 2: AT 指令解析器// ATHandler.cstatic bool IsAT(ProtocolHandler* self, const uint8_t* data, uint16_t len) {// 判断是否包含 AT 或 OK// 注意实际项目中不要在中断里用 strstr这里仅作演示if (len 100) return false;return (data[0] A data[1] T);}static void HandleAT(ProtocolHandler* self, const uint8_t* data, uint16_t len) {printf([%s] Response AT Command...\n, self-handler_name);}static ProtocolHandler s_at_handler {.handler_name AT_Cmd,.match IsAT,.process HandleAT,.next NULL};ProtocolHandler* Get_AT_Handler(void) {return s_at_handler;}3.3 链条组装与运行 (Client)这是一个简单的链表管理器。// ProtocolManager.cstatic ProtocolHandler* s_head NULL;// 注册函数将 Handler 加入链表尾部 (或者头部视优先级而定)void Chain_Register(ProtocolHandler* node) {if (s_head NULL) {s_head node;} else {ProtocolHandler* cur s_head;while (cur-next ! NULL) {cur cur-next;}cur-next node;}}// 核心输入入口void Chain_Input(const uint8_t* data, uint16_t len) {ProtocolHandler* cur s_head;while (cur ! NULL) {// 1. 询问当前节点是否能处理if (cur-match(cur, data, len)) {// 2. 能处理 - 执行并结束cur-process(cur, data, len);return;}// 3. 不能处理 - 传给下一个cur cur-next;}// 4. 谁都没处理 - 丢弃或记录日志printf([Chain] Unknown Protocol, Drop!\n);}3.4 初始化流程// main.cvoid System_Init() {// 像搭积木一样组装协议栈// 优先级由注册顺序决定Chain_Register(Get_Modbus_Handler()); // 优先判断 ModbusChain_Register(Get_AT_Handler()); // 其次 ATChain_Register(Get_CLI_Handler()); // 最后 CLI}// 串口中断回调void UART_RxCpltCallback(uint8_t* buf, uint16_t len) {Chain_Input(buf, len);}4. 内存与性能分析 (The Cost)栈空间 (Stack Usage)优势传统的递归调用链A 调用 BB 调用 C会消耗大量栈空间。我们使用的while循环迭代法无论链条多长栈占用都是O(1)。这对 RAM 只有几 KB 的单片机至关重要。执行效率 (Latency)最坏情况如果链条上有 10 个协议而收到的包是第 10 个协议负责或者干脆是乱码那么 CPU 需要执行 10 次match函数。优化策略贪心排序。统计现网数据将出现频率最高的协议如 Modbus放在链表头部注册。这样 90% 的请求都在第一个节点被拦截效率极高。5. 变种与延伸 (The Evolution)5.1 动态插件系统 (Plugins)基于此模式你可以实现一个支持动态加载的系统。 比如你的设备支持 SD 卡加载驱动。你可以在运行时读取 SD 卡里的 Shared Object (若跑 Linux) 或特定的二进制代码将其中的 Handler 挂载到链表上。这意味着升级新协议不需要重启设备。5.2 责任链 观察者 (Chain Observer)目前的实现是“独占式”的一旦 Match 成功就 return。 有些场景需要“旁路监听”。例如你希望 Modbus 处理数据的同时流量统计模块也能看到这个数据包。改法在process后不直接return而是继续cur cur-next或者引入一个is_final标志位由 Handler 决定是否终结链条。5.3 复杂协议过滤器不仅仅是区分协议还可以用于数据预处理。Node 1:解密 Handler(如果不加密直接 pass加密则解密后修改 data 内容传给 Node 2)。Node 2:解压 Handler。Node 3:业务 Handler。 这样就构成了一个强大的数据处理管道 (Pipeline)。

相关新闻

Infoseek宣发引擎:像部署代码一样管理你的媒介流量

Infoseek宣发引擎:像部署代码一样管理你的媒介流量

作为一名开发者,你是否习惯了一切皆可配置、一切皆有日志?当面对市场部提出的一次媒介宣发需求,你是否觉得那是一个黑盒——预算不明、渠道不清、效果成谜?今天,我们聊聊如何用工程化的思维,重构媒介宣发这…

2026/7/6 12:24:46 阅读更多 →
2025高口碑企业微信SCRM推荐:微盛·企微管家的AI客户管理实践

2025高口碑企业微信SCRM推荐:微盛·企微管家的AI客户管理实践

但企业在用企业微信做客户管理中面临数据分散、响应延迟、转化效率低三个痛点。这要求高口碑企业微信SCRM需兼顾运营效率与客户个性化需求,微盛企微管家作为腾讯四轮投资的官方服务商,通过智能工具与实战策略助力企业解决这些问题,实现客户管…

2026/7/3 6:22:22 阅读更多 →
低成本启动微信外卖!单店/平台二合一系统源码,功能列表全解析

低成本启动微信外卖!单店/平台二合一系统源码,功能列表全解析

温馨提示:文末有资源获取方式系统简述:面对餐饮行业日益增长的线上化需求,我们推出这款高度集成、开箱即用的外卖点餐小程序系统商业版。它最大特点是允许用户以极低的初始成本,在“独立单店”和“聚合平台”两种模式间自由选择&a…

2026/7/6 1:37:15 阅读更多 →

最新新闻

Arduino+ESP32-CAM摄像头+Blinker获取视频监控

Arduino+ESP32-CAM摄像头+Blinker获取视频监控

大家好,这节课给大家分享ArduinoESP32-CAM摄像头Blinker获取视频监控,废话不多说,直接开干: 1.硬件材料准备 ESP32-CAM摄像头 ESP32-CAM 拥有业内极富竞争力的小尺寸摄像头模组,该模块可以作为最小系统独立工作&#x…

2026/7/6 13:24:18 阅读更多 →
PLC标定耗时又易错?LabVIEW自动标定方案效率翻倍

PLC标定耗时又易错?LabVIEW自动标定方案效率翻倍

阅读时间:5分钟 | 适用人群:PLC工程师/自动化测试工程师/工业控制系统设计师痛点直击:国产PLC模块(AI/AO模拟量输入输出)在使用前必须进行标定,传统手工标定需要人工观测数据、手动记录、反复计算&#xff…

2026/7/6 13:24:18 阅读更多 →
5分钟掌握AutoClicker:Windows鼠标自动点击工具的终极指南 [特殊字符]

5分钟掌握AutoClicker:Windows鼠标自动点击工具的终极指南 [特殊字符]

5分钟掌握AutoClicker:Windows鼠标自动点击工具的终极指南 🚀 【免费下载链接】AutoClicker AutoClicker is a useful simple tool for automating mouse clicks. 项目地址: https://gitcode.com/gh_mirrors/au/AutoClicker 你是否厌倦了重复的鼠…

2026/7/6 13:24:18 阅读更多 →
AI 转型任务契约:把创始人的起步试点写成可验收 artifact

AI 转型任务契约:把创始人的起步试点写成可验收 artifact

AI 转型任务契约:把创始人的起步试点写成可验收 artifact 企业内部做 AI 试点,最容易出现的工程问题不是“没有模型”,而是“没有契约”。工具已经接入,群里也有人试用,但项目文档里找不到输入来源、输出对象、审核责任…

2026/7/6 13:20:07 阅读更多 →
小程序开发必知:四大法律合规领域与全周期实操指南

小程序开发必知:四大法律合规领域与全周期实操指南

1. 项目概述:为什么小程序开发者必须懂法?干了这么多年互联网产品,从PC网站做到移动App,再到如今遍地开花的小程序,我最大的感触就是:技术实现的门槛在降低,但合规运营的门槛在急剧升高。尤其是…

2026/7/6 13:18:04 阅读更多 →
图像分类落地:预处理管道比模型选型先出问题

图像分类落地:预处理管道比模型选型先出问题

图像分类落地:预处理管道比模型选型先出问题 一、你的 ResNet 在 ImageNet 上 96%,上自己的数据直接掉到 72%:谁在作怪? 这是图像分类落地的经典翻车剧本:你选了 SOTA 模型架构(EfficientNet/ConvNeXt/ViT&…

2026/7/6 13:16:02 阅读更多 →

日新闻

H2 与 MySQL 单元测试兼容性:5 个关键 SQL 语句差异与规避方案

H2 与 MySQL 单元测试兼容性:5 个关键 SQL 语句差异与规避方案

H2与MySQL单元测试兼容性:5个关键SQL语句差异与规避方案1. 单元测试中的数据库兼容性挑战在Java开发领域,单元测试是保证代码质量的重要环节。当应用涉及数据库操作时,测试环境的搭建往往成为开发者的痛点。H2数据库因其轻量级、内存模式和快…

2026/7/6 0:01:17 阅读更多 →
Windows任务栏终极清理指南:用RBTray一键隐藏窗口到系统托盘

Windows任务栏终极清理指南:用RBTray一键隐藏窗口到系统托盘

Windows任务栏终极清理指南:用RBTray一键隐藏窗口到系统托盘 【免费下载链接】rbtray A fork of RBTray from http://sourceforge.net/p/rbtray/code/. 项目地址: https://gitcode.com/gh_mirrors/rb/rbtray 你是否厌倦了Windows任务栏上密密麻麻的图标&…

2026/7/6 0:01:17 阅读更多 →
Visual C++ 运行时库一键安装终极指南:告别DLL缺失烦恼

Visual C++ 运行时库一键安装终极指南:告别DLL缺失烦恼

Visual C 运行时库一键安装终极指南:告别DLL缺失烦恼 【免费下载链接】vcredist AIO Repack for latest Microsoft Visual C Redistributable Runtimes 项目地址: https://gitcode.com/gh_mirrors/vc/vcredist 你是否曾经遇到过这样的情况:下载了…

2026/7/6 0:05:19 阅读更多 →

周新闻

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/6 8:11:50 阅读更多 →
威胁模型全解析:从新手入门到实战应用,助你构建安全产品!

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

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

2026/7/6 8:11:52 阅读更多 →
渗透测试入门指南:从零基础到实战环境搭建

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

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

2026/7/6 6:52:56 阅读更多 →

月新闻