分布式微服务系统架构第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 小时前
Spring Boot 企业级动态权限全栈深度解决方案,设计思路,代码分析
java·spring boot·后端·spring·spring cloud·性能优化·springcloud
程序员爱钓鱼3 小时前
Go语言实战案例-读取用户输入并打印
后端·google·go
Livingbody7 小时前
基于【ERNIE-4.5-VL-28B-A3B】模型的图片内容分析系统
后端
yanlele8 小时前
我用爬虫抓取了 25 年 6 月掘金热门面试文章
前端·javascript·面试
你的人类朋友8 小时前
🍃Kubernetes(k8s)核心概念一览
前端·后端·自动化运维
追逐时光者9 小时前
面试第一步,先准备一份简洁、优雅的简历模板!
后端·面试
慕木兮人可9 小时前
Docker部署MySQL镜像
spring boot·后端·mysql·docker·ecs服务器
发粪的屎壳郎9 小时前
ASP.NET Core 8 轻松配置Serilog日志
后端·asp.net·serilog
小和尚同志9 小时前
全网影视一网打尽!8.2K Star 的 LibreTV 让你甩开追剧烦恼
开源·github
倔强青铜三10 小时前
苦练Python第4天:Python变量与数据类型入门
前端·后端·python