第一步,netty服务搭建,本人整合了微服务,单机netty可以百度查到

<dependencies>

        <!-- SpringCloud Alibaba Nacos -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

        <!-- SpringCloud Alibaba Nacos Config -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>



        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <dependency>
            <groupId>com.hope</groupId>
            <artifactId>hope-common-redis</artifactId>
        </dependency>

        <dependency>
            <groupId>com.hope</groupId>
            <artifactId>hope-common-rocketmq</artifactId>
            <version>${hope.version}</version>
        </dependency>

    </dependencies>
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

/**
 * socket服务
 * @author zhangYuHui
 * date 2022-09-30
 */
@Slf4j
@Component
public class SocketServer {

    @Resource
    private SocketInitializer socketInitializer;

    @Getter
    private ServerBootstrap serverBootstrap;

    /**
     * netty服务监听端口
     */
    @Value("${netty.port:8088}")
    private int port;

    /**
     * 主线程组数量
     */
    @Value("${netty.bossThread:2}")
    private int bossThread;


    /**
     * 启动netty服务器
     */
    public void start() {
        this.init();
        try {
            this.serverBootstrap.bind(this.port).sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
            log.error("Netty error  on port: {} (TCP) with boss thread {}", this.port, this.bossThread);
        }
        log.info("Netty started on port: {} (TCP) with boss thread {}", this.port, this.bossThread);
    }

    /**
     * 初始化netty配置
     */
    private void init() {
        // 创建两个线程组,bossGroup为接收请求的线程组,一般1-2个就行
        NioEventLoopGroup bossGroup = new NioEventLoopGroup(this.bossThread);
        // 实际工作的线程组
        NioEventLoopGroup workerGroup = new NioEventLoopGroup();
        this.serverBootstrap = new ServerBootstrap();
        // 两个线程组加入进来
        this.serverBootstrap.group(bossGroup, workerGroup)
                // 配置为nio类型
                .channel(NioServerSocketChannel.class)
                .option(ChannelOption.SO_BACKLOG, 1024)
                // 保持连接
                .option(ChannelOption.SO_KEEPALIVE, true)
                .childOption(ChannelOption.SO_KEEPALIVE, true)
                // 加入自己的初始化器
                .childHandler(this.socketInitializer);
    }

}
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

/**
 * 监听Spring容器启动完成,完成后启动Netty服务器
 * @author zhangYuHui
 * date 2022-09-30
 */
@Component
public class NettyStartListener implements  ApplicationRunner {

    @Resource
    private SocketServer socketServer;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        this.socketServer.start();
    }

}
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import org.springframework.stereotype.Component;


/**
 * Socket 初始化器,每一个Channel进来都会调用这里的 InitChannel 方法
 *
 * @author zhangYuHui
 * date 2022-09-30
 */
@Component
public class SocketInitializer extends ChannelInitializer<SocketChannel> {

    @Override
    protected void initChannel(SocketChannel socketChannel){
        ChannelPipeline pipeline = socketChannel.pipeline();
        pipeline.addLast(new DelimiterBasedFrameDecoder(1024,false,true, Unpooled.copiedBuffer(new byte[]{0x23,0x23})));
        pipeline.addLast(new XFSocketHandler());
    }

}
import com.hope.netty.entity.QnResult;
import com.hope.netty.entity.SmartIotProtocol;
import com.hope.netty.service.FireSwitchboardService;
import com.hope.common.core.utils.SpringUtils;
import com.hope.common.netty.ByteUtil;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import lombok.extern.slf4j.Slf4j;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;


/**
 * 消防处理器,一般都是4040开头
 * @author zhangYuHui
 * date 2022-10-28
 */
@Slf4j
public class XFSocketHandler extends ChannelInboundHandlerAdapter {

    public static final Map<String, ChannelHandlerContext> ctxMap = new ConcurrentHashMap<>(16);

    public static FireSwitchboardService fireSwitchboardService;


    static {
        fireSwitchboardService = SpringUtils.getBean(FireSwitchboardService.class);
    }



