远程监控系统中,很多设备都有自己的通讯协议,如何做的兼容这些不同的协议?我们系统采用的的方案是把解析逻辑做成插件,每种协议一个 DLL,在后台进行配置,解析的时候自动匹配到合适的解析器。增加协议的时候,不用改代码,只要根据手册开发对应的 DLL 。


一、背景

1.1 业务场景

协议类型 应用场景 数据格式
RTU 协议 抽油机控制器 自定义帧结构
Modbus RTU 标准工业设备 功能码 + 寄存器
SCDMA 水表/流量计 188 协议变种

传统做法是为每种协议硬编码解析逻辑,耦合重、难维护,加新协议要改核心代码,还没法热更新,必须重启服务。

1.2 设计目标

  • 插件化:每种协议独立 DLL,互不影响
  • 配置驱动:数据库配置启用/禁用协议
  • 热加载:运行时动态加载,无需重启
  • 统一接口:所有协议实现同一接口

二、核心架构

2.1 IAnalytical 接口

所有协议解析器实现 IAnalytical 接口:

// IAnalytical.cs
public interface IAnalytical
{
    // 协议标识
    string AgreementId { get; set; }
    
    // 输入/输出字段列表(用于配置界面)
    List<string> InputNameList { get; }
    List<string> OutputNameList { get; }
    
    // 生命周期管理
    void Start(GetDeviceMonitor d);
    void Stop();
    
    // 核心方法
    bool JudgeAnalytical(...);           // 协议识别
    Dictionary<string, string> AnalyticalData(...);  // 数据解析
    string CreateCommands(...);          // 命令生成
    string MaskSQL(...);                 // SQL 生成
}

2.2 协议识别机制

系统收到原始数据后,遍历所有已加载的协议,找到匹配的解析器:

// HaoPuServer.cs - LogicAnalytical()
private void LogicAnalytical(ref ReceivedDataEventArgs e)
{
    foreach (IAnalytical analytical in this.Agreement.Values)
    {
        if (analytical.JudgeAnalytical(
            e.Clientid, e.RequestHex, 
            ref startIndex, ref startLength, 
            ref isValid, ref rtuAddress, ref code, 
            ref extend, ref deviceId, ref deviceName, 
            ref monitorId, ref monitorName, 
            ref errorMsg, this.Config.monitorMana, this.Config.deviceMana))
        {
            // 找到匹配的协议,跳出循环
            e.Agreementid = analytical.AgreementId;
            break;
        }
    }
}

每个协议的 JudgeAnalytical() 方法自己判断数据是否属于自己,匹配成功后 break,返回 AgreementId,后续解析用对应的协议实例。


三、动态加载

3.1 Agreement 配置表

协议信息存在数据库 Agreement 表里:

字段 说明 示例
id 协议 ID DLL001
dllpath DLL 文件路径 Drive\Rrs.CyyRtu.dll
classname 类全名 Rrs.CyyRtu.Rtu
desc 描述 标准 RTU 协议
status 启用状态 True/False

3.2 运行时加载

系统启动时遍历 Agreement 表,动态加载启用的协议:

// HaoPuServer.cs - AgreementInit()
private bool AgreementInit(Agreement e)
{
    try
    {
        string startupPath = Application.StartupPath;
        
        // 1. 从 DLL 文件加载程序集
        Assembly assembly = Assembly.LoadFile(startupPath + "\\" + e.DllPath);
        
        // 2. 创建协议解析器实例
        IAnalytical analytical = (IAnalytical)assembly.CreateInstance(e.ClassName);
        
        // 3. 设置协议 ID
        analytical.AgreementId = e.ID;
        
        // 4. 启动协议(注册回调)
        analytical.Start(new GetDeviceMonitor(this.GetDeviceMonitor));
        
        // 5. 加入协议字典
        this.Agreement.Add(analytical.AgreementId, analytical);
        
        return true;
    }
    catch (Exception ex)
    {
        // 记录错误日志
        return false;
    }
}

3.3 协议字典

加载后的协议存在字典里,键是 AgreementId

// HaoPuServer.cs
private Dictionary<string, IAnalytical> Agreement;

