spring boot结合Netty对接环保协议HJ212-2017数据接收

1.建立springboot项目(只建接收端)

在handler文件夹下新建EPServerHandler.java
在service下新建EPServer.java
在utils下新建HJ212MsgUtils工具类处理数据
在这里插入图片描述

2.项目代码

EPServerHandler.java

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.timeout.IdleStateEvent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@Component
public class EPServerHandler extends ChannelInboundHandlerAdapter{
/**
     * 定义一个HashMap,用于保存所有的channel和设备ID的对应关系。
     */
    private static Map deviceInfo = new HashMap(64);
/**
     * 消息读取
     */
   @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        log.info("=============" + (new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss")).format(new Date()) + "=============");
        log.info("收到" + ctx.channel().id() + "设备消息 ===> " + msg);

        // 解析物联网设备发来的数据
        //JSONObject data = HJ212MsgUtils.dealMsg1((String) msg);
        //System.out.println(data);

        //String msg1=data.toJSONString();
       //System.out.println(msg);
//     log.info("数据库插入结果:"+res);
        //log.info("设备消息解析JSON结果:" + data.toJSONString());
    }

    /***
     * 超时关闭socket 连接
     */
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent event = (IdleStateEvent) evt;

            String eventType = null;
            switch (event.state()) {
                case READER_IDLE:
                    eventType = "读超时";
                    break;
                case WRITER_IDLE:
                    eventType = "写超时";
                    break;
                case ALL_IDLE:
                    eventType = "读写超时";
                    break;
                default:
                    eventType = "设备超时";
            }
            log.warn(ctx.channel().id() + " : " + eventType + "---> 关闭该设备");
            ctx.channel().close();
        }
    }

    /**
     * 异常处理, 出现异常关闭channel
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        log.error("========= 链接出现异常!exceptionCaught.");
        ctx.fireExceptionCaught(cause);
        // 将通道从deviceInfo中删除
        deviceInfo.remove(ctx.channel().id());
        // 关闭通道链接,节省资源
        ctx.channel().close();
    }

    /**
     * 每加入一个新的链接,保存该通道并写入上线日志。该方法在channelRead方法之前执行
     */
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) {
        // 获取设备ID
        String deviceId = "abc";
        // 将该链接保存到deviceInfo
        deviceInfo.put(ctx.channel().id(), deviceId);
        log.warn("========= " + ctx.channel().id() + "设备加入链接。");
    }

    /**
     * 每去除一个新的链接,去除该通道并写入下线日志
     */
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) {
        // 将通道从deviceInfo中删除
        deviceInfo.remove(ctx.channel().id());
        log.warn("========= " + ctx.channel().id() + "设备断开链接。");
    }

}

EPServer.java

import com.system.office.office_system.handler.EPServerHandler;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
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.http.websocketx.WebSocketFrameAggregator;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.timeout.IdleStateHandler;
import io.netty.util.CharsetUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.annotation.Order;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;
/**
 * @Describe 环保设备对接程序启动类,Netty服务器端启动类,接收设备上传的数据。
 * 重写ApplicationRunner为系统启动类
 */
 @Slf4j
@Component
@Order(1)
@EnableAsync
public class EPServer implements ApplicationRunner {

    /**
     * Netty服务端监听的端口号
     */
    public static int PORT=1008;

    /**
     * 分发线程组
     */
    private static final EventLoopGroup bossGroup = new NioEventLoopGroup(2);

    /**
     * 工作线程组
     */
    private static final EventLoopGroup workerGroup = new NioEventLoopGroup(4);
    /**
     * 启动服务
     */
    public static void runEPServer(){
        log.info("===== EP Netty Server start =====");
        try{
            ServerBootstrap b = new ServerBootstrap();
//            WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(getWebSocketLocation(req), null, true,6553600);
            //创建事件循环组
            b.group(bossGroup, workerGroup);
            b.channel(NioServerSocketChannel.class);
            b.childHandler(new ChannelInitializer<SocketChannel>() {
                @Override
                public void initChannel(SocketChannel ch) {

                    ChannelPipeline pipeline = ch.pipeline();
                    pipeline.addLast("serverDecoder", new StringDecoder(CharsetUtil.UTF_8));
                    pipeline.addLast("serverEncoder", new StringEncoder(CharsetUtil.UTF_8));
                    pipeline.addLast("frame-aggregator", new WebSocketFrameAggregator(10 * 1024 * 1024));
                    // netty提供了空闲状态监测处理器 0表示禁用事件
                    pipeline.addLast(new IdleStateHandler(65,0,0, TimeUnit.MINUTES));
                    pipeline.addLast(new EPServerHandler());
                }
            });
            log.info("环保 Netty Server PORT = " + PORT);
            b.bind(PORT).sync();

        }catch (Exception e){
            e.printStackTrace();
            shutdown();
        }
    }
    /**
     * 关闭服务
     */
    public static void shutdown(){
        // 优雅关闭
        workerGroup.shutdownGracefully();
        bossGroup.shutdownGracefully();
        log.info("已关闭 Netty Server");
    }

    @Override
    public void run(ApplicationArguments args) {
        // 启动环保监测Netty服务端
        runEPServer();
    }

}

HJ212MsgUtils.java

package com.system.office.office_system.utils;

import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class HJ212MsgUtils {
    public static JSONObject dealMsg1(String msg) {
        JSONObject data = new JSONObject();
        try {
            // 拆分消息
            String[] subMsg = msg.split("&&");

            // 清洗消息头基本数据
            String headStr = subMsg[0].substring(2).replace(";CP=", "").replace("=", "\":\"")
                    .replace(",", "\",\"").replace(";", "\",\"");
            data.put("SIZE", headStr.substring(0, 4));
            data.putAll(JSONObject.parseObject("{\"" + headStr.substring(4) + "\"}"));

            // 清洗数据体基本数据
            String paramStr = subMsg[1].replace("=", "\":\"").replace(",", "\",\"")
                    .replace(";", "\",\"");
            data.put("CP", JSONObject.parseObject("{\"" + paramStr + "\"}"));

            // 保存消息尾数据,主要是CRC校验和包结束符
            data.put("End", subMsg[2]);

        } catch (Exception e) {
            log.error("HJ212数据转JSON错误。报错信息:{},消息内容:{}", e.getMessage(), msg);
            e.printStackTrace();
        }
        return data;
    }

}

3.出现的问题及解决方案

hj212-2017协议中的数据太长会导致TCP出现粘包或拆包现象
在这里插入图片描述
原因:TCP作为底层协议不了解上层业务具体逻辑,所以是否为一个完整的包,是按照TCP缓冲区处理逻辑来划分的
解决方案
通过hj212-2017官方文档
在这里插入图片描述
发现包尾固定回车换行符,于是可以添加一个回车换行符解码器,Netty也为我们写了LineBasedFrameDecodere 类专门处理此类情况
在EPServer中添加回车换行符解码器即可

//添加一个回车换行符解码器(当数据解读的长度达到最大846时,仍没有看到回车换行,抛出异常)
                    pipeline.addLast(new LineBasedFrameDecoder(12+834));
                    
                    pipeline.addLast("serverDecoder", new StringDecoder(CharsetUtil.UTF_8));
                    pipeline.addLast("serverEncoder", new StringEncoder(CharsetUtil.UTF_8));
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

事情判断忘记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值