如何优雅的利⽤C#
做协议解析
最近喜欢上了做协议解析,使⽤Java与做了很多的⼚家的产品的协议⽹关。从部标系列到第三⽅私有协议,通过协议解析过程中了解每款产品的特⾊与将来可能的应⽤场景。
下⾯我以⼀款第三⽅私有协议为例,利⽤C#语⾔进⾏优雅的做协议解析。原始数
据:2475201509260111002313101503464722331560113555309F00000000002D0500CB206800F064109326381A03
序号名称值(HEX)长度(Byte)说明
1协议头24
1固定为0x24,即ASCII的”$”符.
2终端ID号75201509265终端的ID号,固定为5字节长度. 75表⽰JT701.3协议版本号01101:表⽰协议版
本号4
终端类型号
1
0.5
1:常规可充电JT701.
5数据类型号10.5
1表明常规⼆进制定位数据,2表⽰报警数据,3表⽰盲区常规⼆进制定位数据
6数据长度00232
16数据内容长度,表明后⾯的数据⼀共有35个字节长.17数据内容长度,表明后⾯的数据⼀共有39个字节长.7⽇期1310153⽇⽉年表⽰.此处为2015年10⽉13号.
8时间0346473时分秒表⽰,为国际标准时.此处表⽰为03:46:47.
9
leaf的复数纬度
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速度001当前速度为5公⾥/⼩时.
13⽅向00
10x98 = 152,乘以2为304,即⽅向在304度.14⾥程
0000002D 4当前⾥程数为45公⾥.以16进制表⽰.invisibleman
15GPS卫星个数05
1GPS卫星个数,若为基站定位,则GPS卫星个数为00.16
绑定车辆ID
00CB2068
4
当前中⼼绑定的车辆ID号,以⼗六进制表⽰.
终端的各种状态及报警情况,最右边为低字节(Byte1),最左边为⾼字节(Byte2),详细定义如下:
字节.位说明
在线自助下单平台Byte1.BIT0
是否基站定位,1表⽰基站定位,0表
17终端状态00F02Byte1.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.
analystByte2.BIT6预留
Byte2.BIT7预留
18电量指⽰641电量指⽰,为当前采集到的电量值,⼗六进制位表⽰.0x64表⽰剩余电量100%,显⽰精度为5%,若为0xFF,则表⽰正在USB充电中.
19CELL ID位置
代码
1093263841093为CELL ID号, 2638为位置代码,即LAC.
20GSM信号质
量
1A1
表明当前GSM的信号强弱,1A表明为0x1A,即信号值为26. GSM
信号强度最⼤为31.
21区域报警ID051⽬前区域进出报警,扩展到最多10个区域,即标识区域报警时,同时显⽰当前进出的区域ID,1.7版本及以后使⽤
22设备状态3011具体标识含义见 4.4设备状态3说明23预留0F0F2预留。
23预留0F0F2预留。
24IMEI号863977039060871F8IMEI号,前⾯15位是BCD码,后⾯补⼀个F。通⽤版本(全是0F ⽆效)。
25预留2预留
26MCC2国家代码,中国460 27MNC1运营商代码移动00
28流⽔号031数据流⽔号,每发送⼀条数据,则累加1,从0x00~0xFF,终端重启流⽔号会清零.
其实像这种协议⼤多都具有通⽤性,神似,只是针对每家的产品粘包问题处理⽅式是个需要思考的问题,不过只要有固定的包头包尾,这些还是相对⽐较好做的。⽐如808协议的7E,这份协议⾥⾯的24。
很多刚刚接触做协议解析通常喜欢⽤字符串截取的⽅式,就是把收到的数据转成16进制,然后再根据16进制截取按协议进⾏解析,这种⽅法不但性能差,代码看起来也会很杂乱,下⾯我就⽤⼆进制流对上⾯的协议进⾏解析。其实Netty框架⾥⾯ByteBuf也是⼀样的原理。多说⼀句,也有Netty,我没怎么深⼊研究过,不知道是否好⽤,如果有时间可以去研究⼀下。
⾸先我们明确⼀点,⽆论是使⽤传统Socket还是netty,接收到的数据都是⼆进制流的⽅式,因为协议⽂档⽆法描述⼆进制流,所以会将⼆进制流转成16进制的⽅式进⾏描述,但是这就容易给⼈造成误导,认为我们接收到的数据也需要进⾏16进制转换。其实⽤⼆进制流做协议解析回更简单。
下⾯把我⽤C#写的⼀个⼩例⼦分享出来:
public static LocationProto LocationParr(byte[] bytes)
{
//定义定位数据实体类
LocationProto model = new LocationProto();
try
{
//跳过包头,然后解析设备ID
model.FAstID = 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.ToUInt32(bytes, 27);//⾥程
//解析GSM信号值
model.FCellSignal = bytes[31];
//解析设备状态
AstStatus astStatus = JT701Common.GetAstStatus(bytes.Skip(36).Take(2).ToArray(), model.FLocationType);
//解析是否基站定位(GPS定位>基站定位>不定位)
model.FLocationType = astStatus.FLocationType;
//解析报警类型
model.FAlarmType = astStatus.FAlarmType;
//是否需要回复终端
model.FNeedReplay = astStatus.FNeedReplay;
//解析锁绳状态
model.FLockRope = astStatus.FLockRope;
model.FLockRope = astStatus.FLockRope;
//解析锁状态
model.FLockStatus = astStatus.FLockStatus;
//解析后盖状态
model.FCoverStatus = astStatus.FCoverStatus;
//获取电量(255为充电中)
model.FBattery = bytes[38];
/
/解析⼩区码信息
model.FCELLID = BitConverter.ToUInt16(bytes, 39);
model.FLAC = BitConverter.ToUInt16(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.ToUInt16(bytes, 56) : model.FCELLID;
model.FMCC = BitConverter.ToUInt16(bytes, 58);
历年高考题
model.FMNC = bytes[60];
}
catch (Exception ex)
{
Log.Instance.Error("LocationParr:" + 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>wda
/// 时间格式转换
/// </summary>
/// <param name="bytes"></param>
/// <returns></returns>
public static DateTime GetDataTime(byte[] bytes)
{
return DateTime.ParExact(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 AstStatus GetAstStatus(byte[] bytes,int fLocationType)
{
try
{
AstStatus model = new AstStatus();
//低8位
if (fLocationType == 0)
{
model.FLocationType = bytes[0] & 0x01;
} el
{
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;