分布式微服务系统架构第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编解码。实际应用中需结合协议文档处理具体字段。

相关推荐
牛奔1 分钟前
Go 如何避免频繁抢占?
开发语言·后端·golang
想用offer打牌5 小时前
MCP (Model Context Protocol) 技术理解 - 第二篇
后端·aigc·mcp
passerby60616 小时前
完成前端时间处理的另一块版图
前端·github·web components
KYGALYX6 小时前
服务异步通信
开发语言·后端·微服务·ruby
掘了6 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
爬山算法7 小时前
Hibernate(90)如何在故障注入测试中使用Hibernate?
java·后端·hibernate
Moment7 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
草梅友仁8 小时前
墨梅博客 1.4.0 发布与开源动态 | 2026 年第 6 周草梅周报
开源·github·ai编程
Cobyte8 小时前
AI全栈实战:使用 Python+LangChain+Vue3 构建一个 LLM 聊天应用
前端·后端·aigc