O(1) 查找,支持运行时增删,配合 lock 保证线程安全。


四、协议实现示例

4.1 Modbus 协议(Moudbs)

// Rrs.Moudbs/Moudbs.cs
public class Moudbs : IAnalytical
{
    private string agreementId;
    
    public string AgreementId
    {
        get { return this.agreementId; }
        set { this.agreementId = value; }
    }
    
    // 输出字段列表(用于配置界面下拉框)
    public List<string> OutputNameList
    {
        get
        {
            return new List<string>(new string[] {
                "小数", "保留小数", "冲程", "冲次", 
                "位移包", "载荷", "电流", "电压", 
                "有功功率", "无功功率", "功率因数", 
                "累计电量", "压力", "年月日时分"
            });
        }
    }
    
    // 协议识别:检查功能码是否为 0x03(读保持寄存器)
    public bool JudgeAnalytical(...)
    {
        int code = ...;
        if (code == 0x03)
        {
            return true;
        }
        return false;
    }
    
    // 数据解析
    public Dictionary<string, string> AnalyticalData(Device d, Monitor m, string responsehex, object extend)
    {
        // 解析 Modbus 响应数据
        // 返回字段名 -> 字段值的字典
    }
}

4.2 SCDMA 协议(水表)

// Rrs.SCDMA/SCdma.cs
public class SCdma : IAnalytical
{
    public List<string> OutputNameList
    {
        get
        {
            return new List<string>(new string[] {
                "小数", "井站类型", "设备厂家", "通讯方式",
                "通讯协议", "波特率", "数据位", "停止位",
                "188电压", "188流量", "188水表状态", "188单位"
            });
        }
    }
    
    // 协议识别:检查是否为 188 协议帧
    public bool JudgeAnalytical(...)
    {
        // 解析 188 协议帧头
        // 匹配则返回 true
    }
}

4.3 RTU 协议(抽油机)

// Rrs.CyyRtu/Rtu.cs
public class Rtu : IAnalytical
{
    public List<string> OutputNameList
    {
        get
        {
            return new List<string>(new string[] {
                "小数", "保留小数", "冲程", "冲次", 
                "位移包", "载荷", "电流", "电压",
                "曲柄销子退扣", "测试类型", "故障停井选项"
            });
        }
    }
}

五、配置界面

5.1 协议管理

配置界面通过 FormConfig 做协议管理:

// FormConfig.cs - 加载协议配置
private void LoadDataConfig()
{
    this.agreementMana = new AgreementMana(this.IData);
    // ... 加载其他配置
}

// 启用/禁用协议
private void ActiveConfig()
{
    Agreement agreement = (Agreement)this.bindingSource1.Current;
    
    if (!agreement.Status)
    {
        // 启用协议
        if (this.ActiveAgreeEvent(agreement))
        {
            agreement.Status = true;
        }
    }
    else
    {
        // 禁用协议
        if (this.ActiveAgreeEvent(agreement))
        {
            agreement.Status = false;
        }
    }
}

5.2 动态字段绑定

配置界面根据协议的 InputNameListOutputNameList 动态生成下拉框:

// FormConfig.cs
IAnalytical analytical = (IAnalytical)Assembly.LoadFile(
    startupPath + "\\" + agreement.DllPath
).CreateInstance(agreement.ClassName);

// 绑定输入字段下拉框
DataGridViewComboBoxColumn col1 = (DataGridViewComboBoxColumn)this.dataGridView1.Columns["ModifiedOutput"];
col1.DataSource = analytical.InputNameList;

// 绑定输出字段下拉框
DataGridViewComboBoxColumn col2 = (DataGridViewComboBoxColumn)this.dataGridView1.Columns["ModifiedOutput"];
col2.DataSource = analytical.OutputNameList;

新增协议不用改配置界面代码,字段列表由协议 DLL 自己定义,配置界面和协议解耦。


六、完整数据流

在这里插入图片描述

关键词:插件化架构,协议解析,多协议适配,工业物联网


本文基于实际项目经验编写,代码已脱敏处理。如需完整源码或技术咨询,欢迎私信或评论交流。

更多推荐