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 ...
随机推荐
- c++ 覆盖、重载与隐藏
成员函数被重载的特征:(1)相同的范围(在同一个类中):(2)函数名字相同:(3)参数不同:(4)virtual 关键字可有可无.覆盖是指派生类函数覆盖基类函数,特征是:(1)不同的范围(分别位于派生 ...
- MySQL数据库设置远程访问权限方法总结
1,设置访问单个数据库权限 mysql>grant all privileges on test.* to 'root'@'%'; 说明:设置用户名为root,密码为空,可访问数据库test 2 ...
- [Note] changing building platform from vs 2013 to vs community 2015
The error turned out as "undefined linkage"(The same as you haven't use some function that ...
- 在Android Studio中使用xUtils2.6.14,import org.apache.http不可用
添加依赖 compile 'org.apache.httpcomponents:httpcore:4.4.2' 删除重复的v-4包
- dos2unix unix2dos
实现windows和linux之间的文件自动转换,消除^M.
- 一个sendMessage
Message 1.判断是否同意协议.2.验证验证码是否正确.3.验证手机是否符合规则,符合规则就用message()发送短信,验证码的有效期以及使用的短信模板,在配置文件中进行管理.返回值下标为st ...
- IOS 支付、性能调试、IPv6兼容支持等
微信支付 支付宝支付 性能调试 IPv6兼容支持 APP引导页框架
- Windows下的UDP爆了10054--远程主机强迫关闭了一个现有的连接
原文地址:http://www.cnblogs.com/pasoraku/p/5612105.html 故事是这样的. 前几天在网上逛,看到了一个漂亮的坦克模型. 我觉得这个坦克可以做一个游戏,那需要 ...
- php socket解决方案
最近一直在为移动应用提供 php服务端api,以前 实时交互数据需求不严格(定时从手机端发送http请求),现在业务需求变更, 需要实时交互式接口,必须增加socket. 服务端框架使用YII 1.1 ...
- STM32 DMA模块的配置与使用
DMA有什么用? 直接存储器存取用来提供在外设和存储器之间或者存储器和存储器之间的高速数据传输.无须CPU的干预,通过DMA数据可以快速地移动.这就节省了CPU的资源来做其他操作. 有多少个DMA资源 ...