# 协议插件架构方案 ## 🤔 问题 如果以后需要动态添加协议插件,这些插件应该放在哪里? - 守护进程中? - 主应用中? - 还是两者都支持? ## 📊 当前架构分析 ### 当前协议解析位置 ``` ┌─────────────────────────────────────────┐ │ 守护进程 (soft_bus_daemon) │ │ │ │ ┌────────────────────────────────────┐ │ │ │ SerialManager │ │ │ │ ┌──────────────────────────────┐ │ │ │ │ │ SerialParserThread │ │ │ │ │ │ - parseModbusRTU() │ │ │ │ │ │ - parseModbusASCII() │ │ │ │ │ │ - parseCustomProtocol() │ │ │ │ │ │ (硬编码协议解析) │ │ │ │ │ └──────────────────────────────┘ │ │ │ └────────────────────────────────────┘ │ │ │ │ 硬件设备 → 串口 → 数据接收 → 协议解析 │ └─────────────────────────────────────────┘ ``` **问题**: - 协议解析逻辑硬编码在 `SerialParserThread` 中 - 添加新协议需要修改守护进程代码并重新编译 - 无法动态加载协议插件 ## ✅ 推荐方案:守护进程支持协议插件机制 ### 架构设计 ``` ┌─────────────────────────────────────────────────────────┐ │ 守护进程 (soft_bus_daemon) │ │ │ │ ┌───────────────────────────────────────────────────┐ │ │ │ SerialManager │ │ │ │ ┌─────────────────────────────────────────────┐ │ │ │ │ │ SerialParserThread │ │ │ │ │ │ ┌───────────────────────────────────────┐ │ │ │ │ │ │ │ ProtocolPluginManager │ │ │ │ │ │ │ │ - loadPlugin(pluginPath) │ │ │ │ │ │ │ │ - unloadPlugin(pluginId) │ │ │ │ │ │ │ │ - getPlugin(protocolName) │ │ │ │ │ │ │ └──────────────────────────────────────┘ │ │ │ │ │ │ │ │ │ │ │ │ │ ▼ │ │ │ │ │ │ ┌──────────────────────────────────────┐ │ │ │ │ │ │ │ IProtocolParser (接口) │ │ │ │ │ │ │ │ - parseFrame(data) │ │ │ │ │ │ │ │ - extractFrame(buffer) │ │ │ │ │ │ │ │ - getName() │ │ │ │ │ │ │ └──────────────────────────────────────┘ │ │ │ │ │ │ ▲ │ │ │ │ │ │ │ 实现 │ │ │ │ │ │ ┌──────┴──────┬──────────┬─────────────┐ │ │ │ │ │ │ │ ModbusRTU │ Modbus │ Custom │ │ │ │ │ │ │ │ Plugin │ ASCII │ Protocol │ │ │ │ │ │ │ │ (.so) │ Plugin │ Plugin │ │ │ │ │ │ │ │ │ (.so) │ (.so) │ │ │ │ │ │ │ └─────────────┴──────────┴─────────────┘ │ │ │ │ │ └─────────────────────────────────────────────┘ │ │ │ └───────────────────────────────────────────────────┘ │ │ │ │ 插件目录: /usr/lib/soft_bus/protocols/ │ │ - modbus_rtu.so │ │ - modbus_ascii.so │ │ - custom_protocol.so │ │ - ... │ └─────────────────────────────────────────────────────────┘ ▲ │ D-Bus 接口 │ (加载/卸载插件) │ ┌────────┴─────────────────────────────────────────────┐ │ 主应用 (soft_bus) │ │ │ │ ┌─────────────────────────────────────────────────┐ │ │ │ DaemonClient │ │ │ │ - loadProtocolPlugin(pluginPath) │ │ │ │ - unloadProtocolPlugin(pluginId) │ │ │ │ - listProtocolPlugins() │ │ │ └─────────────────────────────────────────────────┘ │ │ │ │ 插件管理界面: │ │ - 显示已加载的协议插件 │ │ - 安装/卸载协议插件 │ │ - 配置协议参数 │ └───────────────────────────────────────────────────────┘ ``` ## 🎯 核心设计原则 ### 1. **协议解析在守护进程中** - ✅ **硬件资源在守护进程**:串口、CAN等硬件资源由守护进程管理 - ✅ **数据流最短**:硬件数据 → 协议解析 → 数据库,路径最短 - ✅ **性能最优**:避免跨进程传递大量原始数据 - ✅ **稳定性高**:协议解析崩溃不影响主应用UI ### 2. **支持动态插件加载** - ✅ **插件化架构**:协议解析器作为插件(.so文件)动态加载 - ✅ **热插拔**:可以在运行时加载/卸载协议插件 - ✅ **无需重启**:添加新协议不需要重启守护进程 ### 3. **主应用管理插件** - ✅ **UI管理**:主应用提供插件管理界面 - ✅ **D-Bus通信**:通过D-Bus请求守护进程加载/卸载插件 - ✅ **配置管理**:主应用管理插件配置,守护进程执行 ## 📋 主应用 vs 守护进程职责分工 ### ❓ 问题:主应用 soft_bus 是不是只起到界面显示的作用? **答案:不是!主应用有更重要的职责。** ### 🏗️ 职责分工 #### 守护进程 (soft_bus_daemon) - "执行者" ``` 职责:硬件资源管理和协议解析执行 ✅ 硬件资源管理 - 串口设备打开/关闭 - CAN设备打开/关闭 - 硬件数据接收 ✅ 协议解析执行 - 加载协议插件 - 解析协议数据 - 将解析结果写入数据库 ✅ 数据采集 - 实时数据采集 - 数据存储到数据库 - 设备状态监控 ❌ 不负责 - UI显示 - 用户配置(只执行配置) - 数据可视化 ``` #### 主应用 (soft_bus) - "管理者 + 展示者" ``` 职责:配置管理、数据展示、用户交互 ✅ 配置管理(通过D-Bus发送给守护进程) - 设备配置(串口参数、CAN参数) - 协议配置(选择协议、协议参数) - 插件管理(安装/卸载协议插件) ✅ 数据展示 - 从数据库读取历史数据 - 实时数据可视化(图表、表格) - 设备状态显示 - 消息统计展示 ✅ 用户交互 - 设备发现界面 - 设备配置界面 - 数据查询界面 - 插件管理界面 ✅ 数据库查询(只读) - 查询设备列表 - 查询历史消息 - 查询统计数据 ❌ 不负责 - 直接操作硬件(通过D-Bus请求守护进程) - 协议解析(守护进程执行) - 实时数据采集(守护进程执行) ``` ### 🔄 数据流向 ``` ┌─────────────────────────────────────────────────────────┐ │ 硬件设备 │ └──────────────────┬──────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────┐ │ 守护进程 (soft_bus_daemon) │ │ ┌───────────────────────────────────────────────────┐ │ │ │ SerialManager / CanManager │ │ │ │ - 打开硬件设备 │ │ │ │ - 接收原始数据 │ │ │ └──────────────────┬────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌───────────────────────────────────────────────────┐ │ │ │ ProtocolPluginManager │ │ │ │ - 加载协议插件 │ │ │ │ - 解析协议数据 │ │ │ └──────────────────┬────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌───────────────────────────────────────────────────┐ │ │ │ DeviceBusCore │ │ │ │ - 存储解析结果到数据库 │ │ │ │ - 更新设备状态 │ │ │ └──────────────────┬────────────────────────────────┘ │ └────────────────────┼─────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────┐ │ 数据库 (soft_bus_db) │ │ - 设备信息 │ │ - 原始数据 │ │ - 解析后的数据 │ │ - 设备状态 │ └──────────────────┬──────────────────────────────────────┘ │ │ 读取 ▼ ┌─────────────────────────────────────────────────────────┐ │ 主应用 (soft_bus) │ │ ┌───────────────────────────────────────────────────┐ │ │ │ DeviceBusCore (只读) │ │ │ │ - 从数据库读取设备列表 │ │ │ │ - 从数据库读取历史数据 │ │ │ └──────────────────┬────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌───────────────────────────────────────────────────┐ │ │ │ UI组件 │ │ │ │ - 设备树(显示设备列表) │ │ │ │ - 数据表格(显示历史数据) │ │ │ │ - 图表(数据可视化) │ │ │ │ - 配置界面(发送配置到守护进程) │ │ │ └───────────────────────────────────────────────────┘ │ │ │ │ │ │ D-Bus │ │ ▼ │ │ ┌───────────────────────────────────────────────────┐ │ │ │ DaemonClient │ │ │ │ - 请求守护进程打开/关闭设备 │ │ │ │ - 请求守护进程加载/卸载插件 │ │ │ │ - 查询守护进程状态 │ │ │ └───────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────┘ ``` ### 💡 主应用的 CoreService 做什么? 主应用的 `CoreService` 和 `DeviceBusCore` **不是多余的**,它们负责: 1. **数据库访问(只读)** ```cpp // 主应用:从数据库读取设备列表 QList devices = m_busCore->getAllDevices(); // 主应用:从数据库读取历史消息 QList messages = m_busCore->queryMessages(...); ``` 2. **UI状态管理** ```cpp // 主应用:管理UI中的设备列表状态 // 主应用:管理数据表格的显示状态 // 主应用:管理图表的显示状态 ``` 3. **配置管理(通过D-Bus发送给守护进程)** ```cpp // 主应用:用户配置设备 DeviceInfo device = ...; m_busCore->registerDevice(device); // 写入数据库 // 主应用:通过D-Bus请求守护进程打开设备 daemonClient->openDevice(device.portname); ``` ### ⚠️ 重要区别 | 操作 | 守护进程 | 主应用 | |------|---------|--------| | **打开串口** | ✅ 直接打开 | ❌ 通过D-Bus请求 | | **接收数据** | ✅ 直接接收 | ❌ 不接收 | | **协议解析** | ✅ 直接解析 | ❌ 不解析 | | **写入数据库** | ✅ 写入解析结果 | ❌ 不写入 | | **读取数据库** | ✅ 读取设备配置 | ✅ 读取历史数据 | | **显示UI** | ❌ 无UI | ✅ 显示界面 | | **用户配置** | ❌ 只执行 | ✅ 接收并保存 | ### 🎯 总结 **主应用 soft_bus 不是只起到界面显示的作用!** 主应用的职责: 1. ✅ **配置管理**:用户通过UI配置设备,保存到数据库,通过D-Bus通知守护进程 2. ✅ **数据展示**:从数据库读取数据,在UI中展示(表格、图表) 3. ✅ **用户交互**:提供友好的界面,让用户管理设备、查看数据 4. ✅ **插件管理**:通过D-Bus管理守护进程中的协议插件 守护进程的职责: 1. ✅ **硬件管理**:直接操作硬件设备(串口、CAN) 2. ✅ **协议解析**:加载插件,解析协议数据 3. ✅ **数据采集**:实时采集数据,写入数据库 **简单理解**: - 守护进程 = "工人":负责实际干活(硬件操作、数据采集、协议解析) - 主应用 = "经理":负责管理和展示(配置管理、数据展示、用户交互) 两者通过**数据库**(共享数据)和**D-Bus**(控制命令)协作!🚀 ## 📝 实现方案 ### 1. 定义协议解析器接口 ```cpp // src/core/pal/protocols/IProtocolParser.h /** * @file IProtocolParser.h * @brief 协议解析器接口 - 所有协议插件必须实现此接口 */ #pragma once #include #include #include class IProtocolParser { public: virtual ~IProtocolParser() = default; /** * @brief 获取协议名称 * @return 协议名称(如 "modbus-rtu", "modbus-ascii") */ virtual QString getName() const = 0; /** * @brief 获取协议描述 */ virtual QString getDescription() const = 0; /** * @brief 获取协议版本 */ virtual QString getVersion() const = 0; /** * @brief 从缓冲区提取完整帧 * @param buffer 数据缓冲区 * @param maxLength 最大帧长度 * @return 提取的帧数据,如果没有完整帧则返回空 */ virtual QByteArray extractFrame(const QByteArray& buffer, int maxLength = 256) = 0; /** * @brief 解析协议帧 * @param frame 完整的协议帧 * @param portName 端口名称(用于设备识别) * @return 解析后的JSON数据,解析失败返回空对象 */ virtual QJsonObject parseFrame(const QByteArray& frame, const QString& portName) = 0; /** * @brief 验证帧是否有效 * @param frame 协议帧 * @return 有效返回true */ virtual bool validateFrame(const QByteArray& frame) const = 0; /** * @brief 获取协议配置参数(用于UI配置) * @return JSON格式的配置参数定义 */ virtual QJsonObject getConfigSchema() const = 0; /** * @brief 设置协议配置参数 * @param config JSON格式的配置参数 */ virtual void setConfig(const QJsonObject& config) = 0; }; // 插件导出函数(C接口,避免C++名称修饰问题) extern "C" { /** * @brief 创建协议解析器实例 * @return 协议解析器指针,调用者负责释放 */ IProtocolParser* createProtocolParser(); /** * @brief 获取插件信息 * @return JSON格式的插件元数据 */ const char* getPluginInfo(); } ``` ### 2. 实现协议插件管理器 ```cpp // src/core/pal/protocols/ProtocolPluginManager.h /** * @file ProtocolPluginManager.h * @brief 协议插件管理器 - 管理协议插件的加载和卸载 */ #pragma once #include "IProtocolParser.h" #include #include #include #include class ProtocolPluginManager { public: static ProtocolPluginManager* instance(); /** * @brief 加载协议插件 * @param pluginPath 插件文件路径(.so文件) * @return 插件ID,失败返回空字符串 */ QString loadPlugin(const QString& pluginPath); /** * @brief 卸载协议插件 * @param pluginId 插件ID * @return 成功返回true */ bool unloadPlugin(const QString& pluginId); /** * @brief 获取协议解析器 * @param protocolName 协议名称(如 "modbus-rtu") * @return 协议解析器指针,不存在返回nullptr */ IProtocolParser* getParser(const QString& protocolName); /** * @brief 获取所有已加载的插件信息 * @return JSON格式的插件列表 */ QJsonArray getLoadedPlugins() const; /** * @brief 从目录自动加载插件 * @param pluginDir 插件目录路径 */ void loadPluginsFromDirectory(const QString& pluginDir); private: ProtocolPluginManager() = default; ~ProtocolPluginManager(); struct PluginInfo { QString id; QString path; QString name; QString version; QLibrary* library; IProtocolParser* parser; void (*destroyFunc)(IProtocolParser*); }; QMap m_plugins; // pluginId -> PluginInfo QMap m_protocolMap; // protocolName -> pluginId mutable QMutex m_mutex; }; ``` ### 3. 修改 SerialParserThread 使用插件 ```cpp // src/serial_manager/serial_parser_thread.cpp #include "core/pal/protocols/ProtocolPluginManager.h" void SerialParserThread::run() { ProtocolPluginManager* pluginManager = ProtocolPluginManager::instance(); while (!m_stopped) { // ... 现有代码 ... // 获取设备配置 DeviceInfo device = m_busCore->getDeviceInfoByPortname(portName); // 提取帧 QByteArray frame = buffer->extractFrame(device.protocol, device.protocol_detail); // 使用插件解析 if (!device.protocol.isEmpty()) { IProtocolParser* parser = pluginManager->getParser(device.protocol_detail); if (parser) { QJsonObject parsedData = parser->parseFrame(frame, portName); if (!parsedData.isEmpty()) { emit modbusDataParsed(parsedData); // 或统一的数据解析信号 continue; } } } // 如果插件解析失败,尝试其他协议(向后兼容) // ... } } ``` ### 4. 扩展 DaemonService 添加插件管理接口 ```cpp // src/daemon/daemon_service.h public slots: // ... 现有接口 ... /** * @brief 加载协议插件 * @param pluginPath 插件文件路径 * @return 插件ID,失败返回空字符串 */ QString loadProtocolPlugin(const QString& pluginPath); /** * @brief 卸载协议插件 * @param pluginId 插件ID * @return 成功返回true */ bool unloadProtocolPlugin(const QString& pluginId); /** * @brief 获取已加载的协议插件列表 * @return JSON格式的插件列表 */ QString getProtocolPlugins() const; ``` ### 5. 实现 DaemonService 插件接口 ```cpp // src/daemon/daemon_service.cpp #include "core/pal/protocols/ProtocolPluginManager.h" QString DaemonService::loadProtocolPlugin(const QString& pluginPath) { ProtocolPluginManager* manager = ProtocolPluginManager::instance(); return manager->loadPlugin(pluginPath); } bool DaemonService::unloadProtocolPlugin(const QString& pluginId) { ProtocolPluginManager* manager = ProtocolPluginManager::instance(); return manager->unloadPlugin(pluginId); } QString DaemonService::getProtocolPlugins() const { ProtocolPluginManager* manager = ProtocolPluginManager::instance(); QJsonArray plugins = manager->getLoadedPlugins(); return QJsonDocument(plugins).toJson(QJsonDocument::Compact); } ``` ### 6. 示例:Modbus RTU 插件实现 ```cpp // plugins/modbus_rtu/modbus_rtu_plugin.cpp #include "core/pal/protocols/IProtocolParser.h" class ModbusRTUParser : public IProtocolParser { public: QString getName() const override { return "modbus-rtu"; } QString getDescription() const override { return "Modbus RTU Protocol Parser"; } QString getVersion() const override { return "1.0.0"; } QByteArray extractFrame(const QByteArray& buffer, int maxLength) override { // Modbus RTU 帧提取逻辑 // 检查CRC,提取完整帧 // ... } QJsonObject parseFrame(const QByteArray& frame, const QString& portName) override { // Modbus RTU 解析逻辑 // 解析功能码、数据等 // ... } bool validateFrame(const QByteArray& frame) const override { // 验证CRC等 // ... } QJsonObject getConfigSchema() const override { // 返回配置参数定义 // ... } void setConfig(const QJsonObject& config) override { // 设置配置参数 // ... } }; extern "C" { IProtocolParser* createProtocolParser() { return new ModbusRTUParser(); } const char* getPluginInfo() { return R"({ "name": "modbus-rtu", "version": "1.0.0", "description": "Modbus RTU Protocol Parser", "author": "Your Name" })"; } } ``` ### 7. 插件目录结构 ``` soft_bus/ ├── plugins/ │ ├── modbus_rtu/ │ │ ├── CMakeLists.txt │ │ ├── modbus_rtu_plugin.cpp │ │ └── modbus_rtu_plugin.h │ ├── modbus_ascii/ │ │ └── ... │ └── custom_protocol/ │ └── ... └── install/ └── lib/ └── soft_bus/ └── protocols/ ├── modbus_rtu.so ├── modbus_ascii.so └── custom_protocol.so ``` ## 🔄 工作流程 ### 1. 守护进程启动时自动加载插件 ```cpp // daemon_main.cpp int main(int argc, char *argv[]) { // ... 初始化 ... // 加载协议插件 ProtocolPluginManager* pluginManager = ProtocolPluginManager::instance(); pluginManager->loadPluginsFromDirectory("/usr/lib/soft_bus/protocols"); // ... 其他初始化 ... } ``` ### 2. 主应用通过D-Bus管理插件 ```cpp // 主应用代码 DaemonClient* client = DaemonClient::instance(); client->connectToDaemon(); // 加载新插件 QString pluginId = client->loadProtocolPlugin("/path/to/new_protocol.so"); // 查看已加载的插件 QString pluginsJson = client->getProtocolPlugins(); ``` ## ✅ 优势 1. **协议解析在守护进程** - ✅ 数据流最短,性能最优 - ✅ 硬件资源统一管理 - ✅ 协议解析崩溃不影响UI 2. **支持动态插件** - ✅ 无需重新编译守护进程 - ✅ 可以热插拔协议插件 - ✅ 第三方可以开发协议插件 3. **主应用管理插件** - ✅ 提供友好的UI界面 - ✅ 通过D-Bus远程管理 - ✅ 配置和插件分离 ## 📋 实施建议 ### 阶段1:基础架构(推荐先做) 1. ✅ 定义 `IProtocolParser` 接口 2. ✅ 实现 `ProtocolPluginManager` 3. ✅ 修改 `SerialParserThread` 使用插件 ### 阶段2:插件化现有协议 1. ✅ 将 Modbus RTU 改为插件 2. ✅ 将 Modbus ASCII 改为插件 3. ✅ 测试插件加载和解析 ### 阶段3:D-Bus接口 1. ✅ 扩展 `DaemonService` 添加插件管理接口 2. ✅ 扩展 `DaemonClient` 添加插件管理接口 3. ✅ 主应用添加插件管理UI ### 阶段4:完善功能 1. ✅ 插件配置管理 2. ✅ 插件版本管理 3. ✅ 插件依赖管理 ## 🎯 总结 **回答你的问题**:协议插件应该**放在守护进程中**,但通过**插件机制**支持动态加载。 **原因**: 1. 硬件资源在守护进程,协议解析也应该在守护进程(数据流最短) 2. 通过插件机制,可以动态加载,不需要重新编译守护进程 3. 主应用通过D-Bus管理插件,提供友好的UI界面 这样既保证了性能,又支持了灵活扩展!🚀