分布式微服务系统架构第93集:JT808协议Java编解码实现

加群联系作者vx:xiaoda0423

仓库地址:webvueblog.github.io/JavaPlusDoc...

1024bat.cn/

1. JT808消息结构

消息格式如下:

scss 复制代码
| 起始标识(0x7E) | 消息头 | 消息体 | 校验码 | 结束标识(0x7E) |
  • 消息头:包含消息ID、消息体属性(长度、加密等)、终端手机号、消息流水号等。
  • 消息体:根据消息ID定义的具体业务数据(如位置、心跳等)。
  • 校验码:从消息头到消息体的异或校验。

2. 编码过程(Java对象 → 字节流)

步骤:

  1. 构造消息头和消息体
  2. 合并并转义处理(0x7E → 0x7D 0x02,0x7D → 0x7D 0x01)
  3. 计算校验码(异或校验)
  4. 添加起始和结束标识

示例代码:

ini 复制代码
public class JT808Encoder {
    // 转义处理
    private byte[] escape(byte[] data) {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        for (byte b : data) {
            if (b == 0x7D) {
                out.write(0x7D);
                out.write(0x01);
            } else if (b == 0x7E) {
                out.write(0x7D);
                out.write(0x02);
            } else {
                out.write(b);
            }
        }
        return out.toByteArray();
    }

    // 计算校验码
    private byte calculateCheckCode(byte[] data) {
        byte checkCode = 0;
        for (byte b : data) {
            checkCode ^= b;
        }
        return checkCode;
    }

    public byte[] encode(JT808Message message) {
        ByteBuffer buffer = ByteBuffer.allocate(1024)
            .order(ByteOrder.BIG_ENDIAN);

        // 1. 构造消息头
        buffer.putShort(message.getMsgId());
        buffer.putShort(message.getMsgBodyProps());
        buffer.put(BcdUtil.strToBcd(message.getTerminalId()));
        buffer.putShort(message.getFlowId());

        // 2. 构造消息体
        buffer.put(message.getMsgBody());

        // 3. 合并头体并转义
        byte[] headerBody = Arrays.copyOf(buffer.array(), buffer.position());
        byte[] escapedData = escape(headerBody);

        // 4. 添加起始符、校验码、结束符
        ByteArrayOutputStream finalPacket = new ByteArrayOutputStream();
        finalPacket.write(0x7E);
        finalPacket.writeBytes(escapedData);
        finalPacket.write(calculateCheckCode(headerBody));
        finalPacket.write(0x7E);

        return finalPacket.toByteArray();
    }
}

3. 解码过程(字节流 → Java对象)

步骤:

  1. 去除起始和结束标识
  2. 反转义处理
  3. 验证校验码
  4. 解析消息头和消息体

示例代码:

ini 复制代码
public class JT808Decoder {
    // 反转义处理
    private byte[] unescape(byte[] data) {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        for (int i = 0; i < data.length; i++) {
            if (data[i] == 0x7D) {
                if (i + 1 < data.length) {
                    if (data[i + 1] == 0x01) {
                        out.write(0x7D);
                    } else if (data[i + 1] == 0x02) {
                        out.write(0x7E);
                    }
                    i++;
                }
            } else {
                out.write(data[i]);
            }
        }
        return out.toByteArray();
    }

    public JT808Message decode(byte[] rawData) {
        // 1. 去除起始和结束符
        int length = rawData.length;
        byte[] data = Arrays.copyOfRange(rawData, 1, length - 2);

        // 2. 反转义
        byte[] unescapedData = unescape(data);

        // 3. 校验码验证
        byte receivedCheckCode = rawData[length - 2];
        byte calculatedCheckCode = calculateCheckCode(unescapedData);
        if (receivedCheckCode != calculatedCheckCode) {
            throw new RuntimeException("校验失败");
        }

        ByteBuffer buffer = ByteBuffer.wrap(unescapedData)
            .order(ByteOrder.BIG_ENDIAN);

        // 4. 解析消息头
        JT808Message message = new JT808Message();
        message.setMsgId(buffer.getShort());
        message.setMsgBodyProps(buffer.getShort());
        byte[] terminalIdBytes = new byte[6];
        buffer.get(terminalIdBytes);
        message.setTerminalId(BcdUtil.bcdToStr(terminalIdBytes));
        message.setFlowId(buffer.getShort());

        // 5. 解析消息体(根据MsgId选择不同解析方式)
        int bodyLength = message.getMsgBodyLength(); // 从msgBodyProps中获取长度
        byte[] msgBody = new byte[bodyLength];
        buffer.get(msgBody);
        message.setMsgBody(parseMsgBody(message.getMsgId(), msgBody));

        return message;
    }

    private Object parseMsgBody(short msgId, byte[] bodyData) {
        switch (msgId) {
            case 0x0200: // 位置汇报
                return parseLocation(bodyData);
            case 0x0100: // 终端注册
                return parseRegistration(bodyData);
            // 其他消息类型...
            default:
                return null;
        }
    }

    private Location parseLocation(byte[] data) {
        // 解析经纬度、速度、状态等
        // 示例:解析纬度(4字节,1/10^6度)
        ByteBuffer buffer = ByteBuffer.wrap(data).order(ByteOrder.BIG_ENDIAN);
        int lat = buffer.getInt();
        return new Location(lat / 1_000_000.0);
    }
}

4. 关键工具类

BCD编码工具:

arduino 复制代码
public class BcdUtil {
    public static byte[] strToBcd(String s) {
        if (s.length() % 2 != 0) s = "0" + s;
        byte[] bytes = new byte[s.length() / 2];
        for (int i = 0; i < s.length(); i += 2) {
            bytes[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
                | Character.digit(s.charAt(i + 1), 16);
        }
        return bytes;
    }

    public static String bcdToStr(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            sb.append(String.format("%02X", b));
        }
        return sb.toString();
    }
}

5. 注意事项

  • 字节序:协议规定使用大端序(Big-Endian)。
  • 转义处理:避免遗漏0x7D和0x7E的转义。
  • 校验码:必须验证校验码以确保数据完整性。
  • 消息体解析:根据不同的消息ID动态解析数据(如0x0200为位置信息)。

通过以上步骤,可完成JT808协议消息的Java编解码。实际应用中需结合协议文档处理具体字段。

相关推荐
Cloud_.几秒前
RabbitMQ 基本原理详解
spring boot·分布式·后端·rabbitmq
阿黄学技术4 分钟前
Spring单例Bean的线程安全
java·后端·spring
追逐时光者6 分钟前
工作面试必备:SQL 中的各种连接 JOIN 的区别总结
后端·sql
乘风!6 分钟前
SpringBoot前后端不分离,前端如何解析后端返回html所携带的参数
前端·spring boot·后端
movee10 分钟前
基于本地deepseek搭建一个无需联网也可使用的个人知识库
人工智能·后端
CS semi29 分钟前
Rust从入门到实战
开发语言·后端·rust
重庆穿山甲30 分钟前
状态模式实战指南:用Java实现智能订单状态流转
后端
往日情怀酿做酒 V176392963842 分钟前
Django项目之订单管理part3
后端·python·django
Anlici1 小时前
跨域解决方案还有优劣!?
前端·面试
uhakadotcom1 小时前
MaxCompute Python UDF开发指南:从入门到精通
后端·面试·github