spring boot 结合netty搭建服务端实现自定义协议全过程

 

 

前言

     公司准备做一个关于物联网的试点项目,其中硬件部门需要与我们软件部进行数据对接,将设备获取的数据传给我们软件部门处理。于是安排我来负责与硬件部门数据的对接,搭建netty服务器数据平台.(我们公司是一个小公司,人员有限!!!)

     于是现学netty服务端,现在框架也搭得差不多,把整个过程分享出来,希望能帮助到大家,有不足之处请指导。

1.协议的制定   

       数据头: 两个字节,固定值

       设备id号:代表每个通讯的设备MAC地址;SN号对每个设备来说客户端和服务端累加,但是各不相干,有一个内部约定就是如果是客户端发起指令,那么服务端返回指令时返回相同的SN号,同理,如果是服务端发起指令,那么客户端返回指令时也返回相同的指令号。这样做的目的是,例如服务端同时向某个设备发送多条相同指令,客户端做出相应回应,区分开回应的指令分别对应服务端的那一条。

       加密方式: 自定义

       指令功能码:

      crc16:对之前的所有字节做crc循环冗余校验算法。

2. netty服务器具体实现

2.1 消息体的创建

Message : 对应上面的协议。

package org.example.entity;

public class Message {
    /**数据头*/
    private byte[] header;
    /**设备ID号*/
    private String deviceId;
    /**SN号*/
    private int sn;
    /**加密方式*/
    private int encryMode;
    /**指令功能码*/
    private CommandCode commandCode;
    /**数据体长度*/
    private int bodyLength;
    /**数据体*/
    private byte[] body;
    /**CRC16校验码*/
    private int crc16;

    public Message() {
    }

    public Message(byte[] header, String deviceId, int sn, int encryMode, CommandCode commandCode, int bodyLength, byte[] body, int crc16) {
        this.header = header;
        this.deviceId = deviceId;
        this.sn = sn;
        this.encryMode = encryMode;
        this.commandCode = commandCode;
        this.bodyLength = bodyLength;
        this.body = body;
        this.crc16 = crc16;
    }


    public String getDeviceId() {
        return deviceId;
    }

    public void setDeviceId(String deviceId) {
        this.deviceId = deviceId;
    }

    public int getSn() {
        return sn;
    }

    public void setSn(int sn) {
        this.sn = sn;
    }

    public int getEncryMode() {
        return encryMode;
    }

    public void setEncryMode(int encryMode) {
        this.encryMode = encryMode;
    }

    public CommandCode getCommandCode() {
        return commandCode;
    }

    public void setCommandCode(CommandCode commandCode) {
        this.commandCode = commandCode;
    }

    public int getBodyLength() {
        return bodyLength;
    }

    public void setBodyLength(int bodyLength) {
        this.bodyLength = bodyLength;
    }

    public byte[] getHeader() {
        return header;
    }

    public void setHeader(byte[] header) {
        this.header = header;
    }

    public byte[] getBody() {
        return body;
    }

    public void setBody(byte[] body) {
        this.body = body;
    }

    public int getCrc16() {
        return crc16;
    }

    public void setCrc16(int crc16) {
        this.crc16 = crc16;
    }
}

2.2 Channel、EventLoop(Group)和ChannelFuture 的创建

package org.example.server;


import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

/**
 * 设备服务提供方
 */
@Service
public class DeviceNettyServer implements Runnable, InitializingBean, DisposableBean {

    @Value("${device.netty.port}")
    private int port;

    private EventLoopGroup bossGroup;
    private EventLoopGroup workerGroup;
    private Channel channel;
    private Thread server;


    @Autowired
    NettyServerInitializer nettyServerInitializer;

    @Override
    public void afterPropertiesSet() throws Exception {
        server = new Thread(this);
        server.start();
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("destroy server resources");
        if (null == channel) {
            System.out.println("server channel is null");
        }
        bossGroup.shutdownGracefully();
        workerGroup.shutdownGracefully();
        channel.closeFuture().syncUninterruptibly();
        bossGroup = null;
        workerGroup = null;
        channel = null;
    }

