基于netty实现gps jtt808协议接入

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


提示:以下是本篇文章正文内容,下面案例可供参考

一、实现netty采集jtt808协议

二、使用步骤

1.netty接入数据

代码如下(示例):

 <dependency>
        <groupId>io.netty</groupId>
        <artifactId>netty-all</artifactId>
        <version>4.1.30.Final</version>
 </dependency>

实现netty接入jtt808协议:

import com.qycloud.iot.microserviceindoorpositioning.gps.config.GpsConfiguration;
import com.qycloud.iot.microserviceindoorpositioning.netty.utils.GpsDecoder;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;


@Component
@Slf4j
@Profile("!test1")  //单元测试类添加  @ActiveProfiles("test1")
public class NettyServer implements ApplicationRunner {


    @Autowired
    GpsConfiguration gpsConfiguration;
    @Autowired
    NettyServerHandler nettyServerHandler;

    @Override
    public void run(ApplicationArguments args)
    {

        // 创建对应的 线程池
        // 创建Boss group
        EventLoopGroup boosGroup = new NioEventLoopGroup(1);
        // 创建 workgroup
        EventLoopGroup workGroup = new NioEventLoopGroup();
        // 创建对应的启动类
        ServerBootstrap bootstrap = new ServerBootstrap();

        try{
            // 设置相关的配置信息
            bootstrap.group(boosGroup,workGroup) // 设置对应的线程组
                    .channel(NioServerSocketChannel.class) // 设置对应的通道
                    .option(ChannelOption.SO_BACKLOG,1024) // 设置线程的连接个数
                    .childHandler(new ChannelInitializer<SocketChannel>() { // 设置
                        /**
                         * 给pipeline 设置处理器
                         */
                        @Override
                        protected void initChannel(SocketChannel socketChannel) {
                            socketChannel.pipeline().addLast(new GpsDecoder());       //自定义的解码
                            socketChannel.pipeline().addLast(new StringEncoder(CharsetUtil.UTF_8));
                            socketChannel.pipeline().addLast(nettyServerHandler);
                        }
                    });
            log.info("netty服务启动了....");
            // 绑定端口  启动服务
            ChannelFuture channelFuture = bootstrap.bind(gpsConfiguration.getPort()).sync();
            // 对关闭通道进行监听
            channelFuture.channel().closeFuture().sync();

        }catch (Exception e){
            log.error("netty启动错误....",e);
        }finally {
            // 优雅停服
            boosGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
        }
    }
}


import com.qycloud.iot.microserviceindoorpositioning.gps.GpsProtocolDataHandler;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;


@Component
@ChannelHandler.Sharable
@Slf4j
public class NettyServerHandler extends ChannelInboundHandlerAdapter {

    @Autowired
    GpsProtocolDataHandler gpsProtocolDataHandler;

    /**
     * 读取客户端发送来的数据
     *
     * @param ctx ChannelHandler的上下文对象 有管道 pipeline 通道 channel 和 请求地址 等信息
     * @param msg 客户端发送的具体数据
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        log.info("开始解析设备,拿到的数据为-------{}" ,msg);
        if (null == msg || msg.toString().isEmpty()) return;
        String body = (String) msg;
        gpsProtocolDataHandler.handleProtocol(ctx, body);

    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }


    @Override
    public void channelRegistered(ChannelHandlerContext ctx) throws InterruptedException {
//        SocketAddress address = ctx.channel().remoteAddress();
//        System.out.println("客户端请求到了..." + address);
    }

    @Override
    public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
//        SocketAddress address = ctx.channel().remoteAddress();
//        System.out.println("客户端请求断开..." + address);
    }

    /**
     * 读取客户端发送数据完成后的方法
     * 在本方法中可以发送返回的数据
     *
     * @param ctx
     */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) {


//        ctx.writeAndFlush(hexStrToBytes("ab13"));
        // writeAndFlush 是组合方法
//        ctx.channel().writeAndFlush(hexStrToBytes("ab13"));
//        ctx.writeAndFlush(Unpooled.copiedBuffer(rMsg,CharsetUtil.UTF_8));
    }


}

netty解码,回写工具


import com.qycloud.iot.microserviceindoorpositioning.gps.utils.ByteUtils;
import com.qycloud.iot.microserviceindoorpositioning.gps.utils.GpsAnalyseUtils;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import lombok.extern.slf4j.Slf4j;

import java.util.List;
import java.util.Locale;

/**
 * @Author: WangHao
 * @Date: 2021/10/19/16:47
 * @Description:
 */
@Slf4j
public class GpsDecoder extends ByteToMessageDecoder {


