Android與單片機 | 開發板 | 智能硬件 | 智能設備 | 數據協議 |開發總結

1、緣起

工做中常常遇到須要Android程序與各式各樣的板子、智能設備進行交互,通訊的方式也基本上都是Ble或者Socket tcp/udp等等.....其中最重要的一點就是通訊的協議協議協議重要的是說三遍;通訊協議就是用來定義與設備交互的方式和交互過程當中數據包的格式 如:(包頭—數據包長度—數據—校驗位—包尾)java

2、這裏先來理理各類數據類型、所佔的字節數等

一、咱們寫的代碼最終都是轉化成各類機器所能識別的二進制或者字節碼,不一樣的編程語言具備不一樣的數據類型基本的也好不基本的也好、固然有不一樣的也就有相同的byte(字節)就是其中的一個; 二、平常開發中咱們進行通訊發送的內容最終都會以字節的形式進行發送,這裏以JavaSocket爲例,咱們來看下源碼編程

  • 建立一個socket鏈接,發送數據
Socket socket = new Socket(ip, port);
OutputStream outputStream = socket.getOutputStream();
//發送數據
outputStream.write("Hello World!".getBytes());
outputStream.flush();
//關閉鏈接
outputStream.close();
socket.close();
複製代碼
  • 咱們來看下OutputStreamwirte(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[](字節數組)進行發送,因此只要將數據轉成字節數組便可,下面進入數據類型科普時間

3、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

  • 上面已經說了一個int4個字節32bit
  • 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 上面咱們說了一個int32個字節也就是4byte,那理所固然一個int能夠轉成2個byte或者4byte,以下:

/** * 一個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下標socket

4、上面bb了一大堆,如今咱們經過一個具體的協議來深刻了解這些內容

4.1 協議以下:

在這裏插入圖片描述

這裏須要解釋下ucharuint是什麼意思?uchar = unsigned char 、uint = unsigned int,也就是無符號的數據,也就是表示了這個數據是正數tcp

  • 一、對協議進行分析能夠得知:整個數據包是由兩部分組成的包頭+擴展數據包,其中包頭佔固定的32個字節
  • 二、首先咱們得分析包頭裏面的每個字段所佔了多少個字節
    • uchar 佔1個字節
    • uint 佔4個字節
  • 二、那咱們重點就是得來分析包頭的數據須要怎麼封裝,經過協議咱們能夠看出包頭內一共包含6個字段,分別表示以下:
    • 第一個爲固定的"DH",總共佔2個字節
    • 第二個爲版本1.0 ,總共佔2個字節
    • 第三個爲擴展數據長度"extlen" ,總共佔4個字節
    • 第四個爲擴展數據類型取值0或1 ,總共佔1個字節
    • 第五個爲保留字段不使用就0補齊,總共佔3個字節
    • 第六個爲保留字段不使用就0補齊,總共佔20個字節
  • 三、經過上面一頓分析,咱們就輕鬆的理清了每一個字段所佔的字節了

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);
複製代碼

到這裏一個完整的數據包就愉快的結束了也就實現了與設備的通訊了;重點:之後再拿到一個協議首先研究一下由多少個部分組成,每一個組成的部分佔多少個字節,是高位到低位仍是低位到高位🙃

相關文章
相關標籤/搜索