    @Override
    public void run() {
        bossGroup = new NioEventLoopGroup();
        workerGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup);
            b.channel(NioServerSocketChannel.class);
            b.childHandler(nettyServerInitializer);

            // 服务器绑定端口监听
            ChannelFuture f = b.bind(port).sync();
            // 监听服务器关闭监听
            f.channel().closeFuture().sync();
            channel = f.channel();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }

    }



}
package org.example.server;

import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import org.example.entity.Message;
import org.example.enums.CommandCodeEnum;
import org.example.enums.DeviceMapping;
import org.example.enums.EncryModeEnum;
import org.example.enums.ProductTypeEnum;
import org.example.handler.*;
import org.example.util.CRCUtil;
import org.example.util.ProtocolUtil;
import org.example.util.SersorUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.net.SocketAddress;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

@Component
public class NettyServerInitializer extends ChannelInitializer<SocketChannel> {

    private static  Map<SocketAddress, Channel> channelMap = new ConcurrentHashMap<>();
    private static  Map<Integer, Map<String, Channel>> deviceChannelMap = new ConcurrentHashMap<>();
    private static Map<Channel, AtomicInteger> snMap = new ConcurrentHashMap<>();

    @Autowired
    private UpDataParserHandler upDataParserHandler;


    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();

        // 根据协议格式,自定义的解码器
//        pipeline.addLast("headerDecoder", new HeaderDecoder());
        pipeline.addLast("frameDecoder", new HeaderDecoder(65535, 17, 2, 2, 0));

        // 字符串解码
        pipeline.addLast("decoder", new MessageDecoder());

        //维护心跳
        pipeline.addLast("heart", new HeartBeatHandler(channelMap, deviceChannelMap, snMap));

        // 自己的逻辑Handler
        pipeline.addLast("upParser", upDataParserHandler);
        pipeline.addLast("trackSpike", new TrackSpikeHandler(channelMap, deviceChannelMap, snMap));
        pipeline.addLast("UpgradeHandler", new OnlineUpdateServerHandler(channelMap, deviceChannelMap));
        pipeline.addLast("lightHandler", new TunnelControlServerHandler(channelMap, deviceChannelMap));

        // 字符串编码
        pipeline.addLast("encoder", new MessageEncoder());

    }

    @Scheduled(fixedDelay = 1000 * 10, initialDelay = 1000 * 5)
    public void checkChannel(){
        for(Map.Entry<SocketAddress, Channel> entry : channelMap.entrySet()){
            SocketAddress key = entry.getKey();
            Channel channel = entry.getValue();
            if(!channel.isActive()){
                channelMap.remove(key);
                snMap.remove(channel);

                Iterator<Map.Entry<Integer, Map<String, Channel>>> iterator = deviceChannelMap.entrySet().iterator();
                while (iterator.hasNext()){
                    Map.Entry<Integer, Map<String, Channel>> mapEntry = iterator.next();
                    Map<String, Channel> channelMap = mapEntry.getValue();

                    Iterator<Map.Entry<String, Channel>> iterator1 = channelMap.entrySet().iterator();
                    while (iterator1.hasNext()){
                        Map.Entry<String, Channel> next = iterator1.next();
                        Channel removeChannel = next.getValue();
                        if(channel.equals(removeChannel)){
                            channelMap.remove(next.getKey());
                        }
                    }
                }
            }
        }
    }
}

其工作的大体流程如下:

2.3 创建一系列handler

  handler的流向关系:

 

LengthFieldBasedFrameDecoder:解决粘包/半包问题, 其中参数含义:

