物联网-Spring+Netty 框架整合

前景:服务处于一直对外暴漏监听状态,等待第三方设备连结,连接方式一定是配在设备中的

maven 依赖

java 复制代码
<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.43.Final</version>
</dependency>

netty 服务配置

java 复制代码
package com.demo;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.net.InetSocketAddress;
import java.net.SocketAddress;

/**
 * Netty 服务配置 负责启动netty服务,*等待设备连接*
 * 绑定端口、初始化nettyHandler
 */
@Component
@Slf4j
public class NettyServer {


    // *自定义流程处理器*    
    @Autowired
    private NettyChannelInitializer nettyHandler;

    // 监听设备的端口号
    @Value("${netty.server.port}")
    private int port;
    
    public NettyServer() {
    
    }
    
    public NettyServer(int port) {
        
        super();
        this.port = port;
    }
    
    public void start() throws InterruptedException {
        log.info("服务器启动中,端口:" + this.port);
        this.bind();
    }
    
    private void bind() throws InterruptedException {
        EventLoopGroup parentGroup = new NioEventLoopGroup();
        EventLoopGroup childGroup = new NioEventLoopGroup();
        // 绑定端口
        SocketAddress socketAddress = new InetSocketAddress(port);
        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            //设定主线程和工作线程
            serverBootstrap.group(parentGroup, childGroup)
                           .channel(NioServerSocketChannel.class)
                           .option(ChannelOption.SO_BACKLOG, 1024)
                           .localAddress(socketAddress)
                           // 处理器初始化
                           .childHandler(nettyHandler);
            ChannelFuture channelFuture = serverBootstrap.bind().sync();
            channelFuture.channel().closeFuture().sync();
        } finally {
            // 关闭服务线程
            parentGroup.shutdownGracefully().sync();
            childGroup.shutdownGracefully().sync();
        }
    }
    
}

流程处理器

java 复制代码
package com.demo;

import com.fcev.tcpserver.protocoltokafka.decoder.ByteToArrayDecoder;
import com.fcev.tcpserver.protocoltokafka.decoder.NginxProxyProtocolHandler;
import com.fcev.tcpserver.protocoltokafka.decoder.ProtocolLogAdapter;
import com.fcev.tcpserver.encoder.ProtocolEncoder;
import com.fcev.tcpserver.handler.NettyLoggingHandler;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.timeout.ReadTimeoutHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * Netty 通道初始化器
 *
 * 核心作用:
 * 每当有 TBox 客户端(货车终端)TCP 连接上来时,
 * 自动给这个连接绑定一整套【处理器流水线】,
 * 用来处理:解码、拆包、日志、超时、发 Kafka
 */
@Component
public class NettyChannelInitializer extends ChannelInitializer<SocketChannel> {
    
    // *注入:业务适配器* 这里写后续逻辑代码
    @Autowired
    private ProtocolLogAdapter protocolLogAdapter;
    
    /**
     * 当设备连接成功后,Netty 自动调用这个方法,给这个连接绑定一整套【处理器流水线】
     */
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        
        ChannelPipeline channelPipeline = socketChannel.pipeline();
        //【1】出站编码器 → 回复应答消息
        channelPipeline.addLast(new ProtocolEncoder());

        // 【2】读超时处理器断联处理-5分钟
        channelPipeline.addLast(new ReadTimeoutHandler(5*60));

        //【3】日志打印处理器
        channelPipeline.addLast(new NettyLoggingHandler());

        // 【4】获取设备真实IP(如果前面挂了Nginx代理),解决Nginx转发后获取不到真实客户端IP的问题
        channelPipeline.addLast(new NginxProxyProtocolHandler());

        /*
            【5】拆包器(最关键!解决TCP粘包/半包问题),这里的参数必须根据具体协议定
            一条报文最大 4096 字节;
            从第22个字节开始,能读到"报文总长度"
            长度占 2 个字节
            最后要 +1 字节(校验位)
            解析完直接把完整包给业务层
        */
        channelPipeline.addLast(new LengthFieldBasedFrameDecoder(4096, 22, 2, 1, 0));
        //打印日志
//        channelPipeline.addLast(new NettyLoggingHandler());

        //【6】ByteBuf 转 字节数组,把Netty的二进制数据转成Java标准byte[]
        channelPipeline.addLast(new ByteToArrayDecoder());
        //【7】原始报文发 Kafka(核心业务)将原始报文转成字符串,发送到kafka中,用来记录日志
        channelPipeline.addLast(protocolLogAdapter);
        //将报文解析成ProtocolMessage对象,根据命令id发送到相应的topic
//        channelPipeline.addLast(messageToKafkaAdapter);
        
    }
    
}

业务适配器: ProtocolLogAdapter 编写具体的业务实现,比如ip识别、报文头部识别、channelPipeline.addLast(new LengthFieldBasedFrameDecoder(4096, 22, 2, 1, 0));针对不同的业务而定。

复制代码
@Slf4j
@Component
@ChannelHandler.Sharable
public class ProtocolLogAdapter extends ChannelInboundHandlerAdapter {
    // 1. 接收数据 → 最重要
    @Override
    channelRead(){
        //释放 Netty 内存,内存泄漏、服务器宕机
        ReferenceCountUtil.release(msg)
        //确认 ACK,否则回发重复的消息
        ctx.writeAndFlush (协议应答,根据协议而定)
    }

    // 2. 断开连接 → 清理资源
    @Override
    channelUnregistered(...)

    // 3. 报错处理 → 防止程序崩
    @Override
    exceptionCaught(...)
}
相关推荐
简简单单就是我_hehe1 小时前
后端链路追踪局部采集和全量采集配置说明
java·开发语言
zshs0001 小时前
#从偶发无字幕到补偿探测链路:一次 B 站字幕导入问题的完整收敛过程
java·后端·重构
存在的五月雨2 小时前
SpringBoot 基于数据库的动态定时任务管理器实现方案
java·spring boot
椰羊~王小美2 小时前
@RequestMapping注解的各个属性作用
java
Yeh2020583 小时前
request与response笔记
java·前端·笔记
国产化创客3 小时前
龙芯 2K0300-- 实现工业网关监控仪表盘项目
嵌入式硬件·物联网·数据可视化
程序员老邢3 小时前
【产品底稿 07】商助慧 Admin 运维模块落地:从 “能跑” 到 “能运维”,3 个页面搞定日常排障
java·运维·经验分享·spring boot·后端
元宝骑士3 小时前
Spring @Async 异步无法获取当前登录用户?Sa-Token 1.34.0 终极踩坑解决方案
java·后端
0xDevNull3 小时前
Java项目中Redis热点Key自动检测方案详细教程
java·spring boot·redis