前几节我们把网络通信中的基础都过了一遍, 今天真正开始秀操作了. 本节主要讲解如何在应用层上去定义报文的结构体. 良好的报文设计会让今后的业务扩展变得轻松. 顺带会讲解一下字节序.
可以发现最近的章节都把两个小节合并成了一个. 这里最主要的原因是某些章节拟定标题的时候忘记注意篇幅考虑了, 单独拆除了不合适, 顾合并之, 以后的文章如果还有这种需要合并的不再做额外说明哈, 你们那么聪明看下标题肯定都懂的. o(_)o
章节
Android 与物联网设备通信 - 概念入门 http://www.dajipai.cc/archives/fe5f6231.html
Android 与物联网设备通信 - 数据传递的本质 http://www.dajipai.cc/archives/f1d847c5.html
Android 与物联网设备通信 - 网络模型分层 http://www.dajipai.cc/archives/dcba528a.html
Android 与物联网设备通信 - UDP 协议原理 http://www.dajipai.cc/archives/d555ca34.html
Android 与物联网设备通信 - TCP 协议原理 http://www.dajipai.cc/archives/d555ca34.html
Android 与物联网设备通信 - 基于 TCP/IP 自定义报文 http://www.dajipai.cc/archives/dbd40ba8.html
Android 与物联网设备通信 - 什么是字节序 http://www.dajipai.cc/archives/dbd40ba8.html
Android 与物联网设备通信 - Socket 服务端实现
Android 与物联网设备通信 - Socket 客户端实现
Android 与物联网设备通信 - 利用 UDP 广播来做设备查找
Android 与物联网设备通信 - 实现远程控制 Android 客户端
Android 与物联网设备通信 - Android 做小型服务器
Android 与物联网设备通信 - 调试技巧
Android 与物联网设备通信 - 并行串行与队列
Android 与物联网设备通信 - 数据安全
Android 与物联网设备通信 - 心跳
目录
自定义报文
字节序
自定义报文
什么是报文
实际上在前面几个章节中, 我们已经针对网络模型中的各个报文做过分析. 如果不出意外的话, 你已经认识了它们.
比较形象的解释报文就像我们生活中随处可见的收纳盒.
它是用来装东西的.(废话!)
不同厂家生产的收纳盒, 拥有不同尺寸的大小和容器空间. 虽然他们都叫收纳盒 (报文也一样). 但是可以存放的物件也不一样.
并且值得注意的是, 一个收纳盒往往已经给你划分出了不同的区域, 可以在他们的卡槽里放置符合槽位的物件. 这一点和我们的报文也是惊人的一致. 倘若你把一个不属于这个地方的东西强行塞入, 就会弄坏它. 在报文里把内容放置在错误的字段上也会引发灾难.
总结一句话, 报文就是用来为特定业务而设计成存储内容的一种格式描述, 正是有了它才能让发收数据有了参考, 而不会搞乱. 说到这里, 你大概已经有完全明白了报文是什么东西了吧, 那么我们开始定义一组报文.
定义
怎么定义呢? 当然不是凭空去写. 我们得先有业务嘛.
需求:
我们需要通过手机控制空调的开关, 模式和温度. 请设计一下手机该对空调说发送些什么, 空调怎么去认识手机发过来的内容.
遥控的逻辑图我偷个懒, 就不画, 大家自行脑补.
什么, 你脑子补不过来?
就是你按下遥控器对着空调的那种场景呀.
字段分析:
这里的需求拆出来就三个功能:
开关: 属于 true 和 false 逻辑.
模式: 有制冷, 制热, 自动, 换气, 除湿等.
温度: 一个数值.
通过对功能进行分析, 这里说明我们这个报文上设计字段需要满足三种功能. 并且每种功能对应里面的内容也是不一致的. 有真假, 枚举和数值, 则对应的内容的长度也是不一致的.
好, 我先给出我设计报文结构方案, 一起来看一下.
基本结构:
长度: 描述整组报文从长度字段往后的字节数.
设备号: 接受方序列号, 用于确认是否是自己需要关系的包.
功能码: 表示做什么事情的描述. 比如这里的
开关, 模式, 温度
内容: 功能码对应需要透传过去的内容.
校验和: 确保数据的完整性和一致性.
这里和我们第一节里介绍的结构体非常类似. 我们接着往下看内容里的可变长如何使用.
上表给出了功能码对应的内容使用什么结构和长度, 以最简单的开关为列. 我们发送一组报文. 在 java 里那么就会像下面这样去组装数据.
用 java 对象表示:
- public class AirCondBaseStructure {
- int len; // 长度
- byte sn[]; // 序列号
- byte code; // 功能码
- byte data[]; // 透传数据
- byte crc[]; // 校验
- }
剩下的就是按照对应的功能, 往这个对象里塞入数据.(偏底层的叫法是结构体)
最后发出去的数据就变成了这样:
开关报文:
0x00 0x00 0x00 0x11 | 0x31 0x32 0x33 0x34 0x35 0x36 0x37 0x38 0x39 0x30 0x31 0x32 0x33 | 0x21 | 0x01 |0x57 0x9D
我们根据上面报文表手动解析一把 16 进制数据:
长度:
0x00 0x00 0x00 0x11
int 类型站四个字节, 这里 0x11 转换成十进制就是 17.
设备号:
0x31 0x32 0x33 0x34 0x35 0x36 0x37 0x38 0x39 0x30 0x31 0x32 0x33
占 13 个字节, 转字符串后为:
1234567890123
功能码:
0x21
直接对应表中的开关功能.
内容: 0x01 直接对应表中的打开.
校验和: 0x57 0x9D 验算和发送端一致则为数据正确.
根据上面的解析说明长度为 17, 我们来算一下
设备号 13 字节 + 功能码 1 字节 + 内容 1 字节 + 校验和 2 字节
. 正好长度等于 17.
同样的道理如果把这里的功能码替换成温度调节, 则导致长度发生变化. 因为温度的内容结构是 shot 类型会占用两个字节. 以此类推, 其余的报文也是如法制炮.
这里给出其它两组报文, 读者可以尝试自己动手解析一下试试, 看能不能读出来其中的意思?
模式报文:
0x00 0x00 0x00 0x11 0x31 0x32 0x33 0x34 0x35 0x36 0x37 0x38 0x39 0x30 0x31 0x32 0x33 0x22 0x01 0x57 0x6D
温度调节报文:
0x00 0x00 0x00 0x12 0x31 0x32 0x33 0x34 0x35 0x36 0x37 0x38 0x39 0x30 0x31 0x32 0x33 0x23 0x00 0x1A 0x0D 0x95
留个作业, 想掌握这门技能的同学不妨可以尝试一下自己动手写一写组装报文转换成字节, 再从字节转换回对象. 在下一个章节我将放出这部分的解析代码.
字节序
从字面意思也能读出来是字节排列顺序. 作为在上层开发应用的同学可能真的很少能实际接触使用到. 因为一般都是使用大端口表示法. java 默认也是大端表示法. 以至于你甚至都是第一次听说还有这种东西?
我们先来看到底什么是字节序:
拿 int 来举例, 我们知道一个 int 占用四个字节. 那么四个字节在计算机按字节表现的形式是怎么排列的呢?
比如数字 int a= 201806; 就会像下面这样
小端口 (little endian) 表示法:
0x00 0x03 0x14 0x4E
大端口 (big endian) 表示法:
0x4E 0x14 0x03 0x00
他们都表示数字 201806, 小端口就是低位字节在前高位字节在后. 大端口则正好相反是高位在前低位在后.
从阮一峰老师博客里看到一张很形象的图. 我贴一下:
解释一下为什么会出现两种不同的字节排列顺序. 因为计算机并不知道单独一个字节代表具体的意思, 它只会傻傻的读. 计算机在设计的时候就采用了低位在前效率较高, 但人们更加习惯高位在前的记法. 所以就有了两种模式.
而物联网开发中有些硬件嵌入式开发会采用小端模式, 而更上层应用开发者更倾向用大端模式, 如果有一方没有对此转换处理就会造成数据老读出来是错的, 而代码上看起来和协议实现一致的低级错误.
部分参考: 理解字节序 http://www.ruanyifeng.com/blog/2016/11/byte-order.html
来源: https://juejin.im/post/5b27deeaf265da595d2eb8e7