maxFrameLength:表示的是包的最大长度 
lengthFieldOffset:指的是长度域的偏移量,表示跳过指定个数字节之后的才是长度域
lengthFieldLength:记录该帧数据长度的字段,也就是长度域本身的长度
lengthAdjustment:长度的一个修正值,可正可负
initialBytesToStrip:从数据帧中跳过的字节数,表示得到一个完整的数据包之后,忽略 多少字节,开始读取实际我要的数据 

HeaderDecoder: 继承自 LengthFieldBasedFrameDecoder, 重写了getUnadjustedFrameLength() 方法, 该方法会被decode() 调用,这样做的主要目的是当客户端发送指令时,如果没有按照规定的格式发送,例如发送指令 4A 54 AB AB AB AB AB AB AB AB 00 00 00 01 03 10 0F 00 03 00 00 06 1B,根据协议约定, 数据体长度字节为 00 03 ,实际里面只有两个字节,LengthFieldBasedFrameDecoder就会按照3个字节读取,会读取到下一条指令,把整个读取的字节缓冲区打乱;为了避免类似情况,每一次LengthFieldBasedFrameDecoder解析数据时,就判断一下,如果读取到的数据头不是定义的数据头,就把这些字节丢弃,一直到读取到定义到的头部信息为止。同时这样还可以剔除不符合我们数据格式的数据!
package org.example.handler;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.DecoderException;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import org.example.constant.Constants;
import org.example.util.SersorUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.nio.ByteOrder;
import java.util.Arrays;

public class HeaderDecoder extends LengthFieldBasedFrameDecoder {
    private static final Logger LOG = LoggerFactory.getLogger(HeaderDecoder.class);

    public HeaderDecoder(int maxFrameLength, int lengthFieldOffset, int lengthFieldLength, int lengthAdjustment, int initialBytesToStrip) {
        super(maxFrameLength, lengthFieldOffset, lengthFieldLength, lengthAdjustment, initialBytesToStrip);
    }

    @Override
    protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
        return super.decode(ctx, in);
    }

    @Override
    protected long getUnadjustedFrameLength(ByteBuf buf, int offset, int length, ByteOrder order) {
        buf = buf.order(order);
        if(!checkHeader(buf)){
            byte[] data = new byte[buf.writerIndex()];
            buf.getBytes(0, data);
            LOG.info(" Receive data: " + SersorUtil.bytesToHex(data));
            throw new DecoderException("Header is error, ByteBuf be modified!");
        }
        long frameLength;
        switch (length) {
            case 1:
                frameLength = buf.getUnsignedByte(offset);
                break;
            case 2:
                frameLength = buf.getUnsignedShort(offset);
                break;
            case 3:
                frameLength = buf.getUnsignedMedium(offset);
                break;
            case 4:
                frameLength = buf.getUnsignedInt(offset);
                break;
            case 8:
                frameLength = buf.getLong(offset);
                break;
            default:
                throw new DecoderException(
                        "unsupported lengthFieldLength: " + length + " (expected: 1, 2, 3, 4, or 8)");
        }
        return frameLength;
    }

    /**
     * 检查有信息
     * @param in
     */
    private boolean checkHeader(ByteBuf in){
        boolean flag = true;
        int writer = in.writerIndex();
        byte[] header = new byte[2];
        int beginIndex = in.readerIndex();
        in.getBytes(beginIndex, header);
        if(!Arrays.equals(header, Constants.HEADER_DEFAULT)){
            flag = false;
        }
        while (!Arrays.equals(header, Constants.HEADER_DEFAULT) && beginIndex < writer){
            beginIndex++;
            in.readerIndex(beginIndex);
            in.getBytes(beginIndex, header);
        }
        return flag;
    }
}
MessageDecoder : 自定义解码器。实现将读取到的字节数据转化为 Message。
package org.example.handler;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import org.example.entity.CommandCode;
import org.example.entity.Message;
import org.example.util.CRCUtil;
import org.example.util.SersorUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.SocketAddress;
import java.util.List;
import java.util.zip.CRC32;

/**
 * 解码器
 */
