如何优雅的利用C#做协议解析

最近喜欢上了做协议解析,最近使用Java与.NET做了很多的厂家的产品的协议网关。从部标系列到第三方私有协议,通过协议解析过程中了解每款产品的特色与将来可能的应用场景。

下面我以一款第三方私有协议为例,利用C#语言进行优雅的做协议解析。

原始数据:2475201509260111002313101503464722331560113555309F00000000002D0500CB206800F064109326381A03

序号

名称

(HEX)

长度(Byte)

说明

1

协议头

24

1

固定为0x24,即ASCII的”$”符.

2

终端ID号

7520150926

5

终端的ID号,固定为5字节长度. 75表示JT701.

3

协议版本号

01

1

01:表示协议版本号

4

终端类型

1

0.5

1:常规可充电JT701.

5

数据类型号

1

0.5

1表明常规二进制定位数据,2表示报警数据,3表示盲区常规二进制定位数据

6

数据长度

0023

2

16数据内容长度,表明后面的数据一共有35个字节长.

17数据内容长度,表明后面的数据一共有39个字节长.

7

日期

131015

3

日月年表示.此处为2015年10月13号.

8

时间

034647

3

时分秒表示,为国际标准时.此处表示为03:46:47.

9

纬度

22331560

4

22331560,按照DDMM.MMMM格式定义,此纬度值为: 2233. 1560.

10

经度

113555309

4.5

113555309,按照DDDMM.MMMM格式定义,此经度值为: 11355. 5309.

11

位指示

F

0.5

F = 1111,GPS定位,西经,北纬.

E = 1110,非GPS定位,西经,北纬.

最右边的位为BIT0,最左边的位为BIT3.

1: BIT3为固定值.

1: BIT2表示东经,如果为0表示西经.

1: BIT1 表示北纬,如果为0表示南纬.

1: BIT0 表示定位,如果为0表示GPS不定位.

12

速度

00

1

当前速度为5公里/小时.

13

方向

00

1

0x98 = 152,乘以2为304,即方向在304度.

14

里程

0000002D

4

当前里程数为45公里.以16进制表示.

15

GPS卫星个数

05

1

GPS卫星个数,若为基站定位,则GPS卫星个数为00.

16

绑定车辆ID

00CB2068

4

当前中心绑定的车辆ID号,以十六进制表示.

17

终端状态

00F0

2

终端的各种状态及报警情况,最右边为低字节(Byte1),最左边为高字节(Byte2),详细定义如下:

字节.位

说明

Byte1.BIT0

是否基站定位,1表示基站定位,0表示非基站定位.

Byte1.BIT1

定位数据固定为0,报警数据为1表示进电子围栏报警,否则为0.

Byte1.BIT2

定位数据固定为0,报警数据为1表示出电子围栏报警,否则为0.

Byte1.BIT3

定位数据固定为0,报警数据为1表示锁挂绳剪断报警,否则为0.

Byte1.BIT4

定位数据固定为0,报警数据为1表示震动报警,否则为0.

Byte1.BIT5

定位数据1表示定位数据需要发送确认,否则为0,报警数据固定为1,表示需要发送确认.

Byte1.BIT6

锁挂绳状态,插入状态为1,否则为0.

Byte1.BIT7

锁电机状态,关锁状态位1,否则为0.

Byte2.BIT0

定位数据固定为0,报警数据为1表示长时间开锁报警,否则为0.

Byte2.BIT1

定位数据固定为0,报警数据为1表示密码连续输错5次报警,否则为0.

Byte2.BIT2

定位数据固定为0,报警数据为1表示刷非法卡报警,否则为0.

Byte2.BIT3

定位数据固定为0,报警数据为1表示低电报警,否则为0.

Byte2.BIT4

定位数据固定为0,报警数据为1表示开后盖报警,否则为0.

Byte2.BIT5

后盖开关状态,关盖状态为1,否则为0.

Byte2.BIT6

预留

Byte2.BIT7

预留

 

18

电量指示

64

1

电量指示,为当前采集到的电量值,十六进制位表示.0x64表示剩余电量100%,显示精度为5%,若为0xFF,则表示正在USB充电中.

19

CELL ID位置

代码

10932638

4

1093为CELL ID号, 2638为位置代码,即LAC.

20

GSM信号质量

1A

1

表明当前GSM的信号强弱,1A表明为0x1A,即信号值为26. GSM信号强度最大为31.

21

区域报警ID

05

1

目前区域进出报警,扩展到最多10个区域,即标识区域报警时,同时显示当前进出的区域ID,1.7版本及以后使用

22

设备状态3

01

1

具体标识含义见 4.4设备状态3说明

23

预留

0F0F

2

预留。

24

IMEI号

863977039060871F

8

IMEI号,前面15位是BCD码,后面补一个F。通用版本(全是0F无效)。

25

预留

 

2

预留

26

MCC

 

2

国家代码,中国460

27

MNC

 

1

运营商代码移动00

28

流水号

03

1

数据流水号,每发送一条数据,则累加1,从0x00~0xFF,终端重启流水号会清零.

