Socket 粘包 拆包;

爲什麼會粘包?舉個栗子:

Socket連接成功就相當於通訊管道已經建立,客戶端就一直從管道中取數據,如果數據一次沒有取完就會發生遺留,這些遺留的數據就會和下次的數據包一起傳輸過來,然後就粘包了;

再看個數據:

採用0x7e 表示,若校驗碼、消息頭以及消息體中出現0x7e,則要進行轉義處理,轉義
規則定義如下:
0x7e <————> 0x7d後緊跟一個0x02;
0x7d <————> 0x7d後緊跟一個0x01。 轉義處理過程
如下:
發送消息時:消息封裝——>計算並填充校驗碼——>轉義;
接收消息時:轉義還原——>驗證校驗碼——>解析消息。
示例:
發送一包內容爲0x30 0x7e 0x08 0x7d 0x55的數據包,則經過封裝如下:
0x7e 0x30 7d 0x02 0x08 0x7d 0x01 0x55 0x7e。

7E就是標識位,拿到數據如果首尾時7E且中間沒有出現7E,則說明數據正常,如果不是這樣則需要拆包;

也就是,從第一個7E開始讀取數據,讀到第二個7E數據結束

1、7E8001000501833996222200130014000200DB7E
2、7E8001000501833996222200130014000200DB
3、7E7E8001000501833996222200130014000200DB7E
(數據已經由byte數組轉爲十六進制字符串)

假如讀取到這樣三條數據,可發現數據1正常,數據2、3發生粘包情況;需要特殊處理;

很多現在的協議都是從消息頭中獲取消息體長度,可以大幅度避免粘包的情況發生,但也有極端;

簡單的工具類奉上:

/*
 1、7E8001000501833996222200130014000200DB7E
 2、7E8001000501833996222200130014000200DB
 3、7E7E8001000501833996222200130014000200DB7E
 假如讀取到這樣三條數據,可發現數據1正常,數據2、3發生粘包情況;需要特殊處理;
 */
public class Jt808StickDataUtil {

    private static StringBuffer residueData; //遺留數據

    /**
     * 處理粘包數據;
     *
     * @param bytes 新數據
     * @return 根據7E處理粘包後的集合
     */
    public static List<byte[]> getStickData(byte[] bytes) {
        String Tag7E = "7E";
        List<byte[]> datas = new ArrayList<>();//創建返回的集合
        String data = HexUtil.byte2HexStrNoSpace(bytes); //把byte數組轉十六進制字符串
        if (getStringCount(data, Tag7E) == 2
                && Tag7E.equals(data.substring(0, 2))
                && Tag7E.equals(data.substring(data.length() - 2))) { //7E一共出現兩次,並且是在頭尾處,則該數據不用處理;
            datas.add(bytes);
            return datas;
        }
        if (residueData != null && !TextUtils.isEmpty(residueData.toString())) {  //查看是否有遺留數據,如果有則把新數據拼接在遺留數據後面
            data = residueData.append(data).toString();
        }
        int start = data.indexOf(Tag7E); //從第一個標識位開始讀數據
        int count7E = 0; //讀取到標識位的次數
        StringBuffer currentHex = new StringBuffer(); //存放讀取到的數據
        for (int i = start; i < data.length(); i = i + 2) {

            String currentByte;
            if (data.length() < i + 2) { //最後字數不足兩位的情況
                currentByte = data.substring(i, i + 1);
            } else {
                currentByte = data.substring(i, i + 2);
            }

            if (Tag7E.equals(currentByte)) {
                //如果是7E的情況
                count7E++;
                currentHex.append(currentByte);
                if (count7E == 2) { //讀取到第二個標識位時,把當前的數據歸爲一組;
                    count7E = 0;
                    datas.add(HexUtil.hexStringToByte(currentHex.toString()));
                    currentHex.delete(0, currentHex.length());
                }
            } else {
                //當有標識位的情況開始添加數據
                if (count7E == 1) currentHex.append(currentByte);
            }
        }
        residueData = currentHex; //全部遍歷後剩餘的數據和下次拼接
        return datas;
    }

    //獲取一個字符串,查找這個字符串出現的次數;
    private static int getStringCount(String str, String key) {
        int count = 0;
        while (str.contains(key)) {
            count++;
            str = str.substring(str.indexOf(key) + key.length());
        }
        return count;
    }

    public static void clear() {
        if (residueData == null) return;
        residueData.delete(0, residueData.length());
    }

}

byte數組轉十六進制String:

  /**
     * bytes轉換成十六進制字符串
     *
     * @param b byte數組
     * @return String 每個Byte值之間空格分隔
     */
    public static String byte2HexStr(byte[] b) {
        String stmp;
        StringBuilder sb = new StringBuilder();
        for (int n = 0; n < b.length; n++) {
            stmp = Integer.toHexString(b[n] & 0xFF);
            sb.append((stmp.length() == 1) ? "0" + stmp : stmp);
            sb.append(" ");
        }
        return sb.toString().toUpperCase().trim();
    }

    /**
     * bytes轉換成十六進制字符串
     *
     * @param b byte數組
     * @return String 不加空格分割
     */
    public static String byte2HexStrNoSpace(byte[] b) {
        String stmp;
        StringBuilder sb = new StringBuilder();
        for (int n = 0; n < b.length; n++) {
            stmp = Integer.toHexString(b[n] & 0xFF);
            sb.append((stmp.length() == 1) ? "0" + stmp : stmp);
        }
        return sb.toString().toUpperCase().trim();
    }

    /**
     * 把16進制字符串轉換成字節數組
     *
     * @return byte[]
     */
    public static byte[] hexStringToByte(String hex) {
        int len = (hex.length() / 2);
        byte[] result = new byte[len];
        char[] achar = hex.toCharArray();
        for (int i = 0; i < len; i++) {
            int pos = i * 2;
            result[i] = (byte) (toByte(achar[pos]) << 4 | toByte(achar[pos + 1]));
        }
        return result;
    }

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章