public class MessageDecoder extends ByteToMessageDecoder {

    private static final Logger LOG = LoggerFactory.getLogger(MessageDecoder.class);

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        SocketAddress socketAddress = ctx.channel().remoteAddress();
        if(in.isReadable()){
            try {
                byte[] data = new byte[in.readableBytes()];
                in.getBytes(0, data);
                LOG.info(socketAddress + ", Receive data: " + SersorUtil.bytesToHex(data));
                //头部信息
                byte[] header = new byte[2];
                in.readBytes(header);
                //设备ID号
                byte[] deviceId = new byte[8];
                in.readBytes(deviceId);
                String serialNumber = SersorUtil.bytesToHex(deviceId);
                //SN号
                int sn = in.readUnsignedShort();
                //加密方式
                int encryMode = in.readUnsignedByte();
                //指令功能码
                CommandCode commandCode = new CommandCode(in.readUnsignedByte(), in.readUnsignedByte(), in.readUnsignedShort());
                //数据体长度
                int bodyLength = in.readUnsignedShort();
                //数据体
                byte[] body = new byte[bodyLength];
                in.readBytes(body);
                //CRC校验码
                int crc16 = in.readUnsignedShort();
                //校验数据
                ByteBuf crcBuf = Unpooled.buffer(in.readerIndex() - 2);
                in.getBytes(0, crcBuf, in.writerIndex() - 2);
                int dataCrc = CRCUtil.getCRC16(crcBuf);
                if(crc16 == dataCrc){
                    Message message = new Message(header, serialNumber, sn, encryMode, commandCode, bodyLength, body, crc16);
                    out.add(message);
                }else {
                    //校验不对时, 丢弃包
                    LOG.warn(socketAddress + ", Data is discarded, " + "getCrc: " + crc16 + " calculateCrc: " + dataCrc);
                }

            }catch (IndexOutOfBoundsException e){
                e.printStackTrace();
                in.clear();
                LOG.error(ctx.channel().remoteAddress() + ", 数据格式不正确");
            }catch (Exception e){
                e.printStackTrace();
                in.clear();
            }

        }
    }
}
HeartBeatHandler: 维护心跳,缓存设备与渠道之间的关系
package org.example.handler;

import io.netty.channel.*;
import org.example.constant.Constants;
import org.example.entity.BaseDev;
import org.example.entity.Message;
import org.example.enums.ProductTypeEnum;
import org.example.server.DeviceManager;
import org.example.util.ApplicationContextUtil;
import org.example.util.ProtocolUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;

import java.net.SocketAddress;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;


@ChannelHandler.Sharable
public class HeartBeatHandler extends ChannelInboundHandlerAdapter {

    private static final Logger LOG = LoggerFactory.getLogger(HeartBeatHandler.class);
    private Map<SocketAddress, Channel> channelMap;
    private Map<Integer, Map<String, Channel>> deviceChannelMap;
    private Map<Channel, AtomicInteger> snMap;

    public HeartBeatHandler() {
    }

    public HeartBeatHandler(Map<SocketAddress, Channel> channelMap, Map<Integer, Map<String, Channel>> deviceChannelMap, Map<Channel, AtomicInteger> snMap) {
        this.channelMap = channelMap;
        this.deviceChannelMap = deviceChannelMap;
        this.snMap = snMap;
    }