    StringBuffer temp = new StringBuffer();


    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception {
        String HEXES = "0123456789ABCDEF";
        byte[] req = new byte[msg.readableBytes()];
        msg.readBytes(req);
        final StringBuilder hex = new StringBuilder(2 * req.length);

        for (int i = 0; i < req.length; i++) {
            byte b = req[i];
            hex.append(HEXES.charAt((b & 0xF0) >> 4))
                    .append(HEXES.charAt((b & 0x0F))).append(" ");
        }
//        String content = decodeOri(hex.toString());
        String content = hex.toString().replace(" ", "");
        log.info("原始报文-------{}", content);

        StringBuffer realMsg = null;
        if (temp.length() > 0) {
            realMsg = new StringBuffer(temp);
            realMsg.append(content);
            log.info("合并:上一数据包余下的长度为:" + temp.length() + ",合并后长度为:" + realMsg.length());
        } else {
            realMsg = new StringBuffer(content);
        }

        String realMsgStr = realMsg.toString();

        //如果是  7E开头
        if (realMsgStr.startsWith("7E")) {
            if (realMsgStr.length()>50000){
                //找不到全部舍去
                temp = new StringBuffer();
                return;
            }
            dealStartWith7E(realMsgStr, out);
        } else {
            //查找第一个7E开头的
            int index7E = realMsgStr.indexOf("7E");
            if (index7E < 0) {
                //找不到全部舍去
                temp = new StringBuffer();
                return;
            }
            realMsgStr = realMsgStr.substring(index7E);
            dealStartWith7E(realMsgStr, out);

        }

    }

    private void dealStartWith7E(String realMsgStr, List<Object> out) {
        try {
            int index = realMsgStr.indexOf("7E", realMsgStr.indexOf("7E") + 1);

            if (index == -1) {
                temp = new StringBuffer(realMsgStr);
//                out.add("");
                return;
            }
            String data = realMsgStr.substring(0, index + 2);
            //data 必然为 7E开头,7E结尾
            boolean check = check(data);

            if (check) {
                out.add(decodeOri(data));
                String substring = realMsgStr.substring(data.length());
                //判断是否需要拆包
                if (substring.length() == 0) {
                    temp = new StringBuffer();
                    return;
                } else {
                    dealStartWith7E(substring, out);
                    return;
                }

            }

            String substring = realMsgStr.substring(index);
            dealStartWith7E(substring, out);

        }catch (Exception e){
            log.error("解析数据格式错误,数据为{},错误原因:{}",realMsgStr,e);
        }
    }




    //完整数据结构校验
    private boolean check(String data) {
        String decodeData = decodeOri(data);

        if (decodeData == null || decodeData.length() < 30 || !decodeData.toLowerCase(Locale.CHINA).startsWith("7e") || !decodeData.toLowerCase(Locale.CHINA).endsWith("7e")) {
            log.error("校验数据结构:数据{},错误原因:{}", decodeData, "数据不是以7E开头或者结尾,或者长度不满足最基础的30位");
            return false;
        }
        if (decodeData.length() < 26) {
            log.error("校验数据结构:数据{},错误原因:{}", decodeData, "数据头不正确,不满足26位");
            return false;
        }

        String bodySizeStr = decodeData.substring(6, 10);
        int bodySize = ByteUtils.hexStringToDec(bodySizeStr) * 2; //一个字节两位
        int contentSize = bodySize + 30;
        if (decodeData.length() < contentSize) {
            log.error("校验数据结构:数据{},错误原因:{}", decodeData, "消息体长度不正确");
            return false;
        }

        //验证校验位
        String headAndBody = decodeData.substring(2, 26 + bodySize);
        String check = decodeData.substring(bodySize + 26, bodySize + 26 + 2);
        String checkCode = GpsAnalyseUtils.createCheckCode(headAndBody);
        if (!check.equalsIgnoreCase(checkCode)) {
            log.error("校验数据结构:数据{},错误原因:{}", decodeData, "校验位不匹配");
            return false;
        }
        return true;
    }



    private static String decodeOri(String oriStr) {
        return oriStr.replaceAll("(?i)7D 02", "7E")
                .replaceAll("(?i)7D 01", "7D").replace(" ", "");
    }

