为什么会粘包?举个栗子:
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;
}