写在前面:

通讯协议一般格式:包头 — 消息头 — 消息体 — 校验位 — 包尾;

通讯数据格式 : bit 、 byte  ; 

bit是指的就是二进制 , 也就是位,1 byte = 8 bit ;

android 网络通讯原理 安卓网络通讯协议_android 网络通讯原理

JT808协议:

数据类型:

android 网络通讯原理 安卓网络通讯协议_校验码_02

通俗来讲:WORD就是长度是2的byte数组(byte[2]),DWORD就是长度是4的byte数组(byte[4]);

消息结构:

  • 标识位:

采用 0x7e 表示,若校验码、消息头以及消息体中出现 0x7e,则要进行转义处理,转义

规则定义如下:

0x7e <————> 0x7d 后紧跟一个 0x02;

0x7d <————> 0x7d 后紧跟一个 0x01。

转义处理过程如下:

发送消息时:消息封装——>计算并填充校验码——>转义;

接收消息时:转义还原——>验证校验码——>解析消息。

示例:

发送一包内容为 0x30 0x7e 0x08 0x7d 0x55 的数据包,

则经过封装如下:0x7e 0x30 7d 0x02 0x08 0x7d 0x01 0x55 0x7e

  • 消息头:

android 网络通讯原理 安卓网络通讯协议_校验码_03

  • 消息体:就是你要发送的数据(byte[]);
  • 校验码:

校验码指从消息头开始,同后一字节异或,直到校验码前一个字节,占用一个字节。


 


消息编码和解码:

  • 消息编码:
/**
     * 包装808数据,分包消息
     *
     * @param msgId      消息id
     * @param phone      终端手机号
     * @param msgBody    消息体
     * @param subpackage 是否分包 0:不分包 1:分包
     * @param totalPkg   总包数
     * @param pkgNo      当前序号
     */
    public static byte[] generate808(int msgId, String phone, byte[] msgBody, int subpackage, long totalPkg, long pkgNo) {

        //=========================标识位==================================//
        byte[] flag = new byte[]{0x7E};

        //=========================消息头==================================//
        //[0,1]消息Id
        byte[] msgIdb = BitOperator.numToByteArray(msgId, 2);
        //[2,3]消息体属性
        byte[] msgBodyAttributes = msgBodyAttributes(msgBody.length, subpackage);
        //[4,9]终端手机号 BCD[6](占6位)
        byte[] terminalPhone = BCD8421Operater.string2Bcd(phone);
        //[10,11]流水号
        byte[] flowNum = BitOperator.numToByteArray(SocketConfig.getSocketMsgCount(), 2);
        //[12]消息包封装项 不分包 就没有
        //[12]消息包封装项 不分包 就没有
        byte[] msgHeader;
        if (subpackage == 1) {
            //分包
            byte[] totalPkgB = BitOperator.numToByteArray(totalPkg, 2);
            byte[] pkgNob = BitOperator.numToByteArray(pkgNo, 2);
            msgHeader = ByteUtil.byteMergerAll(msgIdb, msgBodyAttributes, terminalPhone, flowNum, totalPkgB, pkgNob);
        } else {
            msgHeader = ByteUtil.byteMergerAll(msgIdb, msgBodyAttributes, terminalPhone, flowNum);
        }
        //=========================数据合并(消息头,消息体)=====================//
        byte[] bytes = ByteUtil.byteMergerAll(msgHeader, msgBody);

        //=========================计算校验码==================================//
        String checkCodeHexStr = HexUtil.getBCC(bytes);
        byte[] checkCode = HexUtil.hexStringToByte(checkCodeHexStr);

        //=========================合并:消息头 消息体 校验码 得到总数据============//
        byte[] AllData = ByteUtil.byteMergerAll(bytes, checkCode);

        //=========================转义 7E和7D==================================//
        // 转成16进制字符串
        String hexStr = HexUtil.byte2HexStr(AllData);
        // 替换 7E和7D
        String replaceHexStr = hexStr.replaceAll(FLAG_7D, " 7D 01")
                .replaceAll(FLAG_7E, " 7D 02")
                // 最后去除空格
                .replaceAll(" ", "");

        //替换好后 转回byte[]
        byte[] replaceByte = HexUtil.hexStringToByte(replaceHexStr);

        //=========================最终传输给服务器的数据==================================//
        return ByteUtil.byteMergerAll(flag, replaceByte, flag);
    }