    private DeviceManager deviceManager = ApplicationContextUtil.getBean(DeviceManager.class);

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        Channel channel = ctx.channel();
        Message message = (Message) msg;
        String deviceId = message.getDeviceId();
        int functionCode = message.getCommandCode().getFunctionCode();
        int productType = message.getCommandCode().getProductType();
        int returnCode = message.getCommandCode().getReturnCode();
        Optional<BaseDev> optional = Optional.ofNullable(deviceManager.get(deviceId));
        Integer devTypeId =  optional.orElse(new BaseDev()).getDevTypeId();
        initChannelMap(channel, message, devTypeId);
        if(Constants.DATA_RETURN == functionCode && Constants.HEART_BEAT == returnCode){
            Message heart = ProtocolUtil.buildHeartMessage(message);
            LOG.info(channel.remoteAddress() + ", Get the heartbeat, productType :" + productType + ", functionCode : " + functionCode);
            channel.writeAndFlush(heart);
        }else {
            ctx.fireChannelRead(msg);
        }

    }

    /**
     * 初始化连接
     * @param channel
     * @param message
     * @param devTypeId
     */
    private void initChannelMap(Channel channel, Message message,  Integer devTypeId) {
        String deviceId = message.getDeviceId();

        LOG.info(channel.remoteAddress() + ", client relevance deviceId : " + deviceId);

        if(deviceChannelMap.containsKey(devTypeId)){
            Map<String, Channel> channelMap = deviceChannelMap.get(devTypeId);
            if(!channelMap.containsKey(deviceId)){
                channelMap.put(deviceId, channel);
            }else {
                Channel channel1 = channelMap.get(deviceId);
                if(!channel.equals(channel1)){
                    channelMap.put(deviceId, channel1);
                }
            }
        }else {
            Map<String, Channel> channelMap = new ConcurrentHashMap<>();
            channelMap.put(deviceId, channel);
            deviceChannelMap.put(devTypeId, channelMap);
        }
    }

    @Override
    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        SocketAddress socketAddress = ctx.channel().remoteAddress();
        LOG.info(socketAddress+ " Channel are Registered" );
        super.channelRegistered(ctx);
    }

    @Override
    public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
        LOG.info(ctx.channel().remoteAddress() + " Channel are Unregistered" );
        super.channelUnregistered(ctx);
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        SocketAddress socketAddress = ctx.channel().remoteAddress();
        LOG.info(socketAddress + "  Channel are Activated" );
        channelMap.put(socketAddress, ctx.channel());
        snMap.put(ctx.channel(), new AtomicInteger(0));
        super.channelActive(ctx);

    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        SocketAddress socketAddress = ctx.channel().remoteAddress();
        LOG.info(socketAddress + "  Channel are Inactive" );
        removeChannelByMap(socketAddress);
        snMap.remove(ctx.channel());
        super.channelInactive(ctx);

    }

    /**
     * 移除过期的连接
     * @param socketAddress
     */
    private void removeChannelByMap(SocketAddress socketAddress) {
        Channel channel = channelMap.remove(socketAddress);
        for (Map.Entry<Integer, Map<String, Channel>> entry : deviceChannelMap.entrySet()){
            Map<String, Channel> childChannelMap = entry.getValue();
            for(Map.Entry<String, Channel> channelEntry : childChannelMap.entrySet()){
                String key = channelEntry.getKey();
                if(channel == channelEntry.getValue()){
                    childChannelMap.remove(key);
                }
            }
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        LOG.error(ctx.channel().remoteAddress() + " Catch exceptions :" + cause.getMessage() );
    }
}
MessageEncoder: 自定义解码器,将Message转化为字节。
package org.example.handler;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import org.example.entity.Message;
import org.example.util.CRCUtil;
import org.example.util.SersorUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MessageEncoder extends MessageToByteEncoder<Message> {

    private static final Logger LOG = LoggerFactory.getLogger(MessageEncoder.class);

    @Override
    protected void encode(ChannelHandlerContext ctx, Message msg, ByteBuf out) throws Exception {
        ByteBuf buffer = Unpooled.buffer();

        byte[] header = msg.getHeader();
        buffer.writeBytes(header);
        String deviceId = msg.getDeviceId();
        buffer.writeBytes(SersorUtil.hexString2Bytes(deviceId));
        buffer.writeShort(msg.getSn());
        buffer.writeByte(msg.getEncryMode());
        buffer.writeByte(msg.getCommandCode().getProductType());
        buffer.writeByte(msg.getCommandCode().getFunctionCode());
        buffer.writeShort(msg.getCommandCode().getReturnCode());
        buffer.writeShort(msg.getBodyLength());
        buffer.writeBytes(msg.getBody());
        buffer.writeShort(CRCUtil.getCRC16(Unpooled.wrappedBuffer(buffer)));

        byte[] sendMsg = new byte[buffer.readableBytes()];
        buffer.getBytes(0, sendMsg);
        LOG.info("send message : " + SersorUtil.bytesToHex(sendMsg));

        out.writeBytes(buffer);



    }
}

