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

第 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 {
// 甚至还有不知名的私有协议夹杂其中
}
}

架构师的审视

  1. 违反单一职责原则 (SRP):这个函数管得太宽了。只要任何一个协议的解析逻辑变动,都要修改这个主函数。

  2. 维护噩梦:新加入一个成员负责开发“JSON 协议”,他不得不小心翼翼地修改这个大函数,稍有不慎就把同事写的 Modbus 解析搞挂了。

  3. 优先级混乱if-else的顺序决定了优先级。如果想调整优先级(比如让调试命令最先响应),必须手动剪切粘贴代码块。


2. 模式图解 (The Concept)

责任链模式把每个协议解析器看作一个Handler (节点)。我们将这些节点串成一条链表。

-> (Next?) -> [AT Handler] -> (Next?) -> [CLI Handler] -> Drop]

  • Request: 刚收到的原始数据包。

  • Handler: 包含两个核心动作:

    1. CanHandle(data): 我能处理这个数据吗?

    2. Process(data): 处理数据。

    3. 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.h"

static bool IsModbus(ProtocolHandler* self, const uint8_t* data, uint16_t len) {
// 简单的特征判断:地址码 + 功能码合法性
if (len < 4) return false;
if (data[0] != 0x01) return false; // 假设本机地址 01
return true;
}

static void HandleModbus(ProtocolHandler* self, const uint8_t* data, uint16_t len) {
printf("[%s] Processing Modbus Frame...\n", self->handler_name);
// ... 具体业务逻辑 ...
}

// 静态定义对象,无需 malloc
static 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.c
static 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.c
static 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.c
void System_Init() {
// 像搭积木一样组装协议栈
// 优先级由注册顺序决定

Chain_Register(Get_Modbus_Handler()); // 优先判断 Modbus
Chain_Register(Get_AT_Handler()); // 其次 AT
Chain_Register(Get_CLI_Handler()); // 最后 CLI
}

// 串口中断回调
void UART_RxCpltCallback(uint8_t* buf, uint16_t len) {
Chain_Input(buf, len);
}

4. 内存与性能分析 (The Cost)

栈空间 (Stack Usage)

  • 优势:传统的递归调用链(A 调用 B,B 调用 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)。

http://www.jsqmd.com/news/321698/

相关文章:

  • 互联网大厂Java面试:从Spring Boot到微服务的技术场景解读
  • 使用递归的穷举搜索
  • 2026厦门装修公司介绍,业主实测靠谱清单,装修避坑必看
  • 如何写出一个完整的测试用例?
  • The 2021 ICPC Asia East Continent Final Contest (EC-Final 2021)
  • 深度测评9个AI论文平台,MBA高效写作必备!
  • Postman 怎么测接口?新手教程
  • 计算机毕业设计之springboot交友APP的设计与实现
  • Modbus RTU(主站) 485通讯主站程序(端口0作主站) 1.西门子224xp或200...
  • 基于微信小程序的个性化漫画阅读推荐系统的设计与实现
  • 计算机毕业设计之jsp考试报名及成绩查询系统
  • 义乌雷硕包装制品有限公司 联系方式: 供应商联系与风险提示参考
  • 微信立减金回收全攻略,普通人也能轻松上手,闲置不浪费
  • 优化SEO效果的长尾关键词策略与应用技巧
  • 计算机毕业设计之springcloud基于微服务的中小企业实习生管理系统设计与开发
  • 义乌雷硕包装制品有限公司 联系方式:核实官方信息与沟通准备建议
  • 基于微信小程序的大学生就业管理系统设计与实现
  • 计算机毕业设计之jsp基于SSM的社区志愿者服务管理系统
  • CH32系列MCU外设使用相关注意事项
  • Pytest实践:使用Pytest进行API测试
  • 2026厦门装修公司口碑排行TOP10|海滨家装避坑指南,选对不踩雷
  • 使用Docker容器化部署微服务,解决环境配置难题
  • 爆火Browser-Use实战:让AI替你操作浏览器,爬虫/自动化填表一行代码搞定
  • 温州AI巨头光景极欧:揭秘行业巨头的崛起之路
  • 闭环伺服步进电机(磁编码器)全套方案 步进电机 闭环控制器 42步进电机 包含说明文档,AD工...
  • 2026表面缺陷检测系统公司技术创新与行业应用分析
  • 服务端性能测试:行业流行性能监控工具介绍
  • 2026厦门室内设计公司口碑榜单|避坑指南+选企秘籍
  • 文章跨境版权保护难题多?可信时间戳全流程解决方案来救场!
  • deepseek和豆包AI广告GEO服务商选型指南(2026年2月)