news 2026/6/10 21:20:48

第 5 篇:责任链模式 (Chain of Responsibility) —— 协议栈的流水线

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
第 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)。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/9 19:24:54

Thymopoietin II Fragment (29-41) ;GEQRKDVYVELYL

一、基础理化性质 英文名称 &#xff1a;Thymopoietin II Fragment (29-41)三字母序列&#xff1a;Gly-Glu-Gln-Arg-Lys-Asp-Val-Tyr-Val-Glu-Leu-Tyr-Leu-OH单字母序列&#xff1a;GEQRKDVYVELYL精确分子量&#xff1a;1611.82 Da等电点&#xff08;pI&#xff09;&#xff1…

作者头像 李华
网站建设 2026/6/10 11:21:49

如何写出一个完整的测试用例?

&#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快测试用例是为了验证软件功能或需求而设计的一组测试输入、执行条件和预期结果。编写测试用例的目的是确保测试过程全面高效、有据可查。一般来说&#xff0c;编写测…

作者头像 李华
网站建设 2026/6/10 11:22:37

功能测试的测试工作流程

&#x1f345; 点击文末小卡片 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 按照产出的文档&#xff0c;介绍项目开发过程中的工作步骤 一、测试计划&#xff1a;这个计划&#xff0c;我个人觉得应该在详细设计确定后&#xff0c;代码开始…

作者头像 李华
网站建设 2026/6/10 13:19:46

Postman 怎么测接口?新手教程

在当前&#xff0c;API&#xff08;应用程序接口&#xff09;的使用变得越来越普遍。其中&#xff0c;HTTP/HTTPS API 是最常见的一种。无论是开发前端还是后端&#xff0c;测试 API 都是一个关键环节。Postman 是一种流行且强大的 API 测试工具&#xff0c;能够帮助开发人员轻…

作者头像 李华
网站建设 2026/6/9 23:46:47

计算机毕业设计之jsp基于SSM的社区志愿者服务管理系统

社区志愿者服务管理系统的目的是让使用者可以更方便的将人、设备和场景更立体的连接在一起。能让用户以更科幻的方式使用产品&#xff0c;体验高科技时代带给人们的方便&#xff0c;同时也能让用户体会到与以往常规产品不同的体验风格。与安卓&#xff0c;iOS相比较起来&#x…

作者头像 李华
网站建设 2026/6/10 15:38:45

文章跨境版权保护难题多?可信时间戳全流程解决方案来救场!

随着全球文化交流日益频繁&#xff0c;文章跨境传播已成为常态。据《中国网络文学国际传播报告&#xff08;2025&#xff09;》显示&#xff0c;中国网络文学海外活跃用户已达2亿人&#xff0c;覆盖全球200多个国家和地区。然而&#xff0c;跨境传播带来的版权保护问题也日益凸…

作者头像 李华