    /**
     * 读取到客户端发来的消息
     * @param ctx ChannelHandlerContext
     * @param msg msg
     * @throws Exception e
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        // 由于我们配置的是 字节数组 编解码器,所以这里取到的用户发来的数据是 byte数组
        ByteBuf buf = (ByteBuf) msg;
        byte[] data = new byte[buf.readableBytes()];
        buf.readBytes(data);
        String strDate = ByteUtil.bytes2HexStr(data);
        log.info("消防报文数据: " + strDate);

        if (strDate.startsWith("4040") && strDate.endsWith("2323")){
            try {
                // 消防报文,目前只对接青鸟
                SmartIotProtocol smartIotProtocol = SmartIotProtocol.geEntity(data);
                fireSwitchboardService.beiDaQingNiao(smartIotProtocol,ctx.channel().id().asShortText());
            } finally {
                byte[] result = QnResult.success(data);
                ctx.writeAndFlush(Unpooled.copiedBuffer(result));
            }
        }

    }

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        String s = ctx.channel().id().asShortText();
        log.info("消防新的客户端链接: " + s);
        ctxMap.put( s,ctx);
    }


    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        String s = ctx.channel().id().asShortText();
        log.info("消防有的客户端与服务器断开连接: " + s);
        ctxMap.remove(s);
    }


    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.channel().close();
        log.info("消防出现异常,断开连接: " +ctx.channel().id().asShortText());
        ctxMap.remove(ctx.channel().id().asShortText());
    }


}

大概格式

springcloud 项目netty版本设置_.net

 

 4个实体

import com.hope.common.core.utils.DateUtils;
import com.hope.common.netty.ByteUtil;
import com.hope.common.netty.XfDict;
import lombok.Data;

import java.util.Arrays;

/**
 * 青鸟建筑消防设施部件运行状态
 * 青鸟用传装置 类型标志 02
 * @author zhangYuHui
 * date 2022-10-12
 */
@Data
public class QnComponentEntity {

    /**设备id*/
    private String id;

    /**ctxId*/
    private String ctxId;

    /**系统类型  10=消防联动控制器 */
    private String  systemType;

    /**系统地址 3=3号机 */
    private int systemAddress;

    /**部件类型*/
    private String partsType;

    /**部件地址*/
    private String partsAddress;

     /**状态  0测试状态 1 正常运行状态 */
    private int status;

    /**状态  0无火警 1 火警 */
    private int isFire;

    /**状态  0无故障 1 故障 */
    private int isFault;

    /**31字节的中文注释 字符集GB18030(GBK)C类型字符串数组(\0值尾部) 1-16分区消报消钮 */
    private String notes;

    /**状态发生时间*/
    private String date;

    /**系统当前时间*/
    private String sysDateTime;


    public static QnComponentEntity init(byte[] data){

        if (data.length != 48){
            return  null;
        }

        int i  = data[0] & 0xff;

        if (i != 2){
            return null;
        }
        QnComponentEntity entity = new QnComponentEntity();
        entity.setSystemType(XfDict.systemType(data[2] & 0xff));
        entity.setSystemAddress(data[3] & 0xff);
        entity.setPartsType(XfDict.partsType(data[4] & 0xff));
        entity.setPartsAddress(XfDict.partsAddress(Arrays.copyOfRange(data,5,9)));
        entity.setStatus(data[9] & 0x1);
        entity.setIsFire(data[9] >>> 1 & 0x1);
        entity.setIsFault(data[9] >>> 2 & 0x1);
        entity.setNotes(ByteUtil.bytes2HexStr(Arrays.copyOfRange(data,11,42)));
        entity.setDate(XfDict.getDate(Arrays.copyOfRange(data,42,48)));
        entity.setSysDateTime(DateUtils.getDateTime());
        return entity;
    }




}
import com.hope.common.core.utils.DateUtils;
import com.hope.common.netty.XfDict;
import lombok.Data;

import java.util.Arrays;

/**
 * 上传用户信息传输装置操作信息记录
 * 青鸟用传装置 类型标志 24
 * @author zhangYuHui
 * date 2022-10-12
 */
@Data
public class QnOperationEntity {

    /**设备id*/
    private String id;

    /**ctxId*/
    private String ctxId;

     /**状态  0 无操作 1 复位 */
    private int isReset;

    /**状态   0 无操作 1 消音 */
    private int isSilence;

    /**状态   0 无操作 1 报警 */
    private int isWarning;

    /**状态   0 无操作 1 自检  */
    private int isTesting;

    /**状态 操作员编号 */
    private int code;

    /**状态发生时间*/
    private String date;

    /**系统时间**/
    private String sysDateTime;