至此,整个netty服务端就都搭建完成了!

3. 上传数据解析示例

UpDataParserHandler : 解析数据的hanlder

package org.example.handler;

import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import org.example.constant.Constants;
import org.example.entity.BaseDev;
import org.example.entity.CommandCode;
import org.example.entity.Message;
import org.example.enums.IgnoreCommand;
import org.example.kafka.KafkaSender;
import org.example.paser.GeneralParserTemplate;
import org.example.factory.ParserFactory;
import org.example.server.DeviceManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.net.SocketAddress;
import java.util.Map;

@Component
@ChannelHandler.Sharable
public class UpDataParserHandler extends ChannelInboundHandlerAdapter {

    private static final Logger LOG = LoggerFactory.getLogger(UpDataParserHandler.class);

    @Autowired
    private KafkaSender kafkaSender;
    @Autowired
    private ParserFactory parserFactory;
    @Autowired
    private DeviceManager deviceManager;

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        Channel channel = ctx.channel();
        Message message = (Message) msg;
        CommandCode commandCode = message.getCommandCode();
        int functionCode = commandCode.getFunctionCode();
        if(Constants.DATA_UPLOAD == functionCode && !isIgnore(commandCode)){
            GeneralParserTemplate template = (GeneralParserTemplate)parserFactory.generateParser(commandCode.getProductType(), commandCode.getReturnCode());
            if(template != null){
                BaseDev baseDev = deviceManager.get(message.getDeviceId());

                template.readMessage(channel, message, kafkaSender, baseDev);
            }else {
                LOG.error("上传数据不能获取到响应的解析器; " + channel.remoteAddress() + ", ProductType : " + commandCode.getProductType() + ", ReturnCode : " + commandCode.getReturnCode());
            }

        }else {
            ctx.fireChannelRead(msg);
        }

    }

    private boolean isIgnore(CommandCode commandCode){
        IgnoreCommand[] values = IgnoreCommand.values();
        for(IgnoreCommand command : values){
            if(commandCode.getProductType() == command.getProductType() && commandCode.getReturnCode() == command.getReturnCode()){
                return true;
            }
        }
        return false;
    }

}

 GeneralParserTemplate :提供模板方法,解析数据,发送kafka

package org.example.paser;

import com.alibaba.fastjson.JSONObject;
import io.netty.channel.Channel;
import org.example.entity.BaseDev;
import org.example.entity.KafkaData;
import org.example.entity.Message;
import org.example.entity.ResponseMsg;
import org.example.enums.CommandCodeEnum;
import org.example.enums.TopicEnum;
import org.example.handler.HeartBeatHandler;
import org.example.kafka.KafkaSender;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.Timestamp;

public abstract class GeneralParserTemplate implements MessageParser {
    private static final Logger LOG = LoggerFactory.getLogger(GeneralParserTemplate.class);
    /**
     * 消息队列名称
     */
    public TopicEnum topic;
    /**
     * 产品类型
     */
    public int productType;
    /**
     * 自定义协议编码
     */
    public int returnCode;

    /**
     * 解析数据体内容
     * @param message
     * @return
     */
    public abstract String parserBody(Message message, ResponseMsg responseMsg);

    /**
     * 是否回写客户端
     * @return
     */
    public abstract boolean writeBack();

    /**
     * 回写客户端操作
     * @param channel
     * @param message
     */
    public abstract void writeToClient(Channel channel, Message message, ResponseMsg responseMsg);


