licheedev / Android-SerialPort-Tool

Android串口调试助手
602 stars 171 forks source link

Need Help #1

Closed JamesLiAndroid closed 6 years ago

JamesLiAndroid commented 6 years ago

想请教下关于粘包、拼包的问题,我好多次收到下位机发来的指令信息过长,导致信息截断,无法完整读取,这样的情况下该如何进行处理?

licheedev commented 6 years ago

粘包拼包需要结合具体的协议来解决。 大致流程就是收到数据后先缓存下来,然后找出数据帧头,找出包尾,然后校验整个包是否有效,有效就把数据包分发出去(最好在其他线程分发,不要阻塞接收的线程),无效就要考虑是要放弃掉整个包,还是返回到帧头后的下一个字节,继续找帧头。

下面是我具体项目的一个例子,用到ByteBuffer来缓存数据。 我这个项目的数据包是固定7字节长度的,而且有固定的帧头帧尾,所以校验出错的话,我就舍弃掉整个包了,如果是动态包长度的,更多是需要返回帧头后那个字节,再重新找帧头。


    public void receive(byte[] data, int length) {

        //LogPlus.w("收到数据=" + ByteUtil.bytes2HexStr(data, 0, length));

        mByteBuffer.put(data, 0, length);
        checkData();
    }

    //   起始位    仓口ID号    特征码    效数据   校验位   数据尾
    //   0x1b     x          x        x       x      0D 0A
    private void checkData() {

        mByteBuffer.flip();

        byte b;
        byte[] twoBytes = new byte[2];

        int frameStart;
        int frameEnd;
        // 必须比包长度大
        while (mByteBuffer.remaining() >= Protocol.PACKAGE_LENGTH) {
            mByteBuffer.mark(); // 标记一下开始的位置
            // 标记一个第一个元素
            frameStart = mByteBuffer.position();

            b = mByteBuffer.get();
            if (b != Protocol.FRAME_HEAD) { // 第1个byte要0x1B
                continue;
            }

            // 跳过 仓口ID号    特征码    效数据   校验位 (1 1 1 1)
            mByteBuffer.position(mByteBuffer.position() + 4);

            // 数据尾
            mByteBuffer.get(twoBytes);
            frameEnd = mByteBuffer.position();

            // 数据尾符合规则
            if (twoBytes[0] == Protocol.FRAME_FOOT_0 && twoBytes[1] == Protocol.FRAME_FOOT_1) {
                mByteBuffer.position(frameStart + 1);
                int toDiff = mByteBuffer.get(); // 仓口ID号
                toDiff = toDiff ^ mByteBuffer.get();// 特征码
                toDiff = toDiff ^ mByteBuffer.get();// 效数据
                byte check = mByteBuffer.get(); // 校验位

                if (check == (byte) toDiff) {
                    // 校验成功
                    // 拿到整个包
                    mByteBuffer.reset();
                    byte[] data = new byte[Protocol.PACKAGE_LENGTH];
                    mByteBuffer.get(data);

                    if (getValidDataCallback() != null) {
                        getValidDataCallback().onReceiveValidData(data);
                    }
                } else {
                    //校验失败,舍弃这个包
                    mByteBuffer.position(frameEnd);
                }
            } else {
                // 不符合就跳到第二个为重新来
                mByteBuffer.position(frameStart + 1);
            }
        }
        // 最后清掉之前处理过的不合适的数据
        mByteBuffer.compact();
    }
JamesLiAndroid commented 6 years ago

目前我这边的协议是这样的(PS:直接处理字符串)

setcfg{OpenDoor=0;}setcfg\r\n 大括号内的内容是可变的,也就是整个数据包长度不固定

判断 }setcfg\r\n 的位置,然后往前找头 setcfg{ , 最后直接取中间值。

我现在的问题是,尾部能判断,如果尾部不全,从尾部往前的全部舍弃, 但是头部很多时候不全,甚至会出现乱码的情况(一般乱码是FF这样的hex字符),这种情况下如何处理,舍弃吧,中间数据还是完整的,但是不舍弃就错过了完整的数据。

最极端的我想的是直接那中间的OpenDoor=0;在字符串中间进行判断,但是几十个命令,都去判断的话,执行时间上就把握不了了。

所以还是向您请教下!谢谢!

licheedev commented 6 years ago

@JamesLiAndroid 波特率设置的合适不?发的字符串命令包不包含中文等字符? 感觉直接用字符串来给串口发命令不是很好,处理起来比固定协议的要麻烦。 你看看能不能这样,还是按照原始字节数组来处理,先弄出setcfg的和setcfg\r\n的byte[]常量,收到数据后,直接从原始数据中找出setcfg的和setcfg\r\n的byte[]数组的位置,找到后再抽出来转成字符串。

JamesLiAndroid commented 6 years ago

@licheedev

  1. 波特率9600
  2. 发送的字符串命令不包含中文字符

字符串发送的时候是直接转byte发送的,您的意思是按照hex处理完再转字符串?

还忘了两个很重要的问题:

  1. 如果发送的命令下位机不响应,如何进行重试?
  2. 如果发送的命令下位机响应,但是返回信息不正确,这种情况下如何进行重试?
JamesLiAndroid commented 6 years ago

我自己写的一个项目,解决了部分拼包的问题,通过长延时来规避一部分数据包丢失的问题

项目地址:

https://github.com/JamesLiAndroid/FuckingSerialPort