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;
    }

 

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