    public static void main(String[] args) {

//        String realMsgStr = "7E07040342017028567195004B000A010055000000000000000001F0F61E06FD3D0F000000000000220521151419010400000000300114310100542405D46BA64B22BC32ECDA599807C12A54E061460D912890F7B225CEDB2690F7B226E83922E1014EEA0200010055000000000000000001F0F61E06FD3D0F000000000000220521151429010400000000300114310100542405D46BA64B22BC32ECDA599807C12A54E061460D912890F7B225CEDB2690F7B226E83922E1014EEA0200010055000000000000000001F0F61E06FD3D0F000000000000220521151439010400000000300114310100542405D46BA64B22BC32ECDA599807C12A54E061460D912890F7B225CEDB2690F7B226E83922E1014EEA0200010048000000000000000001F0F61E06FD3D0F0000000000002205211525200104000000003001173101009F173436302C30302C353533342C30383238323561302C3233E1014EEA0200010048000000000000000001F0F61E06FD3D0F0000000000002205211525300104000000003001173101009F173436302C30302C353533342C30383238323561302C3233E1014EEA0200010048000000000000000001F0F61E06FD3D0F0000000000002205211525400104000000003001173101009F173436302C30302C353533342C30383238323561302C3233E1014EEA0200010055000000000000000001F0F67E07040335017028567195004C000A010055000000000000000001F0F61E06FD3D0F000000000000220521152632010400000000300115310100542405ECDA599807C13454E061460D912A90F7B225CEDB2390F7B226E83923ECF8EB205F0923E1014EEA0200010055000000000000000001F0F61E06FD3D0F000000000000220521152642010400000000300115310100542405ECDA599807C13454E061460D912A90F7B225CEDB2390F7B226E83923ECF8EB205F0923E1014EEA0200010048000000000000000001F0F61E06FD3D0F0000000000002205211526520104000000003001153101009F173436302C30302C353533342C30383238323561302C3231E1014EEA0200010048000000000000000001F0F61E06FD3D0F0000000000002205211527020104000000003001153101009F173436302C30302C353533342C30383238323561302C3231E1014EEA0200010048000000000000000001F0F61E06FD3D0F0000000000002205211528220104000000003001133101009F173436302C30302C353533342C30383238323561302C3139E1014EEA0200010055000000000000000001F0F61E06FD3D0F000000000000220521152832010400000000300116310100542405ECDA599807C12A54E061460D9128ECF8EB205F092590F7B225CEDB2390F7B226E83921E1014EEA0200010055000000000000000001F0F61E06FD3D0F000000000000220521152953010400000000300113310100542405ECDA599807C13154E061460D912890F7B226E8392190F7B225CEDB20ECF8EB205F0920E1014EEA0200010055000000000000000001F0F61E06FD3D0F000000000000220521153003010400000000300113310100542405ECDA599807C13154E061460D912890F7B226E8392190F7B225CEDB20ECF8EB205F0920E1014EEA0200010055000000000000000001F0F61E06FD3D0F000000000000220521153014010400000000300113310100542405ECDA599807C13154E061460D912890F7B226E8392190F7B225CEDB20ECF8EB205F0920E1014EEA0200010048000000000000000001F0F61E06FD3D0F0000000000002205211530440104000000003001133101009F173436302C30302C353533342C30383238323561302C3139E1014EEA020001A97E";
        String realMsgStr = "7Esdfsdf7E";
        String data = "7Esdfsdf7E";
        String substring = realMsgStr.substring(data.length());
        System.out.println("substring:"+substring);

        String[] split = realMsgStr.split("7E07040E7E", 1);
        System.out.println(split);

//        int i = realMsgStr.indexOf("7E", realMsgStr.indexOf("7E") + 1);
//
//        System.out.println(i);
//        String substring = realMsgStr.substring(realMsgStr.substring(2).indexOf("7E") + 2);
//        System.out.println(substring);
    }
}

import com.qycloud.iot.microserviceindoorpositioning.gps.utils.ByteUtils;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import lombok.extern.slf4j.Slf4j;

/**
 * @Author: WangHao
 * @Date: 2021/10/19/16:49
 * @Description:
 */
@Slf4j
public class MyTools {

    private static volatile MyTools myTools;

    private MyTools() {

    }

    public static MyTools getInstance() {

        if (null == myTools) {
            synchronized (MyTools.class) {
                if (null == myTools) {
                    myTools = new MyTools();
                }
            }
        }

        return myTools;
    }



    public void writeToClient(final String receiveStr, ChannelHandlerContext channel, final String mark) {
        try {
            ByteBuf bufff = Unpooled.buffer();//netty需要用ByteBuf传输
            bufff.writeBytes(ByteUtils.hexString2Bytes(receiveStr));//对接需要16进制
            channel.writeAndFlush(bufff).addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture future) throws Exception {
                    StringBuilder sb = new StringBuilder();
                    if (null != mark && !mark.isEmpty()) {
                        sb.append("【").append(mark).append("】");
                    }
                    if (future.isSuccess()) {
                       log.debug(sb.toString() + "回写成功" + receiveStr);

                    } else {
                        log.warn(sb.toString() + "回写失败" + receiveStr);

                    }
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
            log.warn("调用通用writeToClient()异常" + e.getMessage());

        }
    }

}

