zoukankan      html  css  js  c++  java
  • 与下位机或设备的通信解析优化的一点功能:T4+动态编译

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

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

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

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

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

     1  public class DataItem
     2         {
     3             /// <summary>
     4             ///  数据项ID
     5             /// </summary>
     6             public ObjectId DataItemID { set; get; }
     7 
     8             /// <summary>
     9             /// 偏移量
    10             /// </summary>
    11             public int Pos { set; get; }
    12 
    13             /// <summary>
    14             /// 大小
    15             /// </summary>
    16             public int Size { set; get; }
    17 
    18             public int BitIndex { set; get; }
    19 
    20             /// <summary>
    21             /// 数据项数据库储存类型
    22             /// </summary>
    23             public DbDataTypeEnum DbType { set; get; }
    24 
    25             /// <summary>
    26             /// 数据项协议源字节数组中的数据类型
    27             /// </summary>
    28             public DataTypeEnum SourceType { set; get; }
    29 
    30             /// <summary>
    31             /// 计算因子
    32             /// </summary>
    33             public decimal Factor { set; get; }
    34 
    35             public string Key { set; get; }
    36         }
    37     
    38     /// <summary>
    39     /// 对应的数据库字段类型
    40     /// </summary>
    41     public enum DbDataTypeEnum
    42     {
    43         Int32 = 0,
    44 
    45         Int64 = 1,
    46 
    47         Double = 2,
    48 
    49         DateTime = 3,
    50 
    51         Decimal = 4,
    52 
    53         Boolean = 5
    54     }
    55 
    56     public enum DataTypeEnum
    57     {
    58         Int = 0,
    59 
    60         Short = 1,
    61 
    62         Datetime = 3,
    63 
    64         Long = 5,
    65 
    66         Decimal = 6,
    67 
    68         UInt = 7,
    69 
    70         Byte = 8,
    71 
    72         Boolean = 9,
    73 
    74         Bit = 10,
    75 
    76         UShort = 11,
    77 
    78         UByte = 12
    79     }
    View Code

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

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

     1 <#@ template language="C#" #>
     2 <#@ assembly name="System.Core" #>
     3 <#@ assembly name="Kugar.Core.NetCore" #>
     4 <#@ assembly name="Kugar.Device.Service.BLL" #>
     5 <#@ assembly name="Kugar.Device.Service.Data" #>
     6 <#@ assembly name="MongoDB.Bson" #>
     7 
     8 <#@ import namespace="System.Linq" #>
     9 <#@ import namespace="System.Text" #>
    10 <#@ import namespace="System.Collections.Generic" #>
    11 <#@ import namespace="Kugar.Core.BaseStruct" #>
    12 <#@ import namespace="MongoDB.Bson" #>
    13 
    14 using System;
    15 using System.Text;
    16 using Kugar.Core.BaseStruct;
    17 using Kugar.Core.ExtMethod;
    18 using Kugar.Core.Log;
    19 using Kugar.Device.Service.Data.DTO;
    20 using Kugar.Device.Service.Data.Enums;
    21 using MongoDB.Bson;
    22 
    23 namespace Kugar.Device.Service.BLL
    24 {
    25     <#
    26         var className="ProtocolExecutor_" + Protocol.Version.Replace('.','_') + "_" + this.GetNextClasID();
    27  #>
    28 
    29 
    30     public class <#=className #>:IProtocolExecutor
    31     {
    32         private string _version="";
    33         private ObjectId _protocolID;
    34         private readonly DateTime _baseDt=TimeZone.CurrentTimeZone.ToLocalTime(new System.DateTime(1970, 1, 1));
    35 
    36         public <#=className #> (ObjectId protocolID, string version)
    37         {
    38             _version=version;
    39             _protocolID=protocolID;
    40         }
    41 
    42         public ObjectId ProtocolID {get{return _protocolID;}}
    43 
    44         public BsonDocument Execute(byte[] data, int startIndex)
    45         {
    46             BsonDocument bson=new BsonDocument();
    47             <#
    48                 foreach(var item in Protocol.Items){ #>
    49             bson["<#=item.Key #>"]= <#= DecodeConfig(item,0) #>;
    50 <#
    51                 }
    52  #>
    53 
    54             return bson;
    55             
    56         }    
    57     }
    58 }
    View Code

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

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

      1 public partial class ProtocolExecuteTemplate
      2     {
      3         private static int _classID = 0;
      4 
      5         public ProtocolExecuteTemplate(DTO_ProtocolDataItem protocol)
      6         {
      7             Protocol = protocol;
      8 
      9         }
     10 
     11         
     12 
     13         public DTO_ProtocolDataItem Protocol { set; get; }
     14 
     15         public string DecodeConfig(DTO_ProtocolDataItem.DataItem item,int startIndex)
     16         {
     17             var str = "";
     18 
     19             switch (item.SourceType)
     20             {
     21                 case DataTypeEnum.Int:
     22                     str = $"BitConverter.ToInt32(data,startIndex + {startIndex + item.Pos})";
     23                     break;
     24                 case DataTypeEnum.UInt:
     25                     str = $"BitConverter.ToUInt32(data,startIndex + {startIndex + item.Pos})";
     26                     break;
     27                 case DataTypeEnum.Short:
     28                     str = $"BitConverter.ToInt16(data,startIndex + {startIndex + item.Pos})";
     29                     break;
     30                 case DataTypeEnum.Long:
     31                     str= $"BitConverter.ToInt64(data,startIndex + {startIndex + item.Pos})";
     32                     break;
     33                 case DataTypeEnum.Byte:
     34                 case DataTypeEnum.UByte:
     35                 case DataTypeEnum.Bit:
     36                     str = $"data[startIndex + {startIndex + item.Pos}]";
     37                     break;
     38                 case DataTypeEnum.UShort:
     39                     str = $"BitConverter.ToUInt16(data,startIndex+{startIndex + item.Pos})";
     40                     break;
     41                 default:
     42                     throw new ArgumentOutOfRangeException();
     43             }
     44 
     45             if (item.SourceType==DataTypeEnum.Bit)
     46             {
     47                 return byteToBit(str, item.BitIndex);
     48             }
     49             else
     50             {
     51                 return valueTODBType(str, item.Factor, item.DbType);
     52             }
     53         }
     54 
     55         private string valueTODBType(string sourceValue, decimal factor, DbDataTypeEnum dbType)
     56         {
     57             switch (dbType)
     58             {
     59                 case DbDataTypeEnum.Int32:
     60                     return $" new BsonInt32({(factor > 0 ? $"(int)({factor}{getDecimalShortChar(factor)} * {sourceValue})" : sourceValue)})";
     61                 case DbDataTypeEnum.Int64:
     62                     return $" new BsonInt64({(factor > 0 ? $"(long)({factor}{getDecimalShortChar(factor)} * {sourceValue})" : sourceValue)})";
     63                 case DbDataTypeEnum.Double:
     64                     return $"new BsonDouble({(factor > 0 ? $"(double)({factor}{getDecimalShortChar(factor)} * {sourceValue})" : $"(double){sourceValue}")})";
     65                 case DbDataTypeEnum.DateTime:
     66                     return $"new BsonDateTime(_baseDt.AddSeconds({sourceValue}))";
     67                 case DbDataTypeEnum.Decimal:
     68                     return $"new Decimal128({(factor > 0 ? $"(decimal)({factor}{getDecimalShortChar(factor)}  * {sourceValue})" : sourceValue)})";
     69                 default:
     70                     throw new ArgumentOutOfRangeException(nameof(dbType), dbType, null);
     71             }
     72         }
     73 
     74         private string byteToBit(string data, int index)
     75         {
     76             switch (index)
     77             {
     78                 case 0:
     79                 {
     80 
     81                     return $"(({data} & 1) ==1)";//(data & 1) == 1;
     82                 }
     83                 case 1:
     84                 {
     85                     return $"(({data} & 2) ==1)";// (data & 2) == 2;
     86                     }
     87                 case 2:
     88                 {
     89                     return $"(({data} & 4) ==1)";//(data & 4) == 4;
     90                     }
     91                 case 3:
     92                 {
     93                     return $"(({data} & 8) ==1)";//(data & 8) == 8;
     94                     }
     95                 case 4:
     96                 {
     97                     return $"(({data} & 16) ==1)";//(data & 16) == 16;
     98                     }
     99                 case 5:
    100                 {
    101                     return $"(({data} & 32) ==1)";//(data & 32) == 32;
    102                     }
    103                 case 6:
    104                 {
    105                     return $"(({data} & 64) ==1)";//(data & 64) == 64;
    106                     }
    107                 case 7:
    108                 {
    109                     return $"(({data} & 128) ==1)";//(data & 128) == 128;
    110                     }
    111                 default:
    112                     throw new ArgumentOutOfRangeException(nameof(index));
    113             }
    114 
    115             return $"(({data} & {index + 1}) ==1)";
    116         }
    117         
    118         /// <summary>
    119         /// 用于判断传入的fator是否需要使用deciaml进行运算,如果有小数点的,则是否decimal缩写m,,如果没有小数点,则使用普通的int类型
    120         /// </summary>
    121         /// <param name="value"></param>
    122         /// <returns></returns>
    123         private string getDecimalShortChar(decimal value)
    124         {
    125             return (value % 1) == 0 ? "" : "m";
    126         }
    127 
    128         public int GetNextClasID()
    129         {
    130             return Interlocked.Increment(ref _classID);
    131         }
    132     }
    View Code

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

     1     public class ProtocolExecutor_1_1_000
     2     {
     3         public BsonDocument Execute(byte[] data, int startIndex)
     4         {
     5             BsonDocument bson = new BsonDocument();
     6 
     7             bson["项目名1"] = new BsonInt32(BitConverter.ToInt32(data, startIndex + 偏移量));
     8             bson["项目名2"] = new BsonInt32(BitConverter.ToInt32(data, startIndex + 偏移量));
     9               。。。。。。。 //其他数据项
    10 
    11             return bson;
    12         }
    13     }

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

  • 相关阅读:
    居然就这么没有了
    RAID4 in WAFL
    网络存储导论第15章:Netapp产品分析
    radwareAPSolute应用前端解决方案全局负载均衡解决方案
    RAID , LVM and EVMS
    FND_STANDARD.SET_WHO
    基于基表的Form开发
    eclipse pydev 升级地址
    .net程序员应该知道的
    收集利用Jquery取得iframe中元素
  • 原文地址:https://www.cnblogs.com/kugar/p/10549982.html
Copyright © 2011-2022 走看看