一, 缘起
工作中经常遇到需要 Android 程序与各式各样的板子, 智能设备进行交互, 通信的方式也基本上都是 Ble 或者 Socket tcp/udp 等等..... 其中最重要的一点就是通信的协议, 协议, 协议重要的是说三遍; 通信协议就是用来定义与设备交互的方式和交互过程中数据包的格式 如:(包头 - 数据包长度 - 数据 - 校验位 - 包尾)
二, 这里先来理理各种数据类型, 所占的字节数等
1, 我们写的代码最终都是转化成各种机器所能识别的二进制或者字节码, 不同的编程语言具有不同的数据类型基本的也好不基本的也好, 当然有不同的也就有相同的 byte(字节)就是其中的一个; 2, 日常开发中我们进行通信发送的内容最终都会以字节的形式进行发送, 这里以 Java 的 Socket 为例, 我们来看下源码
创建一个 socket 连接, 发送数据
- Socket socket = new Socket(ip, port);
- OutputStream outputStream = socket.getOutputStream();
- // 发送数据
- outputStream.write("Hello World!".getBytes());
- outputStream.flush();
- // 关闭连接
- outputStream.close();
- socket.close();
我们来看下 OutputStream 的 wirte(byte[] b)函数
- OutputStream # write(byte[] b)
- //1 接着又调用了 write(byte b[], int off, int len)
- public void write(byte b[]) throws IOException {
- write(b, 0, b.length);
- }
- //2 最后又调用了 write(byte b)
- public void write(byte b[], int off, int len) throws IOException {
- if (b == null) {
- throw new NullPointerException();
- } else if ((off <0) || (off> b.length) || (len <0) ||
- ((off + len)> b.length) || ((off + len) <0)) {
- throw new IndexOutOfBoundsException();
- } else if (len == 0) {
- return;
- }
- //3 这里就是讲我们发送的一个 bye[]进行 for 循环一个个写入了
- for (int i = 0 ; i < len ; i++) {
- write(b[off + i]);
- }
- }
- //4 end
- public abstract void write(int b) throws IOException;
小结: 无论定义的通讯格式是什么样的最终肯定要转成 byte[](字节数组)进行发送, 所以只要将数据转成字节数组即可, 下面进入数据类型科普时间
三, Java 中的数据类型所占的字节数和 bit 数
数据类型 | 所占字节数 | 所占 bit 数 | 取值范围 |
---|---|---|---|
byte | 1 | 8 | -128 ~ 127 |
char | 2 | 16 | '\u0000' ~ '\uFFFF' |
short | 2 | 16 | -2^15 ~ 2^15 - 1 |
int | 4 | 32 | -2^31 ~ 2^31 - 1 |
float | 4 | 32 | 2^-149 ~ 2^128 -1 |
long | 8 | 64 | -2^63 ~ 2^63 - 1 |
double | 8 | 64 | 2^-1074 ~ 2^1024 - 1 |
boolean | / | 1 | true or false |
String 在 Java 中不属于基本数据类型, 一个汉字占 2 个字节, 一个英文字母占 1 个字节
小结: 1 byte = 8 bit
3.1 什么是 bit 呢? 什么又是高低位呢? 在 Java 中又怎么写代码呢?
bit 就是位 也就是二进制数据, 取值只有 0,1 - 高位在左, 低位在右
这里以 byte 123 为例:
byte b =123 转为 bit
高位在 0, 低位在 1 0111 1011
在 Java 中获取 byte 的 8 个 bit
- /**
- * byte 转 8 bit
- *
- * @param b byte
- * @return 高位到低位顺序, 以 byte123 为例: 0111 1011
- */
- public static byte[] byte2Bit(byte b) {
- byte[] arr = new byte[8];
- for (int i = 7; i>= 0; i--) {
- arr[i] = (byte) (b & 1);
- b = (byte) (b>> 1);
- }
- return arr;
- }
既然把 byte 转为了 8 个 bit 位, 那我们又怎么再把 bit 转回为 byte 呢?
- /**
- * 8 个 bit 位转为 byte
- */
- public static byte bit2Byte(byte[] bytes) {
- if (bytes.length != 8) return 0;
- String binary = "";
- byte result;
- for (byte b : bytes) {
- binary += b;
- }
- if (bytes[0] == 0) {
- // 正数
- result = (byte) Integer.parseInt(binary, 2);
- } else {
- // 负数
- result = (byte) (Integer.parseInt(binary, 2) - 256);
- }
- return result;
- }
3.2 上面已经说了 byte 与 bit 的相互转化, 现在就轮到 int 了
上面已经说了一个 int 占
4
个字节
32
个 bit
Integer 类已经为我们封装好了转 bit 的方法, 如下:
- String s = Integer.toBinaryString(35235);
- // 输出结果
- 1000100110100011
可以看到没有 32 位, 这是为什么呢? 这是因为高位都是为 0 所以就直接省略了, 当然我们也可以主动补齐
32
位只需要在高位补 0 即可.
bit 再转回为 int
- int result = Integer.parseInt("1000100110100011", 2);
- // 输出结果
- 35235
这里需要注意的是 Integer.toBinaryString()可以将负数转化为二进制, 但是 Integer.parseInt("", 2)不能直接将负数的二进制转为 int, 如下:
- String radix = Integer.toBinaryString(-35235);
- System.out.println(radix);
- int result = Integer.parseInt(radix, 2);
- System.out.println(result);
程序执行会报一个 java.lang.NumberFormatException: For input string:"11111111111111110111011001011101" 异常, 那我们怎么将负数的转回为 int 呢? 当然是有方法的啦, 如下:
- // 需要借助 BigInteger 类
- String radix = Integer.toBinaryString(-3535);
- BigInteger integer = new BigInteger(radix, 2);
- System.out.println(integer.intValue());
- // 输出结果
- -3535
3.3 当然我们可以通过电脑的计算器来计算二进制
3.4 上面我们说了一个 int 占
32
个字节也就是
4
个 byte, 那理所当然一个 int 可以转成 2 个 byte 或者
4
个 byte, 如下:
- /**
- * 一个 int 转 2 个字节的 byte 数组
- * 由低位到高位的转换
- *
- * @param value
- * @return
- */
- public static byte[] intTo2Bytes(int value) {
- byte[] src = new byte[2];
- src[0] = (byte) (value & 0xFF);
- src[1] = (byte) ((value>> 8) & 0xFF);
- return src;
- }
- /**
- * 一个 int 转 4 个字节的 byte 数组
- * 由低位到高位的转换
- *
- * @param value
- * @return
- */
- public static byte[] intTo4Bytes(int value) {
- byte[] src = new byte[4];
- src[0] = (byte) (value & 0xFF);
- src[1] = (byte) ((value>> 8) & 0xFF);
- src[2] = (byte) ((value>> 16) & 0xFF);
- src[3] = (byte) ((value>> 24) & 0xFF);
- return src;
- }
这里需要注意的是 int 转 byte[]的时候是高位在数组的 0 下标 还是低位在数组的 0 下标, 上面的两个方法都是低位在数组的 0 下标
四, 上面 bb 了一大堆, 现在我们通过一个具体的协议来深入了解这些内容
4.1 协议如下:
这里需要解释下 uchar,uint 是什么意思? uchar = unsigned char ,uint = unsigned int, 也就是无符号的数据, 也就是表示了这个数据是正数
1, 对协议进行分析可以得知: 整个数据包是由两部分组成的包头 + 扩展数据包, 其中包头占固定的 32 个字节
2, 首先我们得分析包头里面的每一个字段所占了多少个字节
uchar 占 1 个字节
uint 占 4 个字节
2, 那我们重点就是得来分析包头的数据需要怎么封装, 通过协议我们可以看出包头内一共包含 6 个字段, 分别表示如下:
第一个为固定的 "DH", 总共占 2 个字节
第二个为版本 1.0 , 总共占 2 个字节
第三个为扩展数据长度 "extlen" , 总共占
4
个字节
第四个为扩展数据类型取值 0 或 1 , 总共占 1 个字节
第五个为保留字段不使用就 0 补齐, 总共占
3
个字节
第六个为保留字段不使用就 0 补齐, 总共占
20
个字节
3, 通过上面一顿分析, 我们就轻松的理清了每个字段所占的字节了
4.2 Talk is cheap. Show me the code.
- //magic
- byte[] magicB = {
- 'D', 'H'
- };
- // 协议版本 1.0 转成 int 也就是 1
- byte[] versionB = {
- 0, 1
- };
- // 扩展数据长度, 这里假定扩展数据的长度为 67
- byte[] extLenB = intTo4Bytes(67);
- // 扩展数据类型 0:JSON,1: 二进制数据; 这里使用 JSON
- byte[] extType = {
- 0
- };
- // 两个保留字段, 直接 0 补齐, 上面已经分析了两个字段一共占 23 个字节
- byte[] reserved = new byte[23];
- // 这里将上面的多个数据合并至一个 byte[]
- byte[] data = byteMergerAll(magicB, versionB, extLenB, extType, reserved);
到这里包头的数据就已经处理好了, 还可以进一步对它进行封装
这里提供一个多个数据数组合并的工具方法
- /**
- * 多个数组合并一个
- *
- * @return
- */
- public static byte[] byteMergerAll(byte[]... bytes) {
- int allLength = 0;
- for (byte[] b : bytes) {
- allLength += b.length;
- }
- byte[] allByte = new byte[allLength];
- int countLength = 0;
- for (byte[] b : bytes) {
- System.arraycopy(b, 0, allByte, countLength, b.length);
- countLength += b.length;
- }
- return allByte;
- }
4.3 封装包头数据
- /**
- * 封装包头数据
- * 固定 32 个字节, 其余的 0 补齐
- *
- * @param extLen 扩展数据长度
- */
- public static byte[] getPkgHead(int extLen) {
- //magic
- byte[] magicB = {'D', 'H'};
- // 协议版本 1.0 转成 int 也就是 1
- byte[] versionB = {0, 1};
- // 扩展数据长度, 这里假定扩展数据的长度为 67
- byte[] extLenB = intTo4Bytes(extLen);
- // 扩展数据类型 0:JSON,1: 二进制数据; 这里使用 JSON
- byte[] extType = {0};
- // 两个保留字段, 直接 0 补齐, 上面已经分析了两个字段一共占 23 个字节
- byte[] reserved = new byte[23];
- // 这里将上面的多个数据合并至一个 byte[]
- return byteMergerAll(magicB, versionB, extLenB, extType, reserved);
- }
4.4 上面已经把包头处理好了那现在就可以发送命令了
- // 扩展数据: 这里就需要根据实际的文档来生成了, 我这里就随便写一个了
- String extData = "{\"id\":12,\"cmd\":\"open\"}";
- byte[] extDataB = extData.getBytes();
- // 获取包头
- byte[] pkgHead = getPkgHead(extDataB.length);
- // 一个完整的数据包
- byte[] sendData = byteMergerAll(pkgHead, extDataB);
到这里一个完整的数据包就愉快的结束了也就实现了与设备的通信了; 重点: 以后再拿到一个协议首先研究一下由多少个部分组成, 每个组成的部分占多少个字节, 是高位到低位还是低位到高位
最后
如果你看到了这里, 觉得文章写得不错就给个赞呗? 如果你觉得那里值得改进的, 请给我留言. 一定会认真查询, 修正不足. 谢谢.
来源: http://www.jianshu.com/p/d084a0c9f05a