TJ/T808 终端通讯协议设计与实现(码农本色)
由于公司项目涉及到相关技术,对于平常写WEB的技术人员来说对这人来说比较默生;为了让下面的技术人员更好地对这个协议的实施,所以单独针对这个协议进行了分析和设计,以更于后期更好指导相关开发工作。由于自己对网络这一块比较熟悉,之前也有过想法实现TJ/T808相关协议,只是一直没这个动力去做;恰好碰到这次机会顺更也动动手写下代码。
TJ/T808协议了解
其实看了一下这个协议,在设计上感觉有些不太合理,不过已经是国标的东西也没有什么可异议的;总体来说这个协议还是比较简单,以下是这个协议的基础部分:
为了方便所以截个图就算了,图上已经描述的协议的组成部门和一些主要细节;后面的基本就是一些具体消息体的技术,有需要的朋友可以看协议的详细文档。
设计
协议整体分为两大部分,消息头和消息体;在消息中还有一个相对处理工作比较的多信息,就是消息属性。所以在设计上主要分为以下几大部分:协议封装和解释,消息结构定义,消息体结构定义和消息体属性结构定义;部体结构设计如下:
为了达到更好的通用性,在设计上通过协议封装和解释接口和最终网络通讯环节隔离;这样在集成和开发上都具备比较高的灵活性。
IProtocolBuffer协议
首先我们需要一个规范来定义网络数据封装和解释,并且可以和网络处理层进行一个良好的隔离;这个协议接口的主要功能包括:组包,拆包,相关基础类型的读取和写入。
public interface IProtocolBuffer
{
void Write(byte[] data);
void Write(byte data);
byte Read();
byte[] Read(int length);
bool Import(byte value);
int Import(byte[] data, int offset, int count);
void ReadSubBuffer(IProtocolBuffer buffer, int count);
void WriteSubBuffer(IProtocolBuffer buffer);
void Reset();
int Length { get; }
void SetLength(int length);
int Postion { get; set; }
byte[] Array { get; }
void Write(ushort value);
void Write(uint value);
void WriteBCD(string value);
ushort ReadUInt16();
uint ReadUInt();
string ReadBCD(int length);
}
在实现上需要注意一些细节,由于协议规定是大端处理,而C#是小端的,所以在处理一些数据上需要进行一些反转处理,以下是针对shot,int,long等基础类型处理代码:
public static short SwapInt16(short v)
{
return (short)(((v & 0xff) << 8) | ((v >> 8) & 0xff));
}
public static ushort SwapUInt16(ushort v)
{
return (ushort)(((v & 0xff) << 8) | ((v >> 8) & 0xff));
}
public static int SwapInt32(int v)
{
return (int)(((SwapInt16((short)v) & 0xffff) << 0x10) |
(SwapInt16((short)(v >> 0x10)) & 0xffff));
}
public static uint SwapUInt32(uint v)
{
return (uint)(((SwapUInt16((ushort)v) & 0xffff) << 0x10) |
(SwapUInt16((ushort)(v >> 0x10)) & 0xffff));
}
public static long SwapInt64(long v)
{
return (long)(((SwapInt32((int)v) & 0xffffffffL) << 0x20) |
(SwapInt32((int)(v >> 0x20)) & 0xffffffffL));
}
public static ulong SwapUInt64(ulong v)
{
return (ulong)(((SwapUInt32((uint)v) & 0xffffffffL) << 0x20) |
(SwapUInt32((uint)(v >> 0x20)) & 0xffffffffL));
}
在这个协议上还有一个需要注意的地方,由于协议采用单字节作为开始和结束标识,对于相关字符需要进行一个转议处理;以下是主要部分的代码封装:
private ProtocolBuffer OnWrite(byte value)
{
mArray[mPostion] = value;
mPostion++;
mLength++;
return this;
} public bool Import(byte value)
{
if (value == PROTOBUF_TAG)
{
OnWrite(value);
if (!mProtocolStart)
{
mProtocolStart = true;
}
else
{
mPostion = 0;
return true;
}
}
else
{
if (mProtocolStart)
{
OnWrite(value);
}
}
return false;
} public int Import(byte[] data, int offset, int count)
{
int result = 0;
for (int i = offset; i < count; i++)
{
result++;
byte value = data[i];
if (Import(value))
return result;
}
return -1;
} public byte Read()
{
byte result = mArray[mPostion];
mPostion++;
return result;
} public byte[] Read(int length)
{
byte[] result = new byte[length];
for (int i = 0; i < length; i++)
{
byte value = Read();
if (value == REPLACE_TAG)
{
value = Read();
if (value == 0x01)
{
result[i] = REPLACE_TAG;
}
else if (value == 0x02)
{
result[i] = PROTOBUF_TAG;
}
else
{
//result[i] = value;
}
}
else
{
result[i] = value;
}
}
return result;
} public void Write(byte data)
{
if (data == PROTOBUF_TAG)
{
OnWrite(REPLACE_TAG).OnWrite(0x02);
}
else if (data == REPLACE_TAG)
{
OnWrite(REPLACE_TAG).OnWrite(0x01);
}
else
{
OnWrite(data);
}
}
消息结构定义
一看到需求进行代码编写的实现代码的习惯并不好,最好在设计的时候通过接口结构来描述具体编写代码总体框架的可行性,这样可以在设计阶段能更好的把控存在问题。根据协议的要求消息的结构定义出接口,交根据实际规划细化接口的组成部分:
public interface IMessage
{
ushort ID { get; set; }
MessageBodyAttributes Property { get; set; }
string SIM { get; set; }
ushort BussinessNO { get; set; }
PacketInfo Packet { get; set; }
void Save(IProtocolBuffer buffer);
void Load(IProtocolBuffer buffer);
IMessageBody Body { get; set; }
byte CRC { get; set; }
}
由于有两大部分相对比较复杂所以针对消息的消息体属性和消息体单独抽象出来,这样主要降低在协议封装和解释过程在主消息接口处理的复杂度。
接口制定了Save和Load方法用一描述消息包的封装和解释,通过这个规范设计消息的封装和解释完全和具体的数据来源隔离;根据具体消息封装和解释的具体实现如下:
public void Load(IProtocolBuffer buffer)
{
byte crc = 0;
for (int i = 1; i < buffer.Length - 1; i++)
crc ^= buffer.Array[i];
//read start
buffer.Read();
//read id
ID = buffer.ReadUInt16();
//read property
Property.Load(buffer);
//read sim
SIM = buffer.ReadBCD(6);
//read no
BussinessNO = buffer.ReadUInt16();
//read packet
if (Property.IsPacket)
{
Packet = new PacketInfo();
Packet.Load(buffer);
}
//read body
if (Property.BodyLength > 0)
{
ProtocolBuffer bodybuffer = new ProtocolBuffer();
IMessageBody body = MessageBodyFactory.Default.GetBody(ID);
if (body != null)
body.Load(bodybuffer);
}
//read crc
this.CRC = buffer.Read();
if (this.CRC != crc)
throw new Exception("message check CRC error!");
//read end
buffer.Read();
} public void Save(IProtocolBuffer buffer)
{
ProtocolBuffer bodybuffer = null;
if (Packet != null)
Property.IsPacket = true;
if (Body != null)
{
bodybuffer = new ProtocolBuffer();
Body.Save(bodybuffer);
if (bodybuffer.Length > MessageBodyAttributes.BODY_LENGTH)
throw new Exception("message body to long!");
Property.BodyLength = (ushort)bodybuffer.Length;
}
//write start
buffer.Write(ProtocolBuffer.PROTOBUF_TAG);
//write id
buffer.Write(ID);
//write body property
Property.Save(buffer);
//write sim
buffer.WriteBCD(SIM);
//write no
buffer.Write(BussinessNO);
//write packet
if (Packet != null)
Packet.Save(buffer);
//write body
if (bodybuffer != null)
buffer.WriteSubBuffer(bodybuffer);
//write crc
byte crc = 0;
for (int i = 1; i < buffer.Length; i++)
crc ^= buffer.Array[i];
buffer.Write(crc);
//write end
buffer.Write(ProtocolBuffer.PROTOBUF_TAG);
}
消息体属性描述
由于消息体属性描述是通过解位来处理,所以对于WEB开发的技术人员来这些基础知识相对来说还是比较薄弱了一点。其实大体上就是通过移位,&,|的一些操作来获取相关位的信息,如果对于二进制真的不熟悉其实可以用系统带的计算器开启程序员模式就可以了(这方面的知识对于程序员来说还是有必要补充一下)。
//保留位15
public bool CustomHigh { get; set; }
//保留位14
public bool CustomLow { get; set; }
//分包位13
public bool IsPacket { get; set; }
//加密位12
public bool EncryptHigh { get; set; }
//加密位11
public bool EncryptMiddle { get; set; }
//加密位10
public bool EncryptLow { get; set; }
//消息长度9-0
public ushort BodyLength { get; set; }
public void Save(IProtocolBuffer buffer)
{
ushort value = (ushort)(BodyLength & BODY_LENGTH);
if (CustomHigh)
value |= CUSTOM_HEIGHT;
if (CustomLow)
value |= CUSTOM_LOW;
if (IsPacket)
value |= IS_PACKET;
if (EncryptHigh)
value |= ENCRYPT_HEIGHT;
if (EncryptMiddle)
value |= ENCRYPT_MIDDLE;
if (EncryptLow)
value |= ENCRYPT_LOW;
buffer.Write(value);
}
public void Load(IProtocolBuffer buffer)
{
ushort value = buffer.ReadUInt16();
CustomHigh = (CUSTOM_HEIGHT & value) > 0;
CustomLow = (CUSTOM_LOW & value) > 0;
IsPacket = (IS_PACKET & value) > 0;
EncryptHigh = (ENCRYPT_HEIGHT & value) > 0;
EncryptMiddle = (ENCRYPT_MIDDLE & value) > 0;
EncryptLow = (ENCRYPT_LOW & value) > 0;
BodyLength = (ushort)(BODY_LENGTH & value);
}
消息体描述
在消息设计上通过接口和具体网络处理隔离,在消息体设计也应该采用同样的原则;这样消息体的实现和扩展就不会对上层消息代码有任何的影响。
public interface IMessageBody
{
void Save(IProtocolBuffer buffer);
void Load(IProtocolBuffer buffer);
}
只需要很简单的代码即能完成这个工作,所以我们在设计不要为了一些的方便而不去制定抽象行为;其实在抽象的过程就是一个很好的设计方式。有这个规范那在实现基础消息就会方便多了,也不用提心对上层的影响;以下是一个终端设通用响应的实现
class ClientResponse : IMessageBody
{
public ushort BussinessNO { get; set; } public ushort ResultID { get; set; } public ResultType Result { get; set; } public void Load(IProtocolBuffer buffer)
{
BussinessNO = buffer.ReadUInt16();
ResultID = buffer.ReadUInt16();
Result = (ResultType)buffer.Read();
} public void Save(IProtocolBuffer buffer)
{
buffer.Write(BussinessNO);
buffer.Write(ResultID);
buffer.Write((byte)Result);
}
}
总结
以上是针对TJ/T808协议实现的一种方式紧供参考!其实在设计上我们还是有些基础准则可以遵守的,在设计根据职责划分抽像规则,把复杂的结构拆成简单独立的个体进行组合应用;接口的抽像定义也是非常重要,其实很多时候沟通过时发现有很多程序员对接口的定性是除了多写代码没有什么作用!其实接口是一个逻辑规划的抽像,通过抽像可以上你在设计阶段的时候更深入的了解功能切割和模块快,通过接口可以更快速有效的审核自己设计的合理性。
TJ/T808 终端通讯协议设计与实现(码农本色)的更多相关文章
- 2018-2019-1-20165221&20165225 《信息安全系统设计》实验五:通讯协议设计
2018-2019-1-20165221&20165225 <信息安全系统设计>-实验五:通讯协议设计 OpenSSL学习: 简介: OpenSSL是为网络通信提供安全及数据完整性 ...
- JT/T 808-2013 道路运输车辆卫星定位系统北斗兼容车载终端通讯协议技术规范
文档下载地址:JT/T 808-2013 道路运输车辆卫星定位系统北斗兼容车载终端通讯协议技术规范
- 2018-2019-1 20165318 20165326 实验五 通讯协议设计.md
目录 实验内容 问题及解决 参考资料 实验内容 任务一 在Ubuntu中完成作业 openSSL OpenSSL是一个SSL协议的开源实现,采用C语言作为开发语言,具备了跨平台的能力,支持Unix/L ...
- 蚂蚁通讯框架SOFABolt之私有通讯协议设计
前言 SOFABolt 是蚂蚁金融服务集团开发的一套基于 Netty 实现的网络通信框架. 为了让 Java 程序员能将更多的精力放在基于网络通信的业务逻辑实现上,而不是过多的纠结于网络底层 NIO ...
- TCP、消息分包和协议设计
TCP是一种流式协议 TCP是一种面向连接的.可靠的.基于字节流的传输层通信协议. 流式协议的特点是什么?就像流水连续不断那样,消息之间没有边界.例如send了3条消息(这里的“消息”是指应用层的一个 ...
- 主程的晋升攻略(4):TCP、消息分包和协议设计
在<主程的晋升攻略(3):IP.DNS和CDN>中,一次网络请求经过DNS解析知道了目的IP,如今就要发出网络包,这里我们说一说TCP的相关话题. TCP是一种流式协议 讲网络编程的教科书 ...
- .net 平台下, Socket通讯协议中间件设计思路(附源码)
.net 平台下,实现通讯处理有很多方法(见下表),各有利弊: 序号 实现方式 特点 1 WCF 优点:封装好,方便.缺点:难学,不跨平台 2 RocketMQ,SuperSocket等中间件 优点: ...
- Netty 对通讯协议结构设计的启发和总结
Netty 通讯协议结构设计的总结 key words: 通信,协议,结构设计,netty,解码器,LengthFieldBasedFrameDecoder 原创 包含与机器/设备的通讯协议结构的设计 ...
- MQTT是IBM开发的一个即时通讯协议,构建于TCP/IP协议上,是物联网IoT的订阅协议,借助消息推送功能,可以更好地实现远程控制
最近一直做物联网方面的开发,以下内容关于使用MQTT过程中遇到问题的记录以及需要掌握的机制原理,主要讲解理论. 背景 MQTT是IBM开发的一个即时通讯协议.MQTT构建于TCP/IP协议上,面向M2 ...
随机推荐
- ROS实时采集Android的图像和IMU数据
前言 临近毕业,整理一下之前做的东西.这篇博客来自于博主在2016年3月份投的一篇会议论文(论文主要介绍了一个基于手机摄像头和IMU的简单VIO系统,用于AR的Tracking部分,本博文 ...
- [[UIScreen mainScreen] bounds] 返回的屏幕尺寸不对
在使用cocos2d-iphone 2.0生成项目的时候,用5s测试时全屏中上下一直有黑条,发现[[UIScreen mainScreen] bounds]返回的屏幕尺寸不是320*568的,而是32 ...
- Office组件之Spire.XLS的DotNet操作
Overview 在项目中,我们经常需要将程序中获得的大量数据导出到Excel表格中,打印报表:进一步,还可能生成其折线图,对数据的变化趋势进行分析,从而更好地开展项目工作. 最近,我发现了一个对于D ...
- SQL Server日期时间格式转换字符串详解 (详询请加qq:2085920154)
在SQL Server数据库中,SQL Server日期时间格式转换字符串可以改变SQL Server日期和时间的格式,是每个SQL数据库用户都应该掌握的.本文我们主要就介绍一下SQL Server日 ...
- net软件自动生成开发编程框架编程机器人
有一个.net自动生成平台(编程机器人)推荐给大家,常规几天十几天的工作,机器人几分钟搞定,不写一行代码,留下大把休闲时光,适应于聪明人:不想太累的程序员(看看风景泡泡妞),不想多请人的老板(有限资金 ...
- Mac Virtual System On Windows
Win8.1下利用虚拟机安装苹果操作系统 所需文件: 虚拟机:VMware -10.0.1,这个就是中文版的了. 虚拟机密钥生成器:vm10keygen,要对应虚拟机的版本. 虚拟机的插件: unlo ...
- 增强拉格朗日乘子法(Augmented Lagrange Method)
增强拉格朗日乘子法的作用是用来解决等式约束下的优化问题, 假定需要求解的问题如下: minimize f(X) s.t.: h(X)=0 其中,f:Rn->R; h:Rn->Rm 朴素拉格 ...
- OD使用教程3
reverseMe爆破: 跳转指令 让跳转已实现,把z的1改成0 按F8走,继续把z的1改成0,实现跳转 根据跳转指令,改变s或o,使跳转不实现 指令如上使跳转不实现 继续按f8往下走然后成功
- 修改centos启动项
centos7下修改启动项在路径/etc/grub.d/文件路径下,修改完成之后需要运行命令 grub2-mkconfig --output=/boot/grub2/grub.cfg
- JAVA里面的IO流(一)分类1(字节/字符和输入/输出)
java.io包中定义了多个流类型(流或抽象类)来实现输入/输出功能:可以从不同的角度对其进行分类: 按数据流的方向不同可以分为输入流和输出流 从文件读数据为输入流:往文件写数据为输出流 按处理数 ...