其实像这种协议大多都具有通用性,神似,只是针对每家的产品粘包问题处理方式是个需要思考的问题,不过只要有固定的包头包尾,这些还是相对比较好做的。比如808协议的7E,这份协议里面的24。

很多刚刚接触做协议解析通常喜欢用字符串截取的方式,就是把收到的数据转成16进制,然后再根据16进制截取按协议进行解析,这种方法不但性能差,代码看起来也会很杂乱,下面我就用二进制流对上面的协议进行解析。其实Netty框架里面ByteBuf也是一样的原理。多说一句,.NET也有Netty,我没怎么深入研究过,不知道是否好用,如果有时间可以去研究一下。

首先我们明确一点,无论是使用传统Socket还是netty,接收到的数据都是二进制流的方式,因为协议文档无法描述二进制流,所以会将二进制流转成16进制的方式进行描述,但是这就容易给人造成误导,认为我们接收到的数据也需要进行16进制转换。其实用二进制流做协议解析回更简单。

下面把我用C#写的一个小例子分享出来:

 public static LocationProto LocationParser(byte[] bytes)
        {
            //定义定位数据实体类
            LocationProto model = new LocationProto();
            try
            {
                //跳过包头,然后解析设备ID
                model.FAssetID = CommonClass.ByteToHexStr(bytes.Skip(1).Take(5).ToArray());
                //得到数据长度
                int length = BitConverter.ToInt16(bytes, 8);
                //获取时间段,转成我们识别的"yyyy-MM-dd HH:mm:ss"格式
                model.FGPSTime = CommonClass.GetDataTime(bytes.Skip(10).Take(6).ToArray());
                //这里是数据接收时间,是我自己定义网关接收到数据的时间,因为我系统用的是格林威治时间
                model.FRecvTime = DateTime.UtcNow;
                //解析定位信息,经度,纬度,定位状态,做了一个方法的封装
                PositioningStatus positionStatus = JT701Common.GetPositioningStatus(bytes.Skip(16).Take(9).ToArray());
                model.FLatitude = positionStatus.FLatitude;//纬度
                model.FLongitude = positionStatus.FLongitude;//经度
                model.FLocationType = positionStatus.FLocationType;//定位状态
                //解析速度
                model.FSpeed = bytes[25];
                //解析方向
                model.FDirection = bytes[26] * 2;
                //解析里程
                model.FMileage = BitConverter.ToInt32(bytes, 27);//里程
                //解析GSM信号值
                model.FCellSignal = bytes[31];
                //解析设备状态
                AssetStatus assetStatus = JT701Common.GetAssetStatus(bytes.Skip(36).Take(2).ToArray(), model.FLocationType);
                //解析是否基站定位(GPS定位>基站定位>不定位)
                model.FLocationType = assetStatus.FLocationType;
                //解析报警类型
                model.FAlarmType = assetStatus.FAlarmType;
                //是否需要回复终端
                model.FNeedReplay = assetStatus.FNeedReplay;
                //解析锁绳状态
                model.FLockRope = assetStatus.FLockRope;
                //解析锁状态
                model.FLockStatus = assetStatus.FLockStatus;
                //解析后盖状态
                model.FCoverStatus = assetStatus.FCoverStatus;
                //获取电量(255为充电中)
                model.FBattery = bytes[38];
                //解析小区码信息
                model.FCELLID = BitConverter.ToInt16(bytes, 39);
                model.FLAC = BitConverter.ToInt16(bytes, 41);
                //解析GPS卫星个数
                model.FGPSSignal = bytes[43];
                //解析区域ID
                model.FAreaId = bytes[44];
                //得到唤醒源
                model.FWakeSource = bytes[45] & 0x07;
                //是否GSM信号弱报警
                model.FGSMAlarm = bytes[45] & 0x40;
                //得到IMEI号
                model.FIMEI = CommonClass.ByteToHexStr(bytes.Skip(48).Take(8).ToArray());
                model.FCELLID = model.FCELLID == 0 ? BitConverter.ToInt16(bytes, 56) : model.FCELLID;
                model.FMCC = BitConverter.ToInt16(bytes, 58);
                model.FMNC = bytes[60];
            }
            catch (Exception ex)
            {
                Log.Instance.Error("LocationParser:" + ex.Message);
            }
            return model;
        }

