去年接触的一个项目中,需要通过TCP与设备进行对接的,传的是Modbus协议的数据,然后后台需要可以动态配置协议解析的方式,即寄存器的解析方式,,配置信息有:Key,数据Index,源数据类型,数据库列类型,数据排列方式 

一开始使用的方式是,从数据库读取出协议的配置,然后在接收到数据的时候,循环每个配置项根据配置-----解析数据------转换类型----存临时列表,,后来改进了一下,配置项存缓存,,数据库修改的时候,同时更新缓存。。

但还是慢了一点,因为需一个配置大概是20-30个数据项,每一条数据都要for循环20-30次 ,再加上还有N个根据配置的数据类型去做转换的if判断,这么一套下来,也很耗时间,但待解析的数据量大的情况下,,相对也很耗资源。。。

最后的觉得方案是:利用T4生成C#的class源码+运行时编译成类,数据直接扔class里直接解析出结果,不需要循环,也不需要if判断,因为在t4生成源码的时候,已经根据配置处理完了,因此节省了很多的时间。

不过由于T4模板的IDE支持的很不好,不过好在运行时T4模板在IDE内生成出来的类是partial的,因此,可以把大部分的代码,放在外部的C#文件里。先来看数据项的配置信息:

  public class DataItem
{
/// <summary>
/// 数据项ID
/// </summary>
public ObjectId DataItemID { set; get; } /// <summary>
/// 偏移量
/// </summary>
public int Pos { set; get; } /// <summary>
/// 大小
/// </summary>
public int Size { set; get; } public int BitIndex { set; get; } /// <summary>
/// 数据项数据库储存类型
/// </summary>
public DbDataTypeEnum DbType { set; get; } /// <summary>
/// 数据项协议源字节数组中的数据类型
/// </summary>
public DataTypeEnum SourceType { set; get; } /// <summary>
/// 计算因子
/// </summary>
public decimal Factor { set; get; } public string Key { set; get; }
} /// <summary>
/// 对应的数据库字段类型
/// </summary>
public enum DbDataTypeEnum
{
Int32 = , Int64 = , Double = , DateTime = , Decimal = , Boolean =
} public enum DataTypeEnum
{
Int = , Short = , Datetime = , Long = , Decimal = , UInt = , Byte = , Boolean = , Bit = , UShort = , UByte =
}

这里为什么要区分源数据和数据库数据类型呢?主要是因为设备一般是int,short,double,float等类型,但是,对应到数据库,有时候比如说使用mongodb,之类的数据库,不一定有完全匹配的,因此需要区分两种数据项,

再来就是T4的模板  ProtocolExecuteTemplate.tt:

 <#@ template language="C#" #>