/**
     * 生成消息体属性
     *
     * @param subpackage [13]是否分包 0:不分包 1:分包
     */
    private static byte[] msgBodyAttributes(int msgLength, int subpackage) {
        byte[] length = BitOperator.numToByteArray(msgLength, 2);
        //[0,9]消息体长度
        String msgBodyLength = "" +
                //第一个字节最后2bit
                +(byte) ((length[0] >> 1) & 0x1) + (byte) ((length[0] >> 0) & 0x1)
                //第二个字节8bit
                + (byte) ((length[1] >> 7) & 0x1) + (byte) ((length[1] >> 6) & 0x1)
                + (byte) ((length[1] >> 5) & 0x1) + (byte) ((length[1] >> 4) & 0x1)
                + (byte) ((length[1] >> 3) & 0x1) + (byte) ((length[1] >> 2) & 0x1)
                + (byte) ((length[1] >> 1) & 0x1) + (byte) ((length[1] >> 0) & 0x1);
        //[10,12]数据加密方式 0 表示不加密
        String encryption = SocketConfig.JT808HEADER_ENCRYPT;
        //[13]分包
        String subpackageB = String.valueOf(subpackage);
        //[14,15]保留位
        String reserve = SocketConfig.JT808HEADER_RESERVE;
        String msgAttributes = reserve + subpackageB + encryption + msgBodyLength;
        // 消息体属性
        int msgBodyAttr = Integer.parseInt(msgAttributes, 2);
        return BitOperator.numToByteArray(msgBodyAttr, 2);
    }
/**
     * 包装808数据
     *
     * @param msgId   消息id
     * @param phone   终端手机号
     * @param msgBody 消息体
     * @return
     */
    public static byte[] generate808(int msgId, String phone, byte[] msgBody) {
        return generate808(msgId, phone, msgBody, 0, 0, 0);
    }
  • 消息解码:
/**
     * 解析服务器返回的808数据包
     *
     * @return not null有用的数据包 null 数据校验失败
     */
    public static byte[] check808Data(byte[] bytes) {
        //去除包头 包尾的7E标识 在去掉校验码
        byte[] del7eBytes = Arrays.copyOfRange(bytes, 1, bytes.length - 2);
        //获取数据上的校验码
        String checkCode = HexUtil.byte2HexStr(Arrays.copyOfRange(bytes, bytes.length - 2, bytes.length - 1));
        // 转成16进制字符串
        String hexStr = HexUtil.byte2HexStr(del7eBytes);
        // 替换  7D 02->7E  7D 01->7D
        String replaceHexStr = hexStr.replaceAll(" 7D 02", " 7E")
                .replaceAll(" 7D 01", " 7D")
                // 最后去除空格
                .replaceAll(" ", "");
        byte[] data = HexUtil.hexStringToByte(replaceHexStr);
        //计算校验码
        String sumCode = HexUtil.getBCC(data);
        if (!checkCode.equals(sumCode)) {
            return null;
        }
        return data;

    }

注册、鉴权、心跳、位置信息汇报

generate808方法就是上面的编码!!!


  1. 消息 ID:0x0002。 终端心跳数据消息体 为空。


  • 心跳:
JTT808Coding.generate808(0x0002, phone, new byte[]{})
  • 注册:

android 网络通讯原理 安卓网络通讯协议_转义_04

/**
     * 终端注册
     *
     * @param manufacturerId 制造商 ID
     * @param terminalModel  终端型号
     * @param terminalId     终端 ID
     * @return
     */
    public static byte[] register(String manufacturerId, String terminalModel, String terminalId) {
        //省域 ID
        byte[] p = BitOperator.numToByteArray(31, 2);
        //省域 市县域 ID
        byte[] c = BitOperator.numToByteArray(72, 2);
        //制造商 ID
        byte[] mId = manufacturerId.getBytes();
        //终端型号
        byte[] tmId = terminalModel.getBytes();
        //终端 ID
        byte[] tId = terminalId.getBytes();
        //车牌颜色
        byte[] s = {0};
        // 车辆标识
        byte[] vin = "LSFAM630000000008".getBytes();

        return ByteUtil.byteMergerAll(p, c, mId, tmId, tId, s,vin);
    }
byte[] register = JT808Directive.register("CCCBB","ANDROID0000000000000","AAAAAAA");
                    byte[] body = JTT808Coding.generate808(0x0100,SocketConfig.getmPhont(),register);
                    socketManager.send((body));
  • 鉴权 , authCode是注册返回的鉴权码:

android 网络通讯原理 安卓网络通讯协议_android 网络通讯原理_05

byte[] body = JTT808Coding.generate808(0x0102,SocketConfig.getmPhont(),authCode);
                    socketManager.send((body));
  • 上传位置信息:

android 网络通讯原理 安卓网络通讯协议_分包_06