里面用到的一些主要方法:

 /// <summary>
        /// 字节数组转16进制字符串:空格分隔
        /// </summary>
        /// <param name="byteDatas"></param>
        /// <returns></returns>
        public static string ByteToHexStr(byte[] byteDatas)
        {
            StringBuilder builder = new StringBuilder();
            for (int i = 0; i < byteDatas.Length; i++)
            {
                builder.Append(string.Format("{0:X2}", byteDatas[i]));
            }
            return builder.ToString().Trim();
        }
    /// <summary>
        /// 时间格式转换
        /// </summary>
        /// <param name="bytes"></param>
        /// <returns></returns>
        public static DateTime GetDataTime(byte[] bytes)
        {
            return DateTime.ParseExact(ByteToHexStr(bytes), "ddMMyyHHmmss", System.Globalization.CultureInfo.CurrentCulture);
        }
  /// <summary>
        /// 获取定位状态
        /// </summary>
        /// <param name="bytes"></param>
        /// <returns></returns>
        public static PositioningStatus GetPositioningStatus(byte[] bytes)
        {
            try
            {
                PositioningStatus model = new PositioningStatus();
                model.FLatitude = CommonClass.GetLatLong60(bytes.Skip(0).Take(4).ToArray());
                model.FLongitude = CommonClass.GetLatLong60(bytes.Skip(4).Take(5).ToArray());
                model.FLocationType = bytes[8] & 0x01;
                int latStatus = bytes[8] & 0x02;
                if (latStatus == 0)
                {
                    model.FLatitude = -model.FLatitude;
                }
                int lonStatus = bytes[8] & 0x04;
                if (lonStatus == 0)
                {
                    model.FLongitude = -model.FLongitude;
                }
                return model;
            }
            catch (Exception ex)
            {
                Log.Instance.Error("GetPositioningStatus:"+ex.Message);
                return null;
            }
        }
  /// <summary>
        /// 获取设备状态
        /// </summary>
        /// <param name="bytes"></param>
        /// <returns></returns>
        public static AssetStatus GetAssetStatus(byte[] bytes,int fLocationType)
        {
            try
            {
                AssetStatus model = new AssetStatus();
                //低8位
                if (fLocationType == 0)
                {
                    model.FLocationType = bytes[0] & 0x01;
                } else
                {
                    model.FLocationType = fLocationType;
                }
                int infenceAlarm= bytes[0] & 0x02;
                if (infenceAlarm == 1)
                {
                    model.FAlarmType = (int)AlarmTypeEnum.LOCK_ALARM_9;
                }
               
                int outfenceAlarm = bytes[0] & 0x04;
                if (outfenceAlarm == 1)
                {
                    model.FAlarmType = (int)AlarmTypeEnum.LOCK_ALARM_10;
                }
              
                int cutoffAlarm = bytes[0] & 0x08;
                if (cutoffAlarm == 1)
                {
                    model.FAlarmType = (int)AlarmTypeEnum.LOCK_ALARM_1;
                }
             
                int shockAlarm = bytes[0] & 0x10;
                if (shockAlarm == 1)
                {
                    model.FAlarmType = (int)AlarmTypeEnum.LOCK_ALARM_2;
                }
                else
                {
                    model.FAlarmType = -1;
                }
                model.FNeedReplay = bytes[0] & 0x20;
                model.FLockRope = bytes[0] & 0x40;
                model.FLockStatus = bytes[0] & 0x80;
                //高8位
                int longTime = bytes[1] & 0x01;
                if (longTime == 1)
                {
                    model.FAlarmType = (int)AlarmTypeEnum.LOCK_ALARM_3;
                }
                int fiveError = bytes[1] & 0x02;
                if (fiveError == 1)
                {
                    model.FAlarmType = (int)AlarmTypeEnum.LOCK_ALARM_4;
                }
               
                int swipeCardAlarm = bytes[1] & 0x04;
                if (swipeCardAlarm == 1)
                {
                    model.FAlarmType = (int)AlarmTypeEnum.LOCK_ALARM_5;
                }
               
                int lowPower = bytes[1] & 0x08;
                if (lowPower == 1)
                {
                    model.FAlarmType = (int)AlarmTypeEnum.LOCK_ALARM_6;
                }
                
                int unCover = bytes[1] & 0x10;
                if (unCover == 1)
                {
                    model.FAlarmType = (int)AlarmTypeEnum.LOCK_ALARM_7;
                }
              
                model.FCoverStatus = bytes[1] & 0x20;
                int stuckAlarm = bytes[1] & 0x40;
                if (stuckAlarm == 1)
                {
                    model.FAlarmType = (int)AlarmTypeEnum.LOCK_ALARM_8;
                }
               
                return model;
            }
            catch (Exception ex)
            {
                Log.Instance.Error("GetAssetStatus:" + ex.Message);
                return null;
            }
        }
   /// <summary>
        /// 经纬度计算
        /// </summary>
        /// <param name="bytes"></param>
        /// <returns></returns>
        public static double GetLatLong60(byte[] bytes)
        {
            try
            {
                string locStr = ByteToHexStr(bytes);
                if (locStr.Length < 9)
                {
                    locStr = locStr.PadLeft(9, '0');
                }
                else
                {
                    locStr = locStr.Substring(0, 9);
                }
                var head = Convert.ToDouble(locStr.Remove(3));
                var bodyStr = locStr.Substring(3, locStr.Length - 3);
                var body = Convert.ToDouble(bodyStr) / 10000;
                head += body / 60;
                return head;
            }
            catch (Exception ex)
            {
                // txtHelper.WriteException(ex, "locStr:" + locStr, false);
                return 0;
            }
        }

808的解析也是类似,由于很多同行靠808的源码养家糊口,这里就不以808为例了,有兴趣的朋友可以一起学习交流!

猜你喜欢

转载自blog.csdn.net/qq_17486399/article/details/107480619