协议插件架构方案.md 29 KB

协议插件架构方案

🤔 问题

如果以后需要动态添加协议插件,这些插件应该放在哪里?

  • 守护进程中?
  • 主应用中?
  • 还是两者都支持?

📊 当前架构分析

当前协议解析位置

┌─────────────────────────────────────────┐
│  守护进程 (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 做什么?

主应用的 CoreServiceDeviceBusCore 不是多余的,它们负责:

  1. 数据库访问(只读) ```cpp // 主应用:从数据库读取设备列表 QList devices = m_busCore->getAllDevices();
  2. // 主应用:从数据库读取历史消息 QList messages = m_busCore->queryMessages(...);

    
    2. **UI状态管理**
       ```cpp
       // 主应用:管理UI中的设备列表状态
       // 主应用:管理数据表格的显示状态
       // 主应用:管理图表的显示状态
    
    1. 配置管理(通过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 <QByteArray>
    #include <QString>
    #include <QJsonObject>
    
    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. 实现协议插件管理器

    // src/core/pal/protocols/ProtocolPluginManager.h
    /**
     * @file ProtocolPluginManager.h
     * @brief 协议插件管理器 - 管理协议插件的加载和卸载
     */
    #pragma once
    
    #include "IProtocolParser.h"
    #include <QMap>
    #include <QString>
    #include <QLibrary>
    #include <QMutex>
    
    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<QString, PluginInfo> m_plugins;  // pluginId -> PluginInfo
        QMap<QString, QString> m_protocolMap; // protocolName -> pluginId
        mutable QMutex m_mutex;
    };
    

    3. 修改 SerialParserThread 使用插件

    // 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 添加插件管理接口

    // 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 插件接口

    // 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 插件实现

    // 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. 守护进程启动时自动加载插件

    // daemon_main.cpp
    int main(int argc, char *argv[])
    {
        // ... 初始化 ...
        
        // 加载协议插件
        ProtocolPluginManager* pluginManager = ProtocolPluginManager::instance();
        pluginManager->loadPluginsFromDirectory("/usr/lib/soft_bus/protocols");
        
        // ... 其他初始化 ...
    }
    

    2. 主应用通过D-Bus管理插件

    // 主应用代码
    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界面

    这样既保证了性能,又支持了灵活扩展!🚀