爲什麼會粘包?舉個栗子:
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;
}