2.jtt808协议解析

采用策略模式解析

自定义注解


import com.qycloud.iot.microserviceindoorpositioning.gps.common.SignCode;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @Author: WangHao
 * @Date: 2021/10/21/18:29
 * @Description:
 */
@Target( ElementType.TYPE) //注解定义到类上
@Retention( RetentionPolicy.RUNTIME) //生命周期
public @interface Sign {
//    String value();
//    String message() ;
    SignCode value();

}

import com.qycloud.iot.microserviceindoorpositioning.gps.config.GpsConfiguration;
import com.qycloud.iot.microserviceindoorpositioning.gps.domain.GpsProtocol;
import com.qycloud.iot.microserviceindoorpositioning.gps.handle.ISign;
import com.qycloud.iot.microserviceindoorpositioning.gps.utils.SpringUtils;
import lombok.extern.slf4j.Slf4j;
import org.reflections.Reflections;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.HashMap;
import java.util.Set;

/**
 * @Author: WangHao
 * @Date: 2021/10/21/18:40
 * @Description:
 */
@Slf4j
public class SignFactory {

    @Autowired
    GpsConfiguration gpsConfiguration;

    private static HashMap<String,String> sourceMap = new HashMap<>();

    /**
     * 单例
     *
     */
    static {

        //反射工具包,指明扫描路径
        Reflections reflections = new Reflections("com.qycloud.iot.microserviceindoorpositioning");
        //获取带我们pay注解的类
        Set<Class<?>> classSet =  reflections.getTypesAnnotatedWith(Sign.class);
        //根据注解的值,将全类名放到map中
        for (Class clazz : classSet){
            Sign pay = (Sign) clazz.getAnnotation(Sign.class);
            sourceMap.put(pay.value().getCode(),clazz.getCanonicalName());
        }

    }



    public static ISign getInstance(GpsProtocol gpsProtocol){
        ISign chatType = null;
        try {
            //取得全类名
            String className = sourceMap.get(gpsProtocol.getSignCode());
            //取得类对象
            Class  clazz= Class.forName(className);



            //======================创建对象=====================
            chatType  =  (ISign) SpringUtils.getInterface(clazz);

        }catch (Exception e){
            log.error("尚未定义处理类标志:"+gpsProtocol.getSignCode()+"原始数据为:"+gpsProtocol.getRawData(),e);
        }

        return chatType;
    }



}

/**
 * @Author: WangHao
 * @Date: 2021/10/22/13:51
 * @Description:
 */
public enum SignCode {
    Sign_0002("0002", "终端心跳"),
    Sign_0100("0100", "终端注册"),
    Sign_0102("0102", "终端鉴权"),
    Sign_0104("0104", "查询终端参数应答"),
    Sign_0200("0200", "位置信息汇报"),
    Sign_0704("0704", "定位数据批量上传"),
    Sign_8001("8001", "平台通用应答"),
    Sign_8100("8100", "终端注册应答"),
    Sign_8104("8104", "查询终端参数"),
    ;


    SignCode(String code, String desc) {
        this.code = code;
        this.desc = desc;
    }

    private String code;
    private String desc;

    public String getCode() {
        return code;
    }


    public String getDesc() {
        return desc;
    }

}

开始解析


import com.qycloud.iot.microserviceindoorpositioning.gps.domain.GpsProtocol;
import com.qycloud.iot.microserviceindoorpositioning.gps.handle.ISign;
import com.qycloud.iot.microserviceindoorpositioning.gps.sign.SignFactory;
import com.qycloud.iot.microserviceindoorpositioning.gps.utils.GpsAnalyseUtils;
import io.netty.channel.ChannelHandlerContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

/**
 * @Author: WangHao
 * @Date: 2021/10/21/10:05
 * @Description: Gps协议数据解析类
 */
@Component
@Slf4j
public class GpsProtocolDataHandler {


    /**
     * Gps
     *
     * @param ctx
     * @param oriMsg
     */
    public void handleProtocol(ChannelHandlerContext ctx, String oriMsg) {
        try {
            GpsProtocol gpsProtocol = GpsAnalyseUtils.handleGpsProtocol(oriMsg);
            if (null == gpsProtocol) return;


            ISign instance = SignFactory.getInstance(gpsProtocol);
            if (null != instance) {
                instance.handle(ctx, gpsProtocol);
            }


        } catch (Exception e) {
            log.error("原始数据:"+oriMsg);
            log.error("GPS错误信息:",e);
            e.printStackTrace();
        }
    }




}

aa
aa
aa
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值