    public final void readMessage(Channel channel, Message message, KafkaSender kafkaSender, BaseDev baseDev){
        //错误返回码
        ResponseMsg responseMsg = new ResponseMsg();
        if(null != baseDev){

            //解析数据
            String sendData = parserBody(message, responseMsg);
            //发送数据到消息队列
            if (topic != null){
                Timestamp timestamp = new Timestamp(System.currentTimeMillis());
                KafkaData data = new KafkaData(message.getDeviceId(), message.getCommandCode(), timestamp, baseDev.getProjectCode(), baseDev.getProjectName(), sendData);
                String msg = JSONObject.toJSONString(data);
                LOG.info(channel.remoteAddress() + ", send kafka message: " + msg);
                kafkaSender.send(msg, topic);
            }
            //写数据回客户端
            if(writeBack()){
                writeToClient(channel, message, responseMsg);
            }
        }else {
            LOG.warn(channel.remoteAddress() + ", deviceId: " + message.getDeviceId() + " not exist.");
        }
    }

}

 ReadMessage : 提供处理上传数据格式接口

package org.example.factory;

import io.netty.channel.ChannelHandlerContext;
import org.example.entity.Message;

public interface ReadMessage {

    public void handlerMsg(ChannelHandlerContext ctx, Message message);

}

 LowSpeedEventParser :解析示例

package org.example.paser;

import com.alibaba.fastjson.JSONObject;
import io.netty.channel.Channel;
import org.example.entity.LowSpeedEvent;
import org.example.entity.Message;
import org.example.entity.ResponseMsg;
import org.example.enums.CommandCodeEnum;
import org.example.enums.ProductTypeEnum;
import org.example.enums.TopicEnum;
import org.example.util.ProtocolUtil;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

@Component
public class LowSpeedEventParser extends GeneralParserTemplate{

    @PostConstruct
    public void init(){
        super.topic = TopicEnum.SPIKE_EXIGENCE;
        super.productType = ProductTypeEnum.TRACK_SPIKE.getCode();
        super.returnCode = 0x100E;
    }

    @Override
    public String parserBody(Message message, ResponseMsg responseMsg) {
        byte[] body = message.getBody();
        LowSpeedEvent spikeStatus = new LowSpeedEvent();
        int length = body.length;
        if(length == 8){
            for(int i = 0; i < length; i++){
                int value = body[i]  & 0xff;
                if(value != 0){
                    value = 1;
                }
                if(i == 0){
                    spikeStatus.setLeftOneLane(value);
                }
                if(i == 1){
                    spikeStatus.setLeftTwoLane(value);
                }
                if(i == 2){
                    spikeStatus.setLeftThreeLane(value);
                }
                if(i == 3){
                    spikeStatus.setLeftFourLane(value);
                }
                if(i == 4){
                    spikeStatus.setRightOneLane(value);
                }
                if(i == 5){
                    spikeStatus.setRightTwoLane(value);
                }
                if(i == 6){
                    spikeStatus.setRightThreeLane(value);
                }
                if(i == 7){
                    spikeStatus.setRightFourLane(value);
                }
            }
        }else {
            responseMsg.setErrorCode(CommandCodeEnum.ERROR_LENGTH.getCode());
        }
        return JSONObject.toJSONString(spikeStatus);
    }

    @Override
    public boolean writeBack() {
        return true;
    }

    @Override
    public void writeToClient(Channel channel, Message message, ResponseMsg responseMsg) {
        byte[] body = new byte[0];
        Message returnMessage = ProtocolUtil.buildReturnMessage(message, responseMsg.getErrorCode(), body);
        channel.writeAndFlush(returnMessage);
    }

    @Override
    public boolean match(int productType, int returnCode) {
        return this.productType == productType && this.returnCode == returnCode;
    }
}

整个流程至此就结束了,希望能帮助到大家!!!

  • 3
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值