<#@ assembly name="System.Core" #>
<#@ assembly name="Kugar.Core.NetCore" #>
<#@ assembly name="Kugar.Device.Service.BLL" #>
<#@ assembly name="Kugar.Device.Service.Data" #>
<#@ assembly name="MongoDB.Bson" #> <#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="Kugar.Core.BaseStruct" #>
<#@ import namespace="MongoDB.Bson" #> using System;
using System.Text;
using Kugar.Core.BaseStruct;
using Kugar.Core.ExtMethod;
using Kugar.Core.Log;
using Kugar.Device.Service.Data.DTO;
using Kugar.Device.Service.Data.Enums;
using MongoDB.Bson; namespace Kugar.Device.Service.BLL
{
<#
var className="ProtocolExecutor_" + Protocol.Version.Replace('.','_') + "_" + this.GetNextClasID();
#> public class <#=className #>:IProtocolExecutor
{
private string _version="";
private ObjectId _protocolID;
private readonly DateTime _baseDt=TimeZone.CurrentTimeZone.ToLocalTime(new System.DateTime(, , )); public <#=className #> (ObjectId protocolID, string version)
{
_version=version;
_protocolID=protocolID;
} public ObjectId ProtocolID {get{return _protocolID;}} public BsonDocument Execute(byte[] data, int startIndex)
{
BsonDocument bson=new BsonDocument();
<#
foreach(var item in Protocol.Items){ #>
bson["<#=item.Key #>"]= <#= DecodeConfig(item,) #>;
<#
}
#> return bson; }
}
}

在直接在循环里输出解析后的语句,并且生成的类名记得后面加多一个随机数。。。。

然后再加一个ProtocolExecuteTemplate.Part.cs的部分类,补全T4模板的功能,因为在T4里IDE支持的不好,,写代码确实难受,,没直接写C#舒服:

 public partial class ProtocolExecuteTemplate
{
private static int _classID = ; public ProtocolExecuteTemplate(DTO_ProtocolDataItem protocol)
{
Protocol = protocol; } public DTO_ProtocolDataItem Protocol { set; get; } public string DecodeConfig(DTO_ProtocolDataItem.DataItem item,int startIndex)
{
var str = ""; switch (item.SourceType)
{
case DataTypeEnum.Int:
str = $"BitConverter.ToInt32(data,startIndex + {startIndex + item.Pos})";
break;
case DataTypeEnum.UInt:
str = $"BitConverter.ToUInt32(data,startIndex + {startIndex + item.Pos})";
break;
case DataTypeEnum.Short:
str = $"BitConverter.ToInt16(data,startIndex + {startIndex + item.Pos})";
break;
case DataTypeEnum.Long:
str= $"BitConverter.ToInt64(data,startIndex + {startIndex + item.Pos})";
break;
case DataTypeEnum.Byte:
case DataTypeEnum.UByte:
case DataTypeEnum.Bit:
str = $"data[startIndex + {startIndex + item.Pos}]";
break;
case DataTypeEnum.UShort:
str = $"BitConverter.ToUInt16(data,startIndex+{startIndex + item.Pos})";
break;
default:
throw new ArgumentOutOfRangeException();
} if (item.SourceType==DataTypeEnum.Bit)
{
return byteToBit(str, item.BitIndex);
}
else
{
return valueTODBType(str, item.Factor, item.DbType);
}
} private string valueTODBType(string sourceValue, decimal factor, DbDataTypeEnum dbType)
{
switch (dbType)
{
case DbDataTypeEnum.Int32:
return $" new BsonInt32({(factor > 0 ? $"(int)({factor}{getDecimalShortChar(factor)} * {sourceValue})" : sourceValue)})";
case DbDataTypeEnum.Int64:
return $" new BsonInt64({(factor > 0 ? $"(long)({factor}{getDecimalShortChar(factor)} * {sourceValue})" : sourceValue)})";
case DbDataTypeEnum.Double:
return $"new BsonDouble({(factor > 0 ? $"(double)({factor}{getDecimalShortChar(factor)} * {sourceValue})" : $"(double){sourceValue}")})";
case DbDataTypeEnum.DateTime:
return $"new BsonDateTime(_baseDt.AddSeconds({sourceValue}))";
case DbDataTypeEnum.Decimal:
return $"new Decimal128({(factor > 0 ? $"(decimal)({factor}{getDecimalShortChar(factor)} * {sourceValue})" : sourceValue)})";
default:
throw new ArgumentOutOfRangeException(nameof(dbType), dbType, null);
}
} private string byteToBit(string data, int index)
{
switch (index)
{
case :
{ return $"(({data} & 1) ==1)";//(data & 1) == 1;
}
case :
{
return $"(({data} & 2) ==1)";// (data & 2) == 2;
}
case :
{
return $"(({data} & 4) ==1)";//(data & 4) == 4;
}
case :
{
return $"(({data} & 8) ==1)";//(data & 8) == 8;
}
case :
{
return $"(({data} & 16) ==1)";//(data & 16) == 16;
}
case :
{
return $"(({data} & 32) ==1)";//(data & 32) == 32;
}
case :
{
return $"(({data} & 64) ==1)";//(data & 64) == 64;
}
case :
{
return $"(({data} & 128) ==1)";//(data & 128) == 128;
}
default:
throw new ArgumentOutOfRangeException(nameof(index));
} return $"(({data} & {index + 1}) ==1)";
} /// <summary>
/// 用于判断传入的fator是否需要使用deciaml进行运算,如果有小数点的,则是否decimal缩写m,,如果没有小数点,则使用普通的int类型
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
private string getDecimalShortChar(decimal value)
{
return (value % ) == ? "" : "m";
} public int GetNextClasID()
{
return Interlocked.Increment(ref _classID);
}
}

这样,在运行时,即可直接生成可用于解析的类了,而且也不需要for循环判断,生成出来的类如:

     public class ProtocolExecutor_1_1_000
{
public BsonDocument Execute(byte[] data, int startIndex)
{
BsonDocument bson = new BsonDocument(); bson["项目名1"] = new BsonInt32(BitConverter.ToInt32(data, startIndex + 偏移量));
bson["项目名2"] = new BsonInt32(BitConverter.ToInt32(data, startIndex + 偏移量));
。。。。。。。 //其他数据项 return bson;
}
}

到这一步,就可以根绝配置项生成出对应的C#代码了,剩下的就是动态编译的事情了、将该代码编译出运行时Type,然后传入数据----解析出数据

与下位机或设备的通信解析优化的一点功能:T4+动态编译的更多相关文章

  1. STM32 使用Cubemx 建一个USB(HID)设备下位机,实现数据收发

    这里我主要说一下如何做一个USB下位机,这里主要分3部分:1.建立工程:2.添加报文描述符:3.数据的传输.这里就不讲USB的理论知识了,有想要了解的自行百度一下就可以了. 建立工程:工程建立参考:h ...

  2. 转载 STM32 使用Cubemx 建一个USB(HID)设备下位机,实现数据收发

    STM32 使用Cubemx 建一个USB(HID)设备下位机,实现数据收发  本文转载自 https://www.cnblogs.com/xingboy/p/9913963.html 这里我主要说一 ...

  3. C#使用struct直接转换下位机数据

    编写上位机与下位机通信的时候,涉及到协议的转换,比较多会使用到二进制.传统的方法,是将数据整体获取到byte数组中,然后逐字节对数据进行解析.这样操作工作量比较大,对于较长数据段更容易计算位置出错. ...

  4. C#做上位机软件——绘图并传输给下位机

    拿到任务之后首先分成了几个部分: 1.绘图.学习了GDI+ 2.图片保存. 3.将图片转换成byte[].由于使用Socket通信,只能传输byte[]数据,所以这一步是向下位机传输的关键. 相应地, ...

  5. 下位机多个".c, .h"文件的相互包含及排版

    一.背景: 自从接触单片机编程以来,由于工作上的需要,不可避免的时常会接手别人的代码,但常常由于上一位同事的编码随意性有点大,导致可读性非常的差,有时候不得不完全舍弃原有代码,推倒重来,无形中增加了工 ...

  6. MSP430G2333下位机乘法运算需要注意的一个问题

    背景: 最近负责为主板管理电源的电源管理模块编写软体,使用的MCU为MSP430G2333.功能上很简单,即通过板子上的硬件拨码设定,或者通过IIC与主板通信,由主板的BIOS决定开机及关机的延时供电 ...

  7. vb配置下位机CAN寄存器小结

    2011-12-14 18:44:32 效果图 1,完成设计(由于没有eeprom等存储设备,所以每次上电后需要通过串口配置某些寄存器).在设计中,列出技术评估难度,并进行尝试,参看<我的设计& ...

  8. C# WPF上位机实现和下位机TCP通讯

    下位机使用北京大华程控电源DH1766-1,上位机使用WPF.实现了电压电流实时采集,曲线显示.上午在公司调试成功,手头没有程控电源,使用TCP服务端模拟.昨天写的TCP服务端正好排上用场. 界面如下 ...

  9. linux设备驱动程序-i2c(2)-adapter和设备树的解析

    linux设备驱动程序-i2c(2)-adapter和设备树的解析 (注: 基于beagle bone green开发板,linux4.14内核版本) 在本系列linux内核i2c框架的前两篇,分别讲 ...

随机推荐

  1. 非阻塞connect:Web客户程序

      一.web.h #include <stdio.h> #include <netdb.h> #include <errno.h> #include <fc ...

  2. python selectsort

    # -*- coding: utf-8 -*-"""------------------------------------------------- File Name ...

  3. JavaScript之iframe页面间通信

    [1] iframe父子页面间通信 1.相互调用对方的方法 |> 子级页面调用父级页面 window.parent.父级页面方法(args) |> 父级页面调用子级页面 document. ...

  4. Eclipse使用Git检出项目

    1.打开Eclipse——File——Import...: 2.在弹出的Import框中选择Git——Projects from Git——NEXT: 3.选择Clone URI——Next: 4.输 ...

  5. 记一次解决netty半包问题的经历

    最近学习了netty,想写一个简单的rpc,结果发现发送消息时遇到难题了,网上搜了一下,这种情况是半包问题和粘包问题,主要是出现在并发高一些的时候. talk is cheap 客户端编码: prot ...

  6. DBA_TABLES ALL_TABLES USER_TABLES

    DBA_TABLES >= ALL_TABLES >= USER_TABLES DBA_TABLES意为DBA拥有的或可以访问的所有的关系表. ALL_TABLES意为某一用户拥有的或可以 ...

  7. Sping 里面的适配器模式的实现

    适配器模式----------设计模式最近在看SpringMVC源码,从中看到了比较优秀的设计模式所以来分享下. 1.适配器模式(Adapter):将一个类的接口转换成客户希望的另外一个接口,Adap ...

  8. 阻塞IO,非阻塞IO,IO多路复用模型

    #服务端 import socket sk = socket.socket() sk.bind(('127.0.0.1',8080)) sk.listen() while True: conn, ad ...

  9. 单机多es容器服务部署的网络模式

    3.1 Bridge模式的拓扑 当Docker server启动时,会在主机上创建一个名为docker0的虚拟网桥,此主机上启动的Docker容器会连接到这个虚拟网桥上.虚拟网桥的工作方式和物理交换机 ...

  10. git(windows)

    windows下比较比较好用的git客户端: 1. msysgit + TortoiseGit(乌龟git) 2. GitHub for Windows 3. Git Extensions