使用责任链模式重构串口协议栈,是解决多协议处理、代码高耦合问题的经典方案。它能将复杂的协议解析逻辑拆解为一个个独立的、可复用的处理节点,使你的代码更清晰、更健壮、更易于扩展。
为什么要用责任链模式?
在传统的串口协议处理中,我们常常会写一个巨大的 if-else 或 switch-case 函数,里面混杂着各种协议的判断和处理逻辑。
c
// 重构前:高度耦合的"巨型函数"
void parse_packet(uint8_t *data, int len) {
if (check_crc(data, len)) { // 1. 校验
if (data == 0xAA) { // 2. 判断协议A
// ... 处理协议A
} else if (data == 0x55) { // 3. 判断协议B
// ... 处理协议B
} else if (is_modbus_frame(data, len)) { // 4. 判断Modbus
// ... 处理Modbus
}
// ... 新增协议C?继续在这里加 else if
}
}
这种方式的缺点非常明显:
高耦合: 所有协议逻辑都挤在一起,修改一个协议可能影响到其他协议。
难扩展: 每增加一个新协议,都需要修改这个核心函数,违反了"开闭原则"。
难测试: 无法单独测试某个协议的解析逻辑。
责任链模式通过"链式"传递请求,完美解决了这些问题。每个协议处理器只关心自己能否处理,不能处理就丢给下一个
责任链重构三步走:
第一步:定义统一的处理器接口
首先,我们需要一个抽象基类(在C语言中可以用结构体和函数指针模拟),作为所有协议处理器的"标准"。
c
// handler.h - 抽象处理者
#ifndef HANDLER_H
#define HANDLER_H
#include <stdint.h>
#include <stdbool.h>
// 处理结果枚举
typedef enum {
HANDLER_OK, // 已成功处理,链条终止
HANDLER_PASS, // 无法处理,传递给下一个
HANDLER_ERROR // 发生错误,链条终止
} HandlerResult;
// 抽象处理者接口
typedef struct ProtocolHandler {
// 指向下一个处理者的指针
struct ProtocolHandler* next;
// 处理请求的函数指针
HandlerResult (*handle)(struct ProtocolHandler* self, uint8_t* data, int len);
// 设置下一个处理者
void (*set_next)(struct ProtocolHandler* self, struct ProtocolHandler* next);
} ProtocolHandler;
// 默认实现 set_next 函数
static inline void default_set_next(ProtocolHandler* self, ProtocolHandler* next) {
self->next = next;
}
#endif // HANDLER_H
第二步:实现具体的协议处理器
接下来,为每个具体的协议(如协议A、协议B、Modbus)创建一个处理者。每个处理者都遵循统一的接口。
c
// protocol_a_handler.c - 具体处理者A
#include "handler.h"
#include <stdio.h>
// 协议A的具体处理逻辑
static HandlerResult handle_protocol_a(ProtocolHandler* self, uint8_t* data, int len) {
// 1. 判断是否是协议A (例如,检查帧头)
if (len > 0 && data == 0xAA) {
printf("Protocol A: Handling frame. Data: %02X\n", data);
// ... 执行协议A的完整解析 ...
return HANDLER_OK; // 处理成功,结束链条
}
// 2. 不是协议A,传递给下一个处理者
if (self->next != NULL) {
return self->next->handle(self->next, data, len);
}
return HANDLER_PASS; // 链尾,无人处理
}
// 创建并初始化一个协议A处理者实例
ProtocolHandler* create_protocol_a_handler() {
static ProtocolHandler handler;
handler.next = NULL;
handler.handle = handle_protocol_a;
handler.set_next = default_set_next;
return &handler;
}
// modbus_handler.c - 具体处理者:Modbus RTU
#include "handler.h"
#include <stdio.h>
// Modbus的具体处理逻辑
static HandlerResult handle_modbus(ProtocolHandler* self, uint8_t* data, int len) {
// 1. 判断是否是Modbus帧 (例如,检查地址和CRC)
if (len >= 4 && check_modbus_crc(data, len)) { // 假设有此函数
printf("Modbus RTU: Handling request. Address: %d\n", data);
// ... 执行Modbus命令解析 ...
return HANDLER_OK;
}
// 2. 不是Modbus,传递给下一个
if (self->next != NULL) {
return self->next->handle(self->next, data, len);
}
return HANDLER_PASS;
}
// 创建并初始化一个Modbus处理者实例
ProtocolHandler* create_modbus_handler() {
static ProtocolHandler handler;
handler.next = NULL;
handler.handle = handle_modbus;
handler.set_next = default_set_next;
return &handler;
}
第三步:组装责任链并处理数据
最后,在系统初始化时,将各个处理器链接起来,形成一个完整的处理链条
c
// main.c - 客户端代码
#include "handler.h"
#include <stdio.h>
// 声明外部创建处理者的函数
extern ProtocolHandler* create_protocol_a_handler();
extern ProtocolHandler* create_modbus_handler();
int main() {
// 1. 创建各个处理者
ProtocolHandler* handler_a = create_protocol_a_handler();
ProtocolHandler* handler_modbus = create_modbus_handler();
// 2. 组装责任链: A -> Modbus
handler_a->set_next(handler_a, handler_modbus);
// 现在,handler_a 就是整个责任链的入口
ProtocolHandler* chain_head = handler_a;
// 3. 模拟接收到的串口数据
uint8_t frame_a[] = {0xAA, 0x01, 0x02};
uint8_t frame_modbus[] = {0x01, 0x03, 0x00, 0x00, 0x44, 0xB9}; // 假设的Modbus帧
uint8_t frame_unknown[] = {0xFF, 0xFF};
printf("--- Processing Frame A ---\n");
chain_head->handle(chain_head, frame_a, sizeof(frame_a));
printf("\n--- Processing Modbus Frame ---\n");
chain_head->handle(chain_head, frame_modbus, sizeof(frame_modbus));
printf("\n--- Processing Unknown Frame ---\n");
chain_head->handle(chain_head, frame_unknown, sizeof(frame_unknown));
return 0;
}
重构后的优势
低耦合: 每个协议处理器都是独立的模块,互不干扰。Protocol A 处理器完全不知道 Modbus 处理器的存在。
高内聚: 每个处理者只负责一种协议的解析,逻辑清晰,代码可读性强。
易扩展: 想增加一个新协议?只需创建一个新的处理者类,实现 handle 方法,然后把它插入到责任链的任意位置即可,无需修改任何现有代码。
易复用: 一些通用处理逻辑(如CRC校验、日志记录、解密)也可以做成独立的处理者,方便地在不同的责任链中复用。
通过这种方式,你的串口协议栈将变得像一个可插拔的积木系统,灵活而强大!