/**
     * 位置信息汇报
     *
     * @param lat      纬度
     * @param lng      经度
     * @param altitude 高度
     * @param speed    速度
     * @param bearing  角度/方向
     * @param accuracy  精度
     * @param time     时间
     * @return
     */
    public static byte[] reportLocation(double lat, double lng, double altitude, float speed, float bearing, float accuracy, String time) {
        byte[] alarm = {0, 0, 0, 0};
//        byte[] state = {0, 0, 0, 0};
        byte[] state = stateLocationData(lat,lng);
        //DWORD经纬度
        double pow106 = Math.pow(10, 6);
        double lat106 = lat * pow106;
        double lng106 = lng * pow106;
        byte[] latb = ByteUtil.longToDword(Math.round(lat106));
        byte[] lngb = ByteUtil.longToDword(Math.round(lng106));
        // WORD 高度 速度 方向
        byte[] gaoChen = BitOperator.numToByteArray((int) altitude, 2);
        byte[] speedb = BitOperator.numToByteArray((int) (speed * 3.6), 2);
        byte[] orientation = BitOperator.numToByteArray((int) bearing, 2);
        //bcd时间
        byte[] bcdTime = BCD8421Operater.string2Bcd(time);
        //位置信息附加项
        byte[] additionLocation = additionLocationData(SocketConfig.getmOrderId(),(int) altitude,String.valueOf(accuracy));
        return ByteUtil.byteMergerAll(alarm, state, latb, lngb, gaoChen, speedb, orientation, bcdTime,additionLocation);
    }

    public static byte[] reportLocation(Jt808MapLocation jtData){
       return reportLocation(jtData.getLat()
               ,jtData.getLng()
               ,jtData.getAccuracy()
               ,jtData.getSpeed()
               ,jtData.getBearing()
               ,jtData.getAccuracy()
               ,jtData.getTime());
    }

    /**
     * 位置信息状态项
     *
     * @param
     * @return
     */
    private static byte[] stateLocationData(double lat, double lng) {
        String state = "00";
        state = state + (lat < 0 ? "1" : "0");
        state = state + (lng < 0 ? "1" : "0");
        state = state + "0000000000000000000000000000";
        byte[] stateByte = ByteUtil.int2DWord(Integer.parseInt(state, 2));
        return stateByte;
    }

    /**
     * 位置信息附加项
     * @param order 订单号
     * @param altitude 高度
     * @param accuracy 精度
     * @return
     */
    private static byte[] additionLocationData(String order, int altitude, String accuracy) {
        byte[] orderType = new byte[2]; //订单号
        orderType[0] = (byte) (0xE1);
        orderType[1] = ByteUtil.int2Byte(order.getBytes().length);
        byte[] orderMsg = ByteUtil.byteMergerAll(orderType, order.getBytes());

        byte[] altitudeType = new byte[3]; //高度的正负
        altitudeType[0] = (byte) (0xE2);
        altitudeType[1] = (byte) (0x01);
        altitudeType[2] = (byte) (altitude < 0 ? 0x31 : 0x30);

        byte[] accuracyType = new byte[2]; //订单号
        accuracyType[0] = (byte) (0xE3);
        accuracyType[1] = ByteUtil.int2Byte(accuracy.getBytes().length);
        byte[] accuracyMsg = ByteUtil.byteMergerAll(accuracyType, accuracy.getBytes());

        return ByteUtil.byteMergerAll(orderMsg, altitudeType, accuracyMsg);
    }
  • 批量上传位置信息:

android 网络通讯原理 安卓网络通讯协议_分包_07

8.12位置信息汇报 就是第3节的上传位置信息;


 

byte[] batchBytes = JT808Directive.batchReportLocation(locations);
byte[] body = JTT808Coding.generate808(0x0704, SocketConfig.getmPhont(), batchBytes);
socketManager.send((body));
/**
     * 批量位置信息汇报
     * @return
     */
    public static byte[] batchReportLocation(List<byte[]> locations) {
        byte[] counts = ByteUtil.int2Word(locations.size()); //数据项个数
        byte[] batchType = {0}; //位置数据类型 0:正常位置批量汇报,1:盲区补报

        List<byte[]> formatLocations = new ArrayList<>();
        for (int i = 0; i < locations.size(); i++) {
            //取出每一条的位置数据,然后拼接(位置汇报数据体长度+位置汇报数据体)
            byte[] cLocation = locations.get(i);
            byte[] clocationLength = ByteUtil.int2Word(cLocation.length); //位置汇报数据体长度
            formatLocations.add(ByteUtil.byteMergerAll(clocationLength, cLocation));
        }

        byte[] batchLocations = ByteUtil.byteMergerAll(formatLocations); //位置汇报数据体
        return ByteUtil.byteMergerAll(counts, batchType, batchLocations);
    }

OK!


文中有大量的工具类、例如:校验码的算法、byte和int相互转换 、 bit和byte互相转换、byte和String互相转换;