    public static QnOperationEntity init(byte[] data){

        if (data.length != 10){
            return  null;
        }

        int i  = data[0] & 0xff;

        if (i != 24){
            return null;
        }

        QnOperationEntity entity = new QnOperationEntity();
        entity.setIsReset(data[2] & 0x1);
        entity.setIsSilence(data[2] >>> 1 & 0x1);
        entity.setIsWarning(data[2] >>> 2 & 0x1);
        entity.setIsTesting(data[2] >>> 4 & 0x1);
        entity.setCode(data[3] & 0xff);
        entity.setDate(XfDict.getDate(Arrays.copyOfRange(data,4,10)));
        entity.setSysDateTime(DateUtils.getDateTime());
        return entity;
    }




}
import com.hope.common.netty.ByteUtil;
import lombok.Data;

import java.util.Arrays;

/**
 * 自定义协议
 *
 * @author zhangYuHui
 * date 2022-09-30
 */
@Data
public class SmartIotProtocol {


    /**
     * 数据包启动符号 @@
     */
    public String start;

    /**
     * 业务流水号
     */
    private String flowid;
    /**
     * 主版本
     */
    private byte versionMajor;
    /**
     * 次版本
     */
    private byte versionMinor;
    /**
     * 秒
     */
    private byte second;
    /**
     * 分钟
     */
    private byte minute;
    /**
     * 小时
     */
    private byte hour;
    /**
     * 日
     */
    private byte day;
    /**
     * 月
     */
    private byte month;
    /**
     * 年
     */
    private byte year;
    /**
     * 数据包的源地址
     */
    private byte[] src;
    /**
     * 数据包的目的地址
     */
    private byte[] dest;
    /**
     * 应用数据单元长度 长度不应大于1024;低字节传输在前
     */
    private int dataLen;
    /**
     * 命令字节 为控制单元的命令字节
     */
    private byte cmd;
    /**
     * 应用数据单元  对于确认/否认等命令包,此单元可为空
     */
    private byte[] data;
    /**
     * 校验和 控制单元中各字节数据(第3~第27字节)及应用数据单元的算术校验和,舍去8位以上的进位位后所形成的1字节二进制数
     */
    private byte checksum;
    /**
     * 协议结束符号 ##
     */
    public String  end ;


    /**
     * 打印调试信息
     */
    public void printDebugInfo(){
        System.out.println("---------完整数据包开始------------");
        System.out.println("|开始标志: " + start);
        System.out.println("|业务流水: " + flowid);
        System.out.println("|协议版本: " + printHexByte(versionMajor) + printHexByte(versionMinor));
        System.out.println("|时间标签: " + "20" + year + "-" + month + "-" + day + " " + hour + ":" + minute + ":" + second);
        System.out.println("|源地址 : " + ByteUtil.bytesToIp(src));
        System.out.println("|目的地址: " + ByteUtil.bytesToIp(dest));
        System.out.println("|数据长度: " + dataLen);
        System.out.println("|命令字节: " + printHexByte(cmd));
        System.out.println("|应用数据: " + printHexBytes(data));
        System.out.println("|校验字节: " + printHexByte(checksum));
        System.out.println("|结束标志: " + end);
        System.out.println("---------------------------------");
    }

    private String printHexByte(byte b){
        return String.format("%02X", b);
    }
    private String printHexBytes(byte[] bytes){
        String str = "";
        for(int i=0; i<bytes.length; i++){
            str += String.format("%02X", bytes[i]);
        }
        return str;
    }
    private String printHexShort(int s){
        byte[] bytes = hexShort(s);
        return printHexBytes(bytes);
    }
    private byte[] hexShort(int s){
        byte[] bytes = new byte[2];
        bytes[0] = (byte)((s << 24) >> 24);
        bytes[1] = (byte)((s << 16) >> 24);
        return bytes;
    }
    private byte[] hexInt(int n){
        byte[] bytes = new byte[4];
        bytes[3] = (byte) ((n      ) >> 24);
        bytes[2] = (byte) ((n <<  8) >> 24);
        bytes[1] = (byte) ((n << 16) >> 24);
        bytes[0] = (byte) ((n << 24) >> 24);
        return bytes;
    }



    /**
     * 初始化数据
     * @param data 字节
     * @return SmartIotProtocol
     */
    public static SmartIotProtocol geEntity( byte[] data ){
        SmartIotProtocol entity = new SmartIotProtocol();
        entity.setStart(ByteUtil.bytes2HexStr(data, 0, 2));
        entity.setFlowid(ByteUtil.bytes2HexStr(data, 2, 2));
        entity.setVersionMajor(data[4]);
        entity.setVersionMinor(data[5]);
        entity.setSecond(data[6]);
        entity.setMinute(data[7]);
        entity.setHour(data[8]);
        entity.setDay(data[9]);
        entity.setMonth(data[10]);
        entity.setYear(data[11]);
        entity.setSrc(Arrays.copyOfRange(data,12,17));
        entity.setDest(Arrays.copyOfRange(data,18,23));
        entity.setDataLen(ByteUtil.byteArrayToInt(Arrays.copyOfRange(data,24,26)));
        entity.setCmd(data[26]);
        entity.setData(Arrays.copyOfRange(data,27,data.length-3));
        entity.setChecksum(data[data.length-3]);
        entity.setEnd(ByteUtil.bytes2HexStr(data, data.length-2, 2));
        return  entity;
    }


}
import com.hope.common.netty.ByteUtil;

import java.util.Calendar;

/**
 * 青鸟消防返回报文
 *
 * @author zhangYuHui
 * date 2022-10-18
 */
public class QnResult {

    /**秒*/
    private static  byte SECOND;
    /**分*/
    private static  byte MINUTE;
    /**时*/
    private static  byte HOUR;
    /**日*/
    private static  byte DAY;
    /**月*/
    private static  byte MONTH;
    /**年*/
    private static  byte YEAR;



    /**
     * 成功回应,应答确认
     * @param data 原数据 3
     * @return byte[]
     */
    public static byte[] success(byte[] data){
        return  getBytes(data,(byte)0x03);
    }



    /**
     * 成功回应,否认确认
     * @param data 原数据
     * @return byte[]
     */
    public static byte[] error(byte[] data){
        return  getBytes(data,(byte)0x06);
    }

    /**
     * 返回对应的应答
     * @param data 原数据
     * @param b  3 对控制命令和发送信息的确认回答
     *           6 对控制命令和发送信息的否认回答
     * @return byte
     */
    public static byte[] getBytes(byte[] data,byte b){

        byte cac = getCac(data);

        return new byte[]{(byte)0x40, (byte)0x40,
                data[2],  data[3], (byte)0x01, (byte)0x01,
                SECOND, MINUTE, HOUR,DAY,MONTH,YEAR,
                data[18], data[19], data[20],data[21],data[22],data[23],
                data[12], data[13], data[14],data[15],data[16],data[17],(byte)0x00, (byte)0x00, b,
                cac,(byte)0x23, (byte)0x23};
    }



    /**
     * 控制单元中各字节数据(第3~27字节)及应用数据单元的算术校验和,舍去8位
     * (1字节) 以上的进位位后所形成的 1 字节二进制数
     * @param data 数据
     * @return byte
     */
    public  static byte getCac(byte[] data){
        Calendar c = Calendar.getInstance();
        SECOND = (byte) (c.get(Calendar.SECOND) & 0xff);
        MINUTE = (byte)(c.get(Calendar.MINUTE) & 0xff);
        HOUR = (byte)(c.get(Calendar.HOUR_OF_DAY) & 0xff);
        DAY = (byte)(c.get(Calendar.DATE) & 0xff);
        MONTH = (byte)((c.get(Calendar.MONTH) + 1) & 0xff);
        YEAR = (byte)(Integer.parseInt(String.valueOf(c.get(Calendar.YEAR)).substring(2)) & 0xff);

        byte[] success = new byte[] { data[2],  data[3], (byte)0x01, (byte)0x01,
                SECOND, MINUTE, HOUR,DAY,MONTH,YEAR,
                data[18], data[19], data[20],data[21],data[22],data[23],
                data[12], data[13], data[14],data[15],data[16],data[17],(byte)0x00, (byte)0x00,(byte)0x03};

       return ByteUtil.calCRC(success);
    }


}

service 层

import com.hope.netty.entity.QnComponentEntity;
import com.hope.netty.entity.QnOperationEntity;

/**
 * rocketmq 消息
 * @author zhangyuhui
 * @date 2022-07-20 16:03:07
 */
public interface RocketmqService {

    /**
     * 发送上传用户信息传输装置操作信息记录
     * @param init 原数据
     */
    void send(QnOperationEntity init);

    /**
     * 部件的运行状态
     * @param init 原数据
     */

    void send(QnComponentEntity init);
}
import com.hope.netty.entity.SmartIotProtocol;


/**
 * 消防总机service
 * @author hcbf
 * @version 1.0
 * @date 2022-07-20 16:03:07
 */
public interface FireSwitchboardService {


    /**
     * 北大青鸟消防主键
     * @param smartIotProtocol 消防设备报文解析
     * @param ctxId 客户端id
     */
    void beiDaQingNiao(SmartIotProtocol smartIotProtocol,String ctxId);




}
import com.hope.netty.entity.QnComponentEntity;
import com.hope.netty.entity.QnOperationEntity;
import com.hope.netty.service.RocketmqService;
import com.hope.rocketmq.constant.MqConstants;
import com.hope.rocketmq.domain.NqOperationExchange;
import com.hope.rocketmq.domain.QnComponentExchange;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;

/**
 * rocketmq消息
 * @author zhangYuHui
 * date 2022-10-24
 */

@Service
@Slf4j
public class RocketmqServiceImpl  implements RocketmqService {

    public final RocketMQTemplate rocketMQTemplate;



    public RocketmqServiceImpl(RocketMQTemplate rocketMQTemplate) {

        this.rocketMQTemplate = rocketMQTemplate;
    }


    @Override
    public void send(QnOperationEntity init) {
        if (init ==null){return;}

            int status = 0;

            if (1 == init.getIsReset()){
                status = 1;
            }else if (1 == init.getIsSilence()){
                status = 2;
            }  else  if (1 == init.getIsWarning()){
                status = 3 ;
            } else  if (1 == init.getIsTesting()){
                status = 4;
            }

            NqOperationExchange exchange = new NqOperationExchange();
            exchange.setId(init.getId())
                    .setCtxId(init.getCtxId())
                    .setCode(init.getCode())
                    .setDate(init.getDate())
                    .setSysDateTime(init.getSysDateTime()).setStatus(status);
            Message<NqOperationExchange> springMessage = MessageBuilder.withPayload(exchange).build();
            rocketMQTemplate.send(MqConstants.NQ_OPERATION_EXCHANGE,springMessage);
            log.info("发送上传用户信息传输装置操作信息记录");
    }


    @Override
    public void send(QnComponentEntity init) {

        if (init == null){return;}

        int status =  init.getStatus();
        if (1 == init.getIsFire()){
            status = 3;
        } else  if (1 == init.getIsFault()){
            status = 4;
        }
        QnComponentExchange exchange = new QnComponentExchange();
        exchange.setId(init.getId())
                .setCtxId(init.getCtxId())
                .setDate(init.getDate())
                .setSystemType(init.getSystemType())
                .setSystemAddress(init.getSystemAddress())
                .setPartsType(init.getPartsType())
                .setPartsAddress(init.getPartsAddress())
                .setSysDateTime(init.getSysDateTime()).setStatus(status);
        Message<QnComponentExchange> springMessage = MessageBuilder.withPayload(exchange).build();
        rocketMQTemplate.send(MqConstants.NQ_COMPONENT_EXCHANGE,springMessage);
        log.info("发送部件运行状态信息");
    }
}
import com.hope.netty.entity.QnComponentEntity;
import com.hope.netty.entity.QnOperationEntity;
import com.hope.netty.entity.SmartIotProtocol;
import com.hope.netty.service.FireSwitchboardService;
import com.hope.netty.service.RocketmqService;
import com.hope.common.core.constant.CacheConstants;
import com.hope.common.core.constant.Constants;
import com.hope.common.core.utils.SpringUtils;
import com.hope.common.netty.XfDict;
import com.hope.common.redis.service.RedisService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;


/**
 * 消防总机
 * @author zhangYuHui
 * date 2022-10-03
 */
@Service
@Slf4j
public class FireSwitchboardServiceImpl implements FireSwitchboardService {

    private static Map<String,String> maps = new ConcurrentHashMap<>(10);

    @Autowired
    private RocketmqService rocketmqService;

    @Override
    public void beiDaQingNiao(SmartIotProtocol smartIotProtocol, String ctxId) {

        byte[] data = smartIotProtocol.getData();

        if (data.length  == 0){
            return;
        }
        int i  = data[0] & 0xff;
        // 消防模块
        if (i == 2){
            QnComponentEntity init = QnComponentEntity.init(smartIotProtocol.getData());
            if (init ==null){return;}
            init.setId(maps.get(ctxId));
            init.setCtxId(ctxId);
            rocketmqService.send(init);
        }else  if (i == 24){
            // 装置操作
            QnOperationEntity init = QnOperationEntity.init(smartIotProtocol.getData());
            if (init ==null){return;}
            init.setId(maps.get(ctxId));
            init.setCtxId(ctxId);
            rocketmqService.send(init);
        }else  if (i==26){
            //心跳检测
            String id = XfDict.getId(smartIotProtocol.getData());
            SpringUtils.getBean(RedisService.class).setCacheObject(CacheConstants.EQ_NQ_KEY+id, ctxId, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES);
            maps.put(ctxId,id);
            log.info("心跳检测: " + id);
        }

    }


}

测试结果

springcloud 